Add bounce keys input filter
DD: go/pk_accessibility
Bug: 294546335
Test: TEST=libinputflinger_rs_test; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST
Change-Id: Id38b3d0fc830d16327b36d1b4376c0a66b6551aa
diff --git a/services/inputflinger/InputFilter.cpp b/services/inputflinger/InputFilter.cpp
index 1b8fad3..9c4a3eb 100644
--- a/services/inputflinger/InputFilter.cpp
+++ b/services/inputflinger/InputFilter.cpp
@@ -22,16 +22,19 @@
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 @@
}
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 @@
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 @@
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 699f3a0..06f7d0e 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 @@
* 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 @@
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 @@
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 8cf61f9..02997f0 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -224,6 +224,10 @@
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 aea7bd5..fa7db37 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -102,6 +102,9 @@
/* 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 @@
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 314c433..d068129 100644
--- a/services/inputflinger/aidl/Android.bp
+++ b/services/inputflinger/aidl/Android.bp
@@ -17,6 +17,9 @@
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 0000000..b9e6a03
--- /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 44f959e..14b41cd 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 @@
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 0000000..3b2e88b
--- /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 e213221..2cae6e1 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 0000000..43ee5fe
--- /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 2775bcc..2803805 100644
--- a/services/inputflinger/rust/Android.bp
+++ b/services/inputflinger/rust/Android.bp
@@ -38,6 +38,7 @@
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 0000000..894b881
--- /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 5851877..340ff8e 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 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_ids: &[i32]) -> binder::Result<()> {
- // TODO(b/294546335): Update data based on device changes here
+
+ 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 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 @@
}
#[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 @@
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 @@
}
}
}
+
+#[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 a4049d5..68cd480 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;