Add sticky keys input filter

DD: go/pk_accessibility
'Sticky keys' is an accessibility feature that assists users
who have physical disabilities or help users reduce repetitive
strain injury. It serializes keystrokes instead of pressing
multiple keys at a time, allowing the user to press and
release a modifier key, such as Shift, Ctrl, Alt, or any other
modifier key, and have it remain active until any other key
is pressed.

Bug: 294546335
Test: TEST=libinputflinger_rs_test; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST
Change-Id: Id26ed4a949e929cb6af8b9ecf1cd95c48fe20486
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 45c9b5c..69f42bc 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -77,6 +77,7 @@
         "InputCommonConverter.cpp",
         "InputDeviceMetricsCollector.cpp",
         "InputFilter.cpp",
+        "InputFilterCallbacks.cpp",
         "InputProcessor.cpp",
         "PointerChoreographer.cpp",
         "PreferStylusOverTouchBlocker.cpp",
diff --git a/services/inputflinger/InputFilter.cpp b/services/inputflinger/InputFilter.cpp
index 9c4a3eb..5d87d34 100644
--- a/services/inputflinger/InputFilter.cpp
+++ b/services/inputflinger/InputFilter.cpp
@@ -44,31 +44,9 @@
     return event;
 }
 
-NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) {
-    return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId,
-                         static_cast<uint32_t>(event.source), event.displayId, event.policyFlags,
-                         static_cast<int32_t>(event.action), event.flags, event.keyCode,
-                         event.scanCode, event.metaState, event.downTime);
-}
-
-namespace {
-
-class RustCallbacks : public IInputFilter::BnInputFilterCallbacks {
-public:
-    RustCallbacks(InputListenerInterface& nextListener) : mNextListener(nextListener) {}
-    ndk::ScopedAStatus sendKeyEvent(const AidlKeyEvent& event) override {
-        mNextListener.notifyKey(keyEventToNotifyKeyArgs(event));
-        return ndk::ScopedAStatus::ok();
-    }
-
-private:
-    InputListenerInterface& mNextListener;
-};
-
-} // namespace
-
 InputFilter::InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust)
-      : mNextListener(listener), mCallbacks(ndk::SharedRefBase::make<RustCallbacks>(listener)) {
+      : mNextListener(listener),
+        mCallbacks(ndk::SharedRefBase::make<InputFilterCallbacks>(listener)) {
     LOG_ALWAYS_FATAL_IF(!rust.createInputFilter(mCallbacks, &mInputFilterRust).isOk());
     LOG_ALWAYS_FATAL_IF(!mInputFilterRust);
 }
@@ -92,11 +70,11 @@
 }
 
 void InputFilter::notifyKey(const NotifyKeyArgs& args) {
-    if (!isFilterEnabled()) {
-        mNextListener.notifyKey(args);
+    if (isFilterEnabled()) {
+        LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyKey(notifyKeyArgsToKeyEvent(args)).isOk());
         return;
     }
-    LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyKey(notifyKeyArgsToKeyEvent(args)).isOk());
+    mNextListener.notifyKey(args);
 }
 
 void InputFilter::notifyMotion(const NotifyMotionArgs& args) {
@@ -138,6 +116,15 @@
     }
 }
 
+void InputFilter::setAccessibilityStickyKeysEnabled(bool enabled) {
+    std::scoped_lock _l(mLock);
+
+    if (mConfig.stickyKeysEnabled != enabled) {
+        mConfig.stickyKeysEnabled = enabled;
+        LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyConfigurationChanged(mConfig).isOk());
+    }
+}
+
 void InputFilter::dump(std::string& dump) {
     dump += "InputFilter:\n";
 }
diff --git a/services/inputflinger/InputFilter.h b/services/inputflinger/InputFilter.h
index 06f7d0e..9fa7a87 100644
--- a/services/inputflinger/InputFilter.h
+++ b/services/inputflinger/InputFilter.h
@@ -18,6 +18,7 @@
 
 #include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
 #include <utils/Mutex.h>
+#include "InputFilterCallbacks.h"
 #include "InputListener.h"
 #include "NotifyArgs.h"
 
@@ -33,6 +34,7 @@
      */
     virtual void dump(std::string& dump) = 0;
     virtual void setAccessibilityBounceKeysThreshold(nsecs_t threshold) = 0;
+    virtual void setAccessibilityStickyKeysEnabled(bool enabled) = 0;
 };
 
 class InputFilter : public InputFilterInterface {
@@ -56,11 +58,12 @@
     void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
     void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
     void setAccessibilityBounceKeysThreshold(nsecs_t threshold) override;
+    void setAccessibilityStickyKeysEnabled(bool enabled) override;
     void dump(std::string& dump) override;
 
 private:
     InputListenerInterface& mNextListener;
-    std::shared_ptr<IInputFilterCallbacks> mCallbacks;
+    std::shared_ptr<InputFilterCallbacks> mCallbacks;
     std::shared_ptr<IInputFilter> mInputFilterRust;
     mutable std::mutex mLock;
     InputFilterConfiguration mConfig GUARDED_BY(mLock);
diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp
new file mode 100644
index 0000000..8c8f5e8
--- /dev/null
+++ b/services/inputflinger/InputFilterCallbacks.cpp
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "InputFilterCallbacks"
+
+#include "InputFilterCallbacks.h"
+
+namespace android {
+
+using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent;
+
+NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) {
+    return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId,
+                         static_cast<uint32_t>(event.source), event.displayId, event.policyFlags,
+                         static_cast<int32_t>(event.action), event.flags, event.keyCode,
+                         event.scanCode, event.metaState, event.downTime);
+}
+
+InputFilterCallbacks::InputFilterCallbacks(InputListenerInterface& listener)
+      : mNextListener(listener) {}
+
+ndk::ScopedAStatus InputFilterCallbacks::sendKeyEvent(const AidlKeyEvent& event) {
+    mNextListener.notifyKey(keyEventToNotifyKeyArgs(event));
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus InputFilterCallbacks::onModifierStateChanged(int32_t modifierState,
+                                                                int32_t lockedModifierState) {
+    std::scoped_lock _l(mLock);
+    mStickyModifierState.modifierState = modifierState;
+    mStickyModifierState.lockedModifierState = lockedModifierState;
+    ALOGI("Sticky keys modifier state changed: modifierState=%d, lockedModifierState=%d",
+          modifierState, lockedModifierState);
+    return ndk::ScopedAStatus::ok();
+}
+
+uint32_t InputFilterCallbacks::getModifierState() {
+    std::scoped_lock _l(mLock);
+    return mStickyModifierState.modifierState;
+}
+
+uint32_t InputFilterCallbacks::getLockedModifierState() {
+    std::scoped_lock _l(mLock);
+    return mStickyModifierState.lockedModifierState;
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputFilterCallbacks.h b/services/inputflinger/InputFilterCallbacks.h
new file mode 100644
index 0000000..c0a80fb
--- /dev/null
+++ b/services/inputflinger/InputFilterCallbacks.h
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
+#include <android/binder_auto_utils.h>
+#include <utils/Mutex.h>
+#include <mutex>
+#include "InputListener.h"
+#include "NotifyArgs.h"
+
+/**
+ * The C++ component of InputFilter designed as a wrapper around the rust callback implementation.
+ */
+namespace android {
+
+using IInputFilter = aidl::com::android::server::inputflinger::IInputFilter;
+using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent;
+
+class InputFilterCallbacks : public IInputFilter::BnInputFilterCallbacks {
+public:
+    explicit InputFilterCallbacks(InputListenerInterface& listener);
+    ~InputFilterCallbacks() override = default;
+
+    uint32_t getModifierState();
+    uint32_t getLockedModifierState();
+
+private:
+    InputListenerInterface& mNextListener;
+    mutable std::mutex mLock;
+    struct StickyModifierState {
+        uint32_t modifierState;
+        uint32_t lockedModifierState;
+    } mStickyModifierState GUARDED_BY(mLock);
+
+    ndk::ScopedAStatus sendKeyEvent(const AidlKeyEvent& event) override;
+    ndk::ScopedAStatus onModifierStateChanged(int32_t modifierState,
+                                              int32_t lockedModifierState) override;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
index 14b41cd..2921d30 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
@@ -33,6 +33,9 @@
     interface IInputFilterCallbacks {
         /** Sends back a filtered key event */
         void sendKeyEvent(in KeyEvent event);
+
+        /** Sends back modifier state */
+        void onModifierStateChanged(int modifierState, int lockedModifierState);
     }
 
     /** Returns if InputFilter is enabled */
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
index 3b2e88b..38b1612 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
@@ -22,4 +22,6 @@
 parcelable InputFilterConfiguration {
     // Threshold value for Bounce keys filter (check bounce_keys_filter.rs)
     long bounceKeysThresholdNs;
+    // If sticky keys filter is enabled
+    boolean stickyKeysEnabled;
 }
\ No newline at end of file
diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
index 340ff8e..e94a71f 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -27,6 +27,7 @@
 };
 
 use crate::bounce_keys_filter::BounceKeysFilter;
+use crate::sticky_keys_filter::StickyKeysFilter;
 use log::{error, info};
 use std::sync::{Arc, Mutex, RwLock};
 
@@ -91,6 +92,14 @@
         let mut state = self.state.lock().unwrap();
         let mut first_filter: Box<dyn Filter + Send + Sync> =
             Box::new(BaseFilter::new(self.callbacks.clone()));
+        if config.stickyKeysEnabled {
+            first_filter = Box::new(StickyKeysFilter::new(
+                first_filter,
+                ModifierStateListener::new(self.callbacks.clone()),
+            ));
+            state.enabled = true;
+            info!("Sticky keys filter is installed");
+        }
         if config.bounceKeysThresholdNs > 0 {
             first_filter =
                 Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs));
@@ -125,34 +134,43 @@
     }
 }
 
+pub struct ModifierStateListener {
+    callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+}
+
+impl ModifierStateListener {
+    /// Create a new InputFilter instance.
+    pub fn new(callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>) -> ModifierStateListener {
+        Self { callbacks }
+    }
+
+    pub fn modifier_state_changed(&self, modifier_state: u32, locked_modifier_state: u32) {
+        let _ = self
+            .callbacks
+            .read()
+            .unwrap()
+            .onModifierStateChanged(modifier_state as i32, locked_modifier_state as i32);
+    }
+}
+
 #[cfg(test)]
 mod tests {
-    use crate::input_filter::{test_filter::TestFilter, Filter, InputFilter};
+    use crate::input_filter::{
+        test_callbacks::TestCallbacks, test_filter::TestFilter, InputFilter,
+    };
     use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
-    use binder::{Interface, Strong};
+    use binder::Strong;
     use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
         DeviceInfo::DeviceInfo, IInputFilter::IInputFilter,
-        IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
         InputFilterConfiguration::InputFilterConfiguration, KeyEvent::KeyEvent,
         KeyEventAction::KeyEventAction,
     };
     use std::sync::{Arc, RwLock};
 
-    struct FakeCallbacks {}
-
-    impl Interface for FakeCallbacks {}
-
-    impl IInputFilterCallbacks for FakeCallbacks {
-        fn sendKeyEvent(&self, _event: &KeyEvent) -> binder::Result<()> {
-            Result::Ok(())
-        }
-    }
-
     #[test]
     fn test_not_enabled_with_default_filter() {
-        let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
-            Strong::new(Box::new(FakeCallbacks {}));
-        let input_filter = InputFilter::new(fake_callbacks);
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
         let result = input_filter.isEnabled();
         assert!(result.is_ok());
         assert!(!result.unwrap());
@@ -160,17 +178,21 @@
 
     #[test]
     fn test_notify_key_with_no_filters() {
-        let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
-            Strong::new(Box::new(FakeCallbacks {}));
-        let input_filter = InputFilter::new(fake_callbacks);
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks.clone())));
         let event = create_key_event();
         assert!(input_filter.notifyKey(&event).is_ok());
+        assert_eq!(test_callbacks.last_event().unwrap(), event);
     }
 
     #[test]
     fn test_notify_key_with_filter() {
         let test_filter = TestFilter::new();
-        let input_filter = create_input_filter(Box::new(test_filter.clone()));
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::create_input_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))),
+        );
         let event = create_key_event();
         assert!(input_filter.notifyKey(&event).is_ok());
         assert_eq!(test_filter.last_event().unwrap(), event);
@@ -179,7 +201,11 @@
     #[test]
     fn test_notify_devices_changed() {
         let test_filter = TestFilter::new();
-        let input_filter = create_input_filter(Box::new(test_filter.clone()));
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::create_input_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))),
+        );
         assert!(input_filter
             .notifyInputDevicesChanged(&[DeviceInfo { deviceId: 0, external: true }])
             .is_ok());
@@ -188,21 +214,30 @@
 
     #[test]
     fn test_notify_configuration_changed_enabled_bounce_keys() {
-        let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
-            Strong::new(Box::new(FakeCallbacks {}));
-        let input_filter = InputFilter::new(fake_callbacks);
-        let result = input_filter
-            .notifyConfigurationChanged(&InputFilterConfiguration { bounceKeysThresholdNs: 100 });
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
+        let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
+            bounceKeysThresholdNs: 100,
+            stickyKeysEnabled: false,
+        });
         assert!(result.is_ok());
         let result = input_filter.isEnabled();
         assert!(result.is_ok());
         assert!(result.unwrap());
     }
 
-    fn create_input_filter(filter: Box<dyn Filter + Send + Sync>) -> InputFilter {
-        let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
-            Strong::new(Box::new(FakeCallbacks {}));
-        InputFilter::create_input_filter(filter, Arc::new(RwLock::new(fake_callbacks)))
+    #[test]
+    fn test_notify_configuration_changed_enabled_sticky_keys() {
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
+        let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
+            bounceKeysThresholdNs: 0,
+            stickyKeysEnabled: true,
+        });
+        assert!(result.is_ok());
+        let result = input_filter.isEnabled();
+        assert!(result.is_ok());
+        assert!(result.unwrap());
     }
 
     fn create_key_event() -> KeyEvent {
@@ -272,3 +307,69 @@
         }
     }
 }
+
+#[cfg(test)]
+pub mod test_callbacks {
+    use binder::Interface;
+    use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+        IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks, KeyEvent::KeyEvent,
+    };
+    use std::sync::{Arc, RwLock, RwLockWriteGuard};
+
+    #[derive(Default)]
+    struct TestCallbacksInner {
+        last_modifier_state: u32,
+        last_locked_modifier_state: u32,
+        last_event: Option<KeyEvent>,
+    }
+
+    #[derive(Default, Clone)]
+    pub struct TestCallbacks(Arc<RwLock<TestCallbacksInner>>);
+
+    impl Interface for TestCallbacks {}
+
+    impl TestCallbacks {
+        pub fn new() -> Self {
+            Default::default()
+        }
+
+        fn inner(&self) -> RwLockWriteGuard<'_, TestCallbacksInner> {
+            self.0.write().unwrap()
+        }
+
+        pub fn last_event(&self) -> Option<KeyEvent> {
+            self.0.read().unwrap().last_event
+        }
+
+        pub fn clear(&mut self) {
+            self.inner().last_event = None;
+            self.inner().last_modifier_state = 0;
+            self.inner().last_locked_modifier_state = 0;
+        }
+
+        pub fn get_last_modifier_state(&self) -> u32 {
+            self.0.read().unwrap().last_modifier_state
+        }
+
+        pub fn get_last_locked_modifier_state(&self) -> u32 {
+            self.0.read().unwrap().last_locked_modifier_state
+        }
+    }
+
+    impl IInputFilterCallbacks for TestCallbacks {
+        fn sendKeyEvent(&self, event: &KeyEvent) -> binder::Result<()> {
+            self.inner().last_event = Some(*event);
+            Result::Ok(())
+        }
+
+        fn onModifierStateChanged(
+            &self,
+            modifier_state: i32,
+            locked_modifier_state: i32,
+        ) -> std::result::Result<(), binder::Status> {
+            self.inner().last_modifier_state = modifier_state as u32;
+            self.inner().last_locked_modifier_state = locked_modifier_state as u32;
+            Result::Ok(())
+        }
+    }
+}
diff --git a/services/inputflinger/rust/lib.rs b/services/inputflinger/rust/lib.rs
index 68cd480..fa16898 100644
--- a/services/inputflinger/rust/lib.rs
+++ b/services/inputflinger/rust/lib.rs
@@ -21,6 +21,7 @@
 
 mod bounce_keys_filter;
 mod input_filter;
+mod sticky_keys_filter;
 
 use crate::input_filter::InputFilter;
 use binder::{
diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs
new file mode 100644
index 0000000..da581b8
--- /dev/null
+++ b/services/inputflinger/rust/sticky_keys_filter.rs
@@ -0,0 +1,515 @@
+/*
+ * 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.
+ */
+
+//! Sticky keys input filter implementation.
+//! Sticky keys is an accessibility feature that assists users who have physical disabilities or
+//! helps users reduce repetitive strain injury. It serializes keystrokes instead of pressing
+//! multiple keys at a time, allowing the user to press and release a modifier key, such as Shift,
+//! Ctrl, Alt, or any other modifier key, and have it remain active until any other key is pressed.
+use crate::input_filter::{Filter, ModifierStateListener};
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+    DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+};
+use std::collections::HashSet;
+
+// Modifier keycodes: values are from /frameworks/native/include/android/keycodes.h
+const KEYCODE_ALT_LEFT: i32 = 57;
+const KEYCODE_ALT_RIGHT: i32 = 58;
+const KEYCODE_SHIFT_LEFT: i32 = 59;
+const KEYCODE_SHIFT_RIGHT: i32 = 60;
+const KEYCODE_SYM: i32 = 63;
+const KEYCODE_CTRL_LEFT: i32 = 113;
+const KEYCODE_CTRL_RIGHT: i32 = 114;
+const KEYCODE_CAPS_LOCK: i32 = 115;
+const KEYCODE_SCROLL_LOCK: i32 = 116;
+const KEYCODE_META_LEFT: i32 = 117;
+const KEYCODE_META_RIGHT: i32 = 118;
+const KEYCODE_FUNCTION: i32 = 119;
+const KEYCODE_NUM_LOCK: i32 = 143;
+
+// Modifier states: values are from /frameworks/native/include/android/input.h
+const META_ALT_ON: u32 = 0x02;
+const META_ALT_LEFT_ON: u32 = 0x10;
+const META_ALT_RIGHT_ON: u32 = 0x20;
+const META_SHIFT_ON: u32 = 0x01;
+const META_SHIFT_LEFT_ON: u32 = 0x40;
+const META_SHIFT_RIGHT_ON: u32 = 0x80;
+const META_CTRL_ON: u32 = 0x1000;
+const META_CTRL_LEFT_ON: u32 = 0x2000;
+const META_CTRL_RIGHT_ON: u32 = 0x4000;
+const META_META_ON: u32 = 0x10000;
+const META_META_LEFT_ON: u32 = 0x20000;
+const META_META_RIGHT_ON: u32 = 0x40000;
+
+pub struct StickyKeysFilter {
+    next: Box<dyn Filter + Send + Sync>,
+    listener: ModifierStateListener,
+    /// Tracking devices that contributed to the modifier state.
+    contributing_devices: HashSet<i32>,
+    /// State describing the current enabled modifiers. This contain both locked and non-locked
+    /// modifier state bits.
+    modifier_state: u32,
+    /// State describing the current locked modifiers. These modifiers will not be cleared on a
+    /// non-modifier key press. They will be cleared only if the locked modifier key is pressed
+    /// again.
+    locked_modifier_state: u32,
+}
+
+impl StickyKeysFilter {
+    /// Create a new StickyKeysFilter instance.
+    pub fn new(
+        next: Box<dyn Filter + Send + Sync>,
+        listener: ModifierStateListener,
+    ) -> StickyKeysFilter {
+        Self {
+            next,
+            listener,
+            contributing_devices: HashSet::new(),
+            modifier_state: 0,
+            locked_modifier_state: 0,
+        }
+    }
+}
+
+impl Filter for StickyKeysFilter {
+    fn notify_key(&mut self, event: &KeyEvent) {
+        let up = event.action == KeyEventAction::UP;
+        let mut modifier_state = self.modifier_state;
+        let mut locked_modifier_state = self.locked_modifier_state;
+        if !is_ephemeral_modifier_key(event.keyCode) {
+            // If non-ephemeral modifier key (i.e. non-modifier keys + toggle modifier keys like
+            // CAPS_LOCK, NUM_LOCK etc.), don't block key and pass in the sticky modifier state with
+            // the KeyEvent.
+            let old_modifier_state = event.metaState as u32;
+            let mut new_event = *event;
+            // Send the current modifier state with the key event before clearing non-locked
+            // modifier state
+            new_event.metaState =
+                (clear_ephemeral_modifier_state(old_modifier_state) | modifier_state) as i32;
+            self.next.notify_key(&new_event);
+            if up && !is_modifier_key(event.keyCode) {
+                modifier_state =
+                    clear_ephemeral_modifier_state(modifier_state) | locked_modifier_state;
+            }
+        } else if up {
+            // Update contributing devices to track keyboards
+            self.contributing_devices.insert(event.deviceId);
+            // If ephemeral modifier key, capture the key and update the sticky modifier states
+            let modifier_key_mask = get_ephemeral_modifier_key_mask(event.keyCode);
+            let symmetrical_modifier_key_mask = get_symmetrical_modifier_key_mask(event.keyCode);
+            if locked_modifier_state & modifier_key_mask != 0 {
+                locked_modifier_state &= !symmetrical_modifier_key_mask;
+                modifier_state &= !symmetrical_modifier_key_mask;
+            } else if modifier_key_mask & modifier_state != 0 {
+                locked_modifier_state |= modifier_key_mask;
+                modifier_state =
+                    (modifier_state & !symmetrical_modifier_key_mask) | modifier_key_mask;
+            } else {
+                modifier_state |= modifier_key_mask;
+            }
+        }
+        if self.modifier_state != modifier_state
+            || self.locked_modifier_state != locked_modifier_state
+        {
+            self.modifier_state = modifier_state;
+            self.locked_modifier_state = locked_modifier_state;
+            self.listener.modifier_state_changed(modifier_state, locked_modifier_state);
+        }
+    }
+
+    fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) {
+        // Clear state if all contributing devices removed
+        self.contributing_devices.retain(|id| device_infos.iter().any(|x| *id == x.deviceId));
+        if self.contributing_devices.is_empty()
+            && (self.modifier_state != 0 || self.locked_modifier_state != 0)
+        {
+            self.modifier_state = 0;
+            self.locked_modifier_state = 0;
+            self.listener.modifier_state_changed(0, 0);
+        }
+        self.next.notify_devices_changed(device_infos);
+    }
+}
+
+fn is_modifier_key(keycode: i32) -> bool {
+    matches!(
+        keycode,
+        KEYCODE_ALT_LEFT
+            | KEYCODE_ALT_RIGHT
+            | KEYCODE_SHIFT_LEFT
+            | KEYCODE_SHIFT_RIGHT
+            | KEYCODE_CTRL_LEFT
+            | KEYCODE_CTRL_RIGHT
+            | KEYCODE_META_LEFT
+            | KEYCODE_META_RIGHT
+            | KEYCODE_SYM
+            | KEYCODE_FUNCTION
+            | KEYCODE_CAPS_LOCK
+            | KEYCODE_NUM_LOCK
+            | KEYCODE_SCROLL_LOCK
+    )
+}
+
+fn is_ephemeral_modifier_key(keycode: i32) -> bool {
+    matches!(
+        keycode,
+        KEYCODE_ALT_LEFT
+            | KEYCODE_ALT_RIGHT
+            | KEYCODE_SHIFT_LEFT
+            | KEYCODE_SHIFT_RIGHT
+            | KEYCODE_CTRL_LEFT
+            | KEYCODE_CTRL_RIGHT
+            | KEYCODE_META_LEFT
+            | KEYCODE_META_RIGHT
+    )
+}
+
+fn get_ephemeral_modifier_key_mask(keycode: i32) -> u32 {
+    match keycode {
+        KEYCODE_ALT_LEFT => META_ALT_LEFT_ON | META_ALT_ON,
+        KEYCODE_ALT_RIGHT => META_ALT_RIGHT_ON | META_ALT_ON,
+        KEYCODE_SHIFT_LEFT => META_SHIFT_LEFT_ON | META_SHIFT_ON,
+        KEYCODE_SHIFT_RIGHT => META_SHIFT_RIGHT_ON | META_SHIFT_ON,
+        KEYCODE_CTRL_LEFT => META_CTRL_LEFT_ON | META_CTRL_ON,
+        KEYCODE_CTRL_RIGHT => META_CTRL_RIGHT_ON | META_CTRL_ON,
+        KEYCODE_META_LEFT => META_META_LEFT_ON | META_META_ON,
+        KEYCODE_META_RIGHT => META_META_RIGHT_ON | META_META_ON,
+        _ => 0,
+    }
+}
+
+/// Modifier mask including both left and right versions of a modifier key.
+fn get_symmetrical_modifier_key_mask(keycode: i32) -> u32 {
+    match keycode {
+        KEYCODE_ALT_LEFT | KEYCODE_ALT_RIGHT => META_ALT_LEFT_ON | META_ALT_RIGHT_ON | META_ALT_ON,
+        KEYCODE_SHIFT_LEFT | KEYCODE_SHIFT_RIGHT => {
+            META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON | META_SHIFT_ON
+        }
+        KEYCODE_CTRL_LEFT | KEYCODE_CTRL_RIGHT => {
+            META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON | META_CTRL_ON
+        }
+        KEYCODE_META_LEFT | KEYCODE_META_RIGHT => {
+            META_META_LEFT_ON | META_META_RIGHT_ON | META_META_ON
+        }
+        _ => 0,
+    }
+}
+
+fn clear_ephemeral_modifier_state(modifier_state: u32) -> u32 {
+    modifier_state
+        & !(META_ALT_LEFT_ON
+            | META_ALT_RIGHT_ON
+            | META_ALT_ON
+            | META_SHIFT_LEFT_ON
+            | META_SHIFT_RIGHT_ON
+            | META_SHIFT_ON
+            | META_CTRL_LEFT_ON
+            | META_CTRL_RIGHT_ON
+            | META_CTRL_ON
+            | META_META_LEFT_ON
+            | META_META_RIGHT_ON
+            | META_META_ON)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::input_filter::{
+        test_callbacks::TestCallbacks, test_filter::TestFilter, Filter, ModifierStateListener,
+    };
+    use crate::sticky_keys_filter::{
+        StickyKeysFilter, KEYCODE_ALT_LEFT, KEYCODE_ALT_RIGHT, KEYCODE_CAPS_LOCK,
+        KEYCODE_CTRL_LEFT, KEYCODE_CTRL_RIGHT, KEYCODE_FUNCTION, KEYCODE_META_LEFT,
+        KEYCODE_META_RIGHT, KEYCODE_NUM_LOCK, KEYCODE_SCROLL_LOCK, KEYCODE_SHIFT_LEFT,
+        KEYCODE_SHIFT_RIGHT, KEYCODE_SYM, META_ALT_LEFT_ON, META_ALT_ON, META_ALT_RIGHT_ON,
+        META_CTRL_LEFT_ON, META_CTRL_ON, META_CTRL_RIGHT_ON, META_META_LEFT_ON, META_META_ON,
+        META_META_RIGHT_ON, META_SHIFT_LEFT_ON, META_SHIFT_ON, META_SHIFT_RIGHT_ON,
+    };
+    use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+    use binder::Strong;
+    use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+        DeviceInfo::DeviceInfo, IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
+        KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+    };
+    use std::sync::{Arc, RwLock};
+
+    static DEVICE_ID: i32 = 1;
+    static KEY_A: i32 = 29;
+    static BASE_KEY_DOWN: KeyEvent = KeyEvent {
+        id: 1,
+        deviceId: DEVICE_ID,
+        downTime: 0,
+        readTime: 0,
+        eventTime: 0,
+        source: Source::KEYBOARD,
+        displayId: 0,
+        policyFlags: 0,
+        action: KeyEventAction::DOWN,
+        flags: 0,
+        keyCode: 0,
+        scanCode: 0,
+        metaState: 0,
+    };
+
+    static BASE_KEY_UP: KeyEvent = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_DOWN };
+
+    #[test]
+    fn test_notify_key_consumes_ephemeral_modifier_keys() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let key_codes = &[
+            KEYCODE_ALT_LEFT,
+            KEYCODE_ALT_RIGHT,
+            KEYCODE_CTRL_LEFT,
+            KEYCODE_CTRL_RIGHT,
+            KEYCODE_SHIFT_LEFT,
+            KEYCODE_SHIFT_RIGHT,
+            KEYCODE_META_LEFT,
+            KEYCODE_META_RIGHT,
+        ];
+        for key_code in key_codes.iter() {
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: *key_code, ..BASE_KEY_DOWN });
+            assert!(test_filter.last_event().is_none());
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: *key_code, ..BASE_KEY_UP });
+            assert!(test_filter.last_event().is_none());
+        }
+    }
+
+    #[test]
+    fn test_notify_key_passes_non_ephemeral_modifier_keys() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let key_codes = &[
+            KEYCODE_CAPS_LOCK,
+            KEYCODE_NUM_LOCK,
+            KEYCODE_SCROLL_LOCK,
+            KEYCODE_FUNCTION,
+            KEYCODE_SYM,
+        ];
+        for key_code in key_codes.iter() {
+            let event = KeyEvent { keyCode: *key_code, ..BASE_KEY_DOWN };
+            sticky_keys_filter.notify_key(&event);
+            assert_eq!(test_filter.last_event().unwrap(), event);
+            let event = KeyEvent { keyCode: *key_code, ..BASE_KEY_UP };
+            sticky_keys_filter.notify_key(&event);
+            assert_eq!(test_filter.last_event().unwrap(), event);
+        }
+    }
+
+    #[test]
+    fn test_notify_key_passes_non_modifier_keys() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let event = KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN };
+        sticky_keys_filter.notify_key(&event);
+        assert_eq!(test_filter.last_event().unwrap(), event);
+
+        let event = KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP };
+        sticky_keys_filter.notify_key(&event);
+        assert_eq!(test_filter.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_modifier_state_updated_on_modifier_key_press() {
+        let mut test_filter = TestFilter::new();
+        let mut test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let test_states = &[
+            (KEYCODE_ALT_LEFT, META_ALT_ON | META_ALT_LEFT_ON),
+            (KEYCODE_ALT_RIGHT, META_ALT_ON | META_ALT_RIGHT_ON),
+            (KEYCODE_CTRL_LEFT, META_CTRL_ON | META_CTRL_LEFT_ON),
+            (KEYCODE_CTRL_RIGHT, META_CTRL_ON | META_CTRL_RIGHT_ON),
+            (KEYCODE_SHIFT_LEFT, META_SHIFT_ON | META_SHIFT_LEFT_ON),
+            (KEYCODE_SHIFT_RIGHT, META_SHIFT_ON | META_SHIFT_RIGHT_ON),
+            (KEYCODE_META_LEFT, META_META_ON | META_META_LEFT_ON),
+            (KEYCODE_META_RIGHT, META_META_ON | META_META_RIGHT_ON),
+        ];
+        for test_state in test_states.iter() {
+            test_filter.clear();
+            test_callbacks.clear();
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
+            assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+            // Re-send keys to lock it
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), test_state.1);
+
+            // Re-send keys to clear
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), test_state.1);
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
+            assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+        }
+    }
+
+    #[test]
+    fn test_modifier_state_cleared_on_non_modifier_key_press() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+
+        assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+    }
+
+    #[test]
+    fn test_locked_modifier_state_not_cleared_on_non_modifier_key_press() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_SHIFT_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_SHIFT_LEFT, ..BASE_KEY_UP });
+
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            META_SHIFT_LEFT_ON | META_SHIFT_ON | META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+        assert_eq!(
+            test_callbacks.get_last_locked_modifier_state(),
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+
+        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(
+            test_callbacks.get_last_locked_modifier_state(),
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+    }
+
+    #[test]
+    fn test_key_events_have_sticky_modifier_state() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
+        assert_eq!(
+            test_filter.last_event().unwrap().metaState as u32,
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+        assert_eq!(
+            test_filter.last_event().unwrap().metaState as u32,
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+    }
+
+    #[test]
+    fn test_modifier_state_not_cleared_until_all_devices_removed() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 1,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_DOWN
+        });
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 1,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_UP
+        });
+
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 2,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_DOWN
+        });
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 2,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_UP
+        });
+
+        sticky_keys_filter.notify_devices_changed(&[DeviceInfo { deviceId: 2, external: true }]);
+        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(
+            test_callbacks.get_last_locked_modifier_state(),
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+
+        sticky_keys_filter.notify_devices_changed(&[]);
+        assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+    }
+
+    fn setup_filter(
+        next: Box<dyn Filter + Send + Sync>,
+        callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+    ) -> StickyKeysFilter {
+        StickyKeysFilter::new(next, ModifierStateListener::new(callbacks))
+    }
+}