diff options
author | 2023-12-11 10:53:08 +0000 | |
---|---|---|
committer | 2023-12-11 10:53:08 +0000 | |
commit | 34216dcfd4b5221f6508a44e4abb500f028d72f7 (patch) | |
tree | 05c2588d3481e60516995a9a3ad0f4744c6f74cd | |
parent | b1cd51911b0eac1ee599f16d13db4c3af75f4e18 (diff) | |
parent | 953e6a447d18de1929ed2fc5ccd2fcec5c880e52 (diff) |
Merge "Add bounce keys input filter" into main
14 files changed, 608 insertions, 34 deletions
diff --git a/services/inputflinger/InputFilter.cpp b/services/inputflinger/InputFilter.cpp index 1b8fad3c65..9c4a3eb274 100644 --- a/services/inputflinger/InputFilter.cpp +++ b/services/inputflinger/InputFilter.cpp @@ -22,16 +22,19 @@ namespace android { using aidl::com::android::server::inputflinger::IInputFilter; using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent; +using aidl::com::android::server::inputflinger::KeyEventAction; +using AidlDeviceInfo = aidl::com::android::server::inputflinger::DeviceInfo; +using aidl::android::hardware::input::common::Source; AidlKeyEvent notifyKeyArgsToKeyEvent(const NotifyKeyArgs& args) { AidlKeyEvent event; event.id = args.id; event.eventTime = args.eventTime; event.deviceId = args.deviceId; - event.source = args.source; + event.source = static_cast<Source>(args.source); event.displayId = args.displayId; event.policyFlags = args.policyFlags; - event.action = args.action; + event.action = static_cast<KeyEventAction>(args.action); event.flags = args.flags; event.keyCode = args.keyCode; event.scanCode = args.scanCode; @@ -42,9 +45,10 @@ AidlKeyEvent notifyKeyArgsToKeyEvent(const NotifyKeyArgs& args) { } NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) { - return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId, event.source, - event.displayId, event.policyFlags, event.action, event.flags, - event.keyCode, event.scanCode, event.metaState, event.downTime); + 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 { @@ -71,11 +75,14 @@ InputFilter::InputFilter(InputListenerInterface& listener, IInputFlingerRust& ru void InputFilter::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { if (isFilterEnabled()) { - std::vector<int32_t> deviceIds; + std::vector<AidlDeviceInfo> deviceInfos; for (auto info : args.inputDeviceInfos) { - deviceIds.push_back(info.getId()); + AidlDeviceInfo aidlInfo; + aidlInfo.deviceId = info.getId(); + aidlInfo.external = info.isExternal(); + deviceInfos.push_back(aidlInfo); } - LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(deviceIds).isOk()); + LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(deviceInfos).isOk()); } mNextListener.notify(args); } @@ -122,6 +129,15 @@ bool InputFilter::isFilterEnabled() { return result; } +void InputFilter::setAccessibilityBounceKeysThreshold(nsecs_t threshold) { + std::scoped_lock _l(mLock); + + if (mConfig.bounceKeysThresholdNs != threshold) { + mConfig.bounceKeysThresholdNs = threshold; + 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 699f3a0151..06f7d0e601 100644 --- a/services/inputflinger/InputFilter.h +++ b/services/inputflinger/InputFilter.h @@ -17,6 +17,7 @@ #pragma once #include <aidl/com/android/server/inputflinger/IInputFlingerRust.h> +#include <utils/Mutex.h> #include "InputListener.h" #include "NotifyArgs.h" @@ -31,6 +32,7 @@ public: * This method may be called on any thread (usually by the input manager on a binder thread). */ virtual void dump(std::string& dump) = 0; + virtual void setAccessibilityBounceKeysThreshold(nsecs_t threshold) = 0; }; class InputFilter : public InputFilterInterface { @@ -39,6 +41,8 @@ public: using IInputFilter = aidl::com::android::server::inputflinger::IInputFilter; using IInputFilterCallbacks = aidl::com::android::server::inputflinger::IInputFilter::IInputFilterCallbacks; + using InputFilterConfiguration = + aidl::com::android::server::inputflinger::InputFilterConfiguration; explicit InputFilter(InputListenerInterface& listener, IInputFlingerRust&); ~InputFilter() override = default; @@ -51,12 +55,15 @@ public: void notifyVibratorState(const NotifyVibratorStateArgs& args) override; void notifyDeviceReset(const NotifyDeviceResetArgs& args) override; void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override; + void setAccessibilityBounceKeysThreshold(nsecs_t threshold) override; void dump(std::string& dump) override; private: InputListenerInterface& mNextListener; std::shared_ptr<IInputFilterCallbacks> mCallbacks; std::shared_ptr<IInputFilter> mInputFilterRust; + mutable std::mutex mLock; + InputFilterConfiguration mConfig GUARDED_BY(mLock); bool isFilterEnabled(); }; diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp index af4ba5a012..296f2449ea 100644 --- a/services/inputflinger/InputManager.cpp +++ b/services/inputflinger/InputManager.cpp @@ -224,6 +224,10 @@ InputDispatcherInterface& InputManager::getDispatcher() { return *mDispatcher; } +InputFilterInterface& InputManager::getInputFilter() { + return *mInputFilter; +} + void InputManager::monitor() { mReader->monitor(); mBlocker->monitor(); diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h index aea7bd56ae..fa7db379e0 100644 --- a/services/inputflinger/InputManager.h +++ b/services/inputflinger/InputManager.h @@ -102,6 +102,9 @@ public: /* Gets the input dispatcher. */ virtual InputDispatcherInterface& getDispatcher() = 0; + /* Gets the input filter */ + virtual InputFilterInterface& getInputFilter() = 0; + /* Check that the input stages have not deadlocked. */ virtual void monitor() = 0; @@ -126,6 +129,7 @@ public: InputProcessorInterface& getProcessor() override; InputDeviceMetricsCollectorInterface& getMetricsCollector() override; InputDispatcherInterface& getDispatcher() override; + InputFilterInterface& getInputFilter() override; void monitor() override; void dump(std::string& dump) override; diff --git a/services/inputflinger/aidl/Android.bp b/services/inputflinger/aidl/Android.bp index 314c43356d..d06812995c 100644 --- a/services/inputflinger/aidl/Android.bp +++ b/services/inputflinger/aidl/Android.bp @@ -17,6 +17,9 @@ aidl_interface { srcs: ["**/*.aidl"], unstable: true, host_supported: true, + imports: [ + "android.hardware.input.common-V1", + ], backend: { cpp: { enabled: false, diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl new file mode 100644 index 0000000000..b9e6a03560 --- /dev/null +++ b/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl @@ -0,0 +1,26 @@ +/* + * 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. + */ + +package com.android.server.inputflinger; + +/** + * Analogous to Android's InputDeviceInfo + * Stores the basic information connected input devices. + */ +parcelable DeviceInfo { + int deviceId; + boolean external; +}
\ 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 44f959ef63..14b41cd00c 100644 --- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl +++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl @@ -16,6 +16,8 @@ package com.android.server.inputflinger; +import com.android.server.inputflinger.DeviceInfo; +import com.android.server.inputflinger.InputFilterConfiguration; import com.android.server.inputflinger.KeyEvent; /** @@ -40,6 +42,9 @@ interface IInputFilter { void notifyKey(in KeyEvent event); /** Notifies if any InputDevice list changed and provides the list of connected peripherals */ - void notifyInputDevicesChanged(in int[] deviceIds); + void notifyInputDevicesChanged(in DeviceInfo[] deviceInfos); + + /** Notifies when configuration changes */ + void notifyConfigurationChanged(in InputFilterConfiguration config); } diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl new file mode 100644 index 0000000000..3b2e88ba24 --- /dev/null +++ b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package com.android.server.inputflinger; + +/** + * Contains data for the current Input filter configuration + */ +parcelable InputFilterConfiguration { + // Threshold value for Bounce keys filter (check bounce_keys_filter.rs) + long bounceKeysThresholdNs; +}
\ No newline at end of file diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl index e213221d44..2cae6e1673 100644 --- a/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl +++ b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl @@ -16,20 +16,24 @@ package com.android.server.inputflinger; +import android.hardware.input.common.Source; +import com.android.server.inputflinger.KeyEventAction; + /** * Analogous to Android's native KeyEvent / NotifyKeyArgs. * Stores the basic information about Key events. */ +@RustDerive(Copy=true, Clone=true, Eq=true, PartialEq=true) parcelable KeyEvent { int id; int deviceId; long downTime; long readTime; long eventTime; - int source; + Source source; int displayId; int policyFlags; - int action; + KeyEventAction action; int flags; int keyCode; int scanCode; diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/KeyEventAction.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEventAction.aidl new file mode 100644 index 0000000000..43ee5fe069 --- /dev/null +++ b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEventAction.aidl @@ -0,0 +1,37 @@ +/* + * 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. + */ + +package com.android.server.inputflinger; + +/** Different Key event actions */ +enum KeyEventAction { + /** The key has been pressed down. */ + DOWN = 0, + + /** The key has been released. */ + UP = 1, + + /** + * Multiple duplicate key events have occurred in a row, or a + * complex string is being delivered. The repeat_count property + * of the key event contains the number of times the given key + * code should be executed. + * + * NOTE: This is deprecated and should never be used. This just + * for consistency with KeyEvent actions defined in NotifyKeyArgs. + */ + MULTIPLE = 2 +}
\ No newline at end of file diff --git a/services/inputflinger/rust/Android.bp b/services/inputflinger/rust/Android.bp index 2775bcc962..2803805619 100644 --- a/services/inputflinger/rust/Android.bp +++ b/services/inputflinger/rust/Android.bp @@ -38,6 +38,7 @@ rust_defaults { rustlibs: [ "libcxx", "com.android.server.inputflinger-rust", + "android.hardware.input.common-V1-rust", "libbinder_rs", "liblog_rust", "liblogger", diff --git a/services/inputflinger/rust/bounce_keys_filter.rs b/services/inputflinger/rust/bounce_keys_filter.rs new file mode 100644 index 0000000000..894b881638 --- /dev/null +++ b/services/inputflinger/rust/bounce_keys_filter.rs @@ -0,0 +1,289 @@ +/* + * 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. + */ + +//! Bounce keys input filter implementation. +//! Bounce keys is an accessibility feature to aid users who have physical disabilities, that +//! allows the user to configure the device to ignore rapid, repeated key presses of the same key. +use crate::input_filter::Filter; + +use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source; +use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ + DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction, +}; +use log::debug; +use std::collections::{HashMap, HashSet}; + +#[derive(Debug)] +struct LastUpKeyEvent { + keycode: i32, + event_time: i64, +} + +#[derive(Debug)] +struct BlockedEvent { + device_id: i32, + keycode: i32, +} + +pub struct BounceKeysFilter { + next: Box<dyn Filter + Send + Sync>, + key_event_map: HashMap<i32, LastUpKeyEvent>, + blocked_events: Vec<BlockedEvent>, + external_devices: HashSet<i32>, + bounce_key_threshold_ns: i64, +} + +impl BounceKeysFilter { + /// Create a new BounceKeysFilter instance. + pub fn new( + next: Box<dyn Filter + Send + Sync>, + bounce_key_threshold_ns: i64, + ) -> BounceKeysFilter { + Self { + next, + key_event_map: HashMap::new(), + blocked_events: Vec::new(), + external_devices: HashSet::new(), + bounce_key_threshold_ns, + } + } +} + +impl Filter for BounceKeysFilter { + fn notify_key(&mut self, event: &KeyEvent) { + if !(self.external_devices.contains(&event.deviceId) && event.source == Source::KEYBOARD) { + self.next.notify_key(event); + return; + } + match event.action { + KeyEventAction::DOWN => match self.key_event_map.get(&event.deviceId) { + None => self.next.notify_key(event), + Some(last_up_event) => { + if event.keyCode == last_up_event.keycode + && event.eventTime < last_up_event.event_time + self.bounce_key_threshold_ns + { + self.blocked_events.push(BlockedEvent { + device_id: event.deviceId, + keycode: event.keyCode, + }); + debug!("Event dropped because last up was too recent"); + } else { + self.key_event_map.remove(&event.deviceId); + self.next.notify_key(event); + } + } + }, + KeyEventAction::UP => { + self.key_event_map.insert( + event.deviceId, + LastUpKeyEvent { keycode: event.keyCode, event_time: event.eventTime }, + ); + if let Some(index) = self.blocked_events.iter().position(|blocked_event| { + blocked_event.device_id == event.deviceId + && blocked_event.keycode == event.keyCode + }) { + self.blocked_events.remove(index); + debug!("Event dropped because key down was already dropped"); + } else { + self.next.notify_key(event); + } + } + _ => (), + } + } + + fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) { + self.key_event_map.retain(|id, _| device_infos.iter().any(|x| *id == x.deviceId)); + self.blocked_events.retain(|blocked_event| { + device_infos.iter().any(|x| blocked_event.device_id == x.deviceId) + }); + self.external_devices.clear(); + for device_info in device_infos { + if device_info.external { + self.external_devices.insert(device_info.deviceId); + } + } + self.next.notify_devices_changed(device_infos); + } +} + +#[cfg(test)] +mod tests { + use crate::bounce_keys_filter::BounceKeysFilter; + use crate::input_filter::{test_filter::TestFilter, Filter}; + use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source; + use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ + DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction, + }; + + static BASE_KEY_EVENT: KeyEvent = KeyEvent { + id: 1, + deviceId: 1, + downTime: 0, + readTime: 0, + eventTime: 0, + source: Source::KEYBOARD, + displayId: 0, + policyFlags: 0, + action: KeyEventAction::DOWN, + flags: 0, + keyCode: 1, + scanCode: 0, + metaState: 0, + }; + + #[test] + fn test_is_notify_key_for_external_keyboard() { + let mut next = TestFilter::new(); + let mut filter = setup_filter_with_external_device( + Box::new(next.clone()), + 1, /* device_id */ + 100, /* threshold */ + ); + + let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + + let event = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + + next.clear(); + let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert!(next.last_event().is_none()); + + let event = KeyEvent { eventTime: 100, action: KeyEventAction::UP, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert!(next.last_event().is_none()); + + let event = KeyEvent { eventTime: 200, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + } + + #[test] + fn test_is_notify_key_doesnt_block_for_internal_keyboard() { + let next = TestFilter::new(); + let mut filter = setup_filter_with_internal_device( + Box::new(next.clone()), + 1, /* device_id */ + 100, /* threshold */ + ); + + let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + + let event = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + + let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + } + + #[test] + fn test_is_notify_key_doesnt_block_for_external_stylus() { + let next = TestFilter::new(); + let mut filter = setup_filter_with_external_device( + Box::new(next.clone()), + 1, /* device_id */ + 100, /* threshold */ + ); + + let event = + KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + + let event = + KeyEvent { action: KeyEventAction::UP, source: Source::STYLUS, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + + let event = + KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + } + + #[test] + fn test_is_notify_key_for_multiple_external_keyboards() { + let mut next = TestFilter::new(); + let mut filter = setup_filter_with_devices( + Box::new(next.clone()), + &[ + DeviceInfo { deviceId: 1, external: true }, + DeviceInfo { deviceId: 2, external: true }, + ], + 100, /* threshold */ + ); + + let event = KeyEvent { deviceId: 1, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + + let event = KeyEvent { deviceId: 1, action: KeyEventAction::UP, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + + next.clear(); + let event = KeyEvent { deviceId: 1, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert!(next.last_event().is_none()); + + let event = KeyEvent { deviceId: 2, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }; + filter.notify_key(&event); + assert_eq!(next.last_event().unwrap(), event); + } + + fn setup_filter_with_external_device( + next: Box<dyn Filter + Send + Sync>, + device_id: i32, + threshold: i64, + ) -> BounceKeysFilter { + setup_filter_with_devices( + next, + &[DeviceInfo { deviceId: device_id, external: true }], + threshold, + ) + } + + fn setup_filter_with_internal_device( + next: Box<dyn Filter + Send + Sync>, + device_id: i32, + threshold: i64, + ) -> BounceKeysFilter { + setup_filter_with_devices( + next, + &[DeviceInfo { deviceId: device_id, external: false }], + threshold, + ) + } + + fn setup_filter_with_devices( + next: Box<dyn Filter + Send + Sync>, + devices: &[DeviceInfo], + threshold: i64, + ) -> BounceKeysFilter { + let mut filter = BounceKeysFilter::new(next, threshold); + filter.notify_devices_changed(devices); + filter + } +} diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs index 585187750b..340ff8e296 100644 --- a/services/inputflinger/rust/input_filter.rs +++ b/services/inputflinger/rust/input_filter.rs @@ -16,17 +16,39 @@ //! InputFilter manages all the filtering components that can intercept events, modify the events, //! block events, etc depending on the situation. This will be used support Accessibility features -//! like Slow keys, Bounce keys, etc. +//! like Sticky keys, Slow keys, Bounce keys, etc. use binder::{Interface, Strong}; use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ + DeviceInfo::DeviceInfo, IInputFilter::{IInputFilter, IInputFilterCallbacks::IInputFilterCallbacks}, + InputFilterConfiguration::InputFilterConfiguration, KeyEvent::KeyEvent, }; +use crate::bounce_keys_filter::BounceKeysFilter; +use log::{error, info}; +use std::sync::{Arc, Mutex, RwLock}; + +/// Interface for all the sub input filters +pub trait Filter { + fn notify_key(&mut self, event: &KeyEvent); + fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]); +} + +struct InputFilterState { + first_filter: Box<dyn Filter + Send + Sync>, + enabled: bool, +} + /// The rust implementation of InputFilter pub struct InputFilter { - callbacks: Strong<dyn IInputFilterCallbacks>, + // In order to have multiple immutable references to the callbacks that is thread safe need to + // wrap the callbacks in Arc<RwLock<...>> + callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>, + // Access to mutable references to mutable state (includes access to filters, enabled, etc.) is + // guarded by Mutex for thread safety + state: Mutex<InputFilterState>, } impl Interface for InputFilter {} @@ -34,35 +56,87 @@ impl Interface for InputFilter {} impl InputFilter { /// Create a new InputFilter instance. pub fn new(callbacks: Strong<dyn IInputFilterCallbacks>) -> InputFilter { - Self { callbacks } + let ref_callbacks = Arc::new(RwLock::new(callbacks)); + let base_filter = Box::new(BaseFilter::new(ref_callbacks.clone())); + Self::create_input_filter(base_filter, ref_callbacks) + } + + /// Create test instance of InputFilter + fn create_input_filter( + first_filter: Box<dyn Filter + Send + Sync>, + callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>, + ) -> InputFilter { + Self { callbacks, state: Mutex::new(InputFilterState { first_filter, enabled: false }) } } } impl IInputFilter for InputFilter { fn isEnabled(&self) -> binder::Result<bool> { - // TODO(b/294546335): Return true if any filters are to be applied, false otherwise - Result::Ok(false) + Result::Ok(self.state.lock().unwrap().enabled) } + fn notifyKey(&self, event: &KeyEvent) -> binder::Result<()> { - // TODO(b/294546335): Handle key event and modify key events here - // Just send back the event without processing for now. - let _ = self.callbacks.sendKeyEvent(event); + let first_filter = &mut self.state.lock().unwrap().first_filter; + first_filter.notify_key(event); + Result::Ok(()) + } + + fn notifyInputDevicesChanged(&self, device_infos: &[DeviceInfo]) -> binder::Result<()> { + let first_filter = &mut self.state.lock().unwrap().first_filter; + first_filter.notify_devices_changed(device_infos); Result::Ok(()) } - fn notifyInputDevicesChanged(&self, _device_ids: &[i32]) -> binder::Result<()> { - // TODO(b/294546335): Update data based on device changes here + + fn notifyConfigurationChanged(&self, config: &InputFilterConfiguration) -> binder::Result<()> { + 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.bounceKeysThresholdNs > 0 { + first_filter = + Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs)); + state.enabled = true; + info!("Bounce keys filter is installed"); + } + state.first_filter = first_filter; Result::Ok(()) } } +struct BaseFilter { + callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>, +} + +impl BaseFilter { + fn new(callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>) -> BaseFilter { + Self { callbacks } + } +} + +impl Filter for BaseFilter { + fn notify_key(&mut self, event: &KeyEvent) { + match self.callbacks.read().unwrap().sendKeyEvent(event) { + Ok(_) => (), + _ => error!("Failed to send key event back to native C++"), + } + } + + fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) { + // do nothing + } +} + #[cfg(test)] mod tests { - use crate::input_filter::InputFilter; + use crate::input_filter::{test_filter::TestFilter, Filter, InputFilter}; + use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source; use binder::{Interface, Strong}; use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ - IInputFilter::IInputFilter, IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks, - KeyEvent::KeyEvent, + DeviceInfo::DeviceInfo, IInputFilter::IInputFilter, + IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks, + InputFilterConfiguration::InputFilterConfiguration, KeyEvent::KeyEvent, + KeyEventAction::KeyEventAction, }; + use std::sync::{Arc, RwLock}; struct FakeCallbacks {} @@ -75,31 +149,60 @@ mod tests { } #[test] - fn test_is_enabled() { + fn test_not_enabled_with_default_filter() { let fake_callbacks: Strong<dyn IInputFilterCallbacks> = Strong::new(Box::new(FakeCallbacks {})); - let filter: Box<dyn IInputFilter> = Box::new(InputFilter::new(fake_callbacks)); - let result = filter.isEnabled(); + let input_filter = InputFilter::new(fake_callbacks); + let result = input_filter.isEnabled(); assert!(result.is_ok()); assert!(!result.unwrap()); } #[test] - fn test_notify_key() { + fn test_notify_key_with_no_filters() { let fake_callbacks: Strong<dyn IInputFilterCallbacks> = Strong::new(Box::new(FakeCallbacks {})); - let filter: Box<dyn IInputFilter> = Box::new(InputFilter::new(fake_callbacks)); + let input_filter = InputFilter::new(fake_callbacks); let event = create_key_event(); - assert!(filter.notifyKey(&event).is_ok()); + assert!(input_filter.notifyKey(&event).is_ok()); + } + + #[test] + fn test_notify_key_with_filter() { + let test_filter = TestFilter::new(); + let input_filter = create_input_filter(Box::new(test_filter.clone())); + let event = create_key_event(); + assert!(input_filter.notifyKey(&event).is_ok()); + assert_eq!(test_filter.last_event().unwrap(), event); } #[test] fn test_notify_devices_changed() { + let test_filter = TestFilter::new(); + let input_filter = create_input_filter(Box::new(test_filter.clone())); + assert!(input_filter + .notifyInputDevicesChanged(&[DeviceInfo { deviceId: 0, external: true }]) + .is_ok()); + assert!(test_filter.is_device_changed_called()); + } + + #[test] + fn test_notify_configuration_changed_enabled_bounce_keys() { let fake_callbacks: Strong<dyn IInputFilterCallbacks> = Strong::new(Box::new(FakeCallbacks {})); - let filter: Box<dyn IInputFilter> = Box::new(InputFilter::new(fake_callbacks)); - let result = filter.notifyInputDevicesChanged(&[0]); + let input_filter = InputFilter::new(fake_callbacks); + let result = input_filter + .notifyConfigurationChanged(&InputFilterConfiguration { bounceKeysThresholdNs: 100 }); 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))) } fn create_key_event() -> KeyEvent { @@ -109,10 +212,10 @@ mod tests { downTime: 0, readTime: 0, eventTime: 0, - source: 0, + source: Source::KEYBOARD, displayId: 0, policyFlags: 0, - action: 0, + action: KeyEventAction::DOWN, flags: 0, keyCode: 0, scanCode: 0, @@ -120,3 +223,52 @@ mod tests { } } } + +#[cfg(test)] +pub mod test_filter { + use crate::input_filter::Filter; + use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ + DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, + }; + use std::sync::{Arc, RwLock, RwLockWriteGuard}; + + #[derive(Default)] + struct TestFilterInner { + is_device_changed_called: bool, + last_event: Option<KeyEvent>, + } + + #[derive(Default, Clone)] + pub struct TestFilter(Arc<RwLock<TestFilterInner>>); + + impl TestFilter { + pub fn new() -> Self { + Default::default() + } + + fn inner(&mut self) -> RwLockWriteGuard<'_, TestFilterInner> { + 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 + } + + pub fn is_device_changed_called(&self) -> bool { + self.0.read().unwrap().is_device_changed_called + } + } + + impl Filter for TestFilter { + fn notify_key(&mut self, event: &KeyEvent) { + self.inner().last_event = Some(*event); + } + fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) { + self.inner().is_device_changed_called = true; + } + } +} diff --git a/services/inputflinger/rust/lib.rs b/services/inputflinger/rust/lib.rs index a4049d52d1..68cd4800fe 100644 --- a/services/inputflinger/rust/lib.rs +++ b/services/inputflinger/rust/lib.rs @@ -19,6 +19,7 @@ //! We use cxxbridge to create IInputFlingerRust - the Rust component of inputflinger - and //! pass it back to C++ as a local AIDL interface. +mod bounce_keys_filter; mod input_filter; use crate::input_filter::InputFilter; |