diff options
author | Jeff Brown <jeffbrown@google.com> | 2011-03-30 02:25:18 -0700 |
---|---|---|
committer | Jeff Brown <jeffbrown@google.com> | 2011-03-30 16:55:15 -0700 |
commit | 0029c66203ab9ded4342976bf7a17bb63af8c44a (patch) | |
tree | a0d5f54ff6f88dae18179d1621dd2c87f1fa8d27 | |
parent | 6e6cd7a5660af1a4b5a9ad091c41ef1c72ad2000 (diff) | |
download | frameworks_base-0029c66203ab9ded4342976bf7a17bb63af8c44a.zip frameworks_base-0029c66203ab9ded4342976bf7a17bb63af8c44a.tar.gz frameworks_base-0029c66203ab9ded4342976bf7a17bb63af8c44a.tar.bz2 |
Add input filter mechanism for accessibility.
This patch adds a mechanism for capturing, filtering, transforming
and injecting input events at a very low level before the input
dispatcher attempts to deliver them to applications. At this time,
the mechanism is only intended to be used by the accessibility
system to implement built-in system-level accessibility affordances.
The accessibility input filter is currently just a stub.
It logs the input events receives and reinjects them unchanged,
except that it transforms KEYCODE_Q into KEYCODE_Z.
Currently, the accessibility input filter is installed whenever
accessibility is enabled. We'll probably want to change that
so it only enables the input filter when a screen reader is
installed and we want touch exploration.
Change-Id: I35764fdf75522b69d09ebd78c9766eb7593c1afe
-rwxr-xr-x | core/java/android/view/InputEvent.java | 9 | ||||
-rw-r--r-- | core/java/android/view/WindowManagerPolicy.java | 2 | ||||
-rw-r--r-- | include/ui/Input.h | 6 | ||||
-rw-r--r-- | services/input/InputDispatcher.cpp | 81 | ||||
-rw-r--r-- | services/input/InputDispatcher.h | 23 | ||||
-rw-r--r-- | services/input/tests/InputDispatcher_test.cpp | 28 | ||||
-rw-r--r-- | services/input/tests/InputReader_test.cpp | 7 | ||||
-rw-r--r-- | services/java/com/android/server/accessibility/AccessibilityInputFilter.java | 86 | ||||
-rw-r--r-- | services/java/com/android/server/accessibility/AccessibilityManagerService.java | 24 | ||||
-rw-r--r-- | services/java/com/android/server/wm/InputFilter.java | 231 | ||||
-rw-r--r-- | services/java/com/android/server/wm/InputManager.java | 89 | ||||
-rw-r--r-- | services/java/com/android/server/wm/WindowManagerService.java | 4 | ||||
-rw-r--r-- | services/jni/com_android_server_InputManager.cpp | 58 |
13 files changed, 611 insertions, 37 deletions
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java index f6aeb39..03189ca 100755 --- a/core/java/android/view/InputEvent.java +++ b/core/java/android/view/InputEvent.java @@ -67,6 +67,15 @@ public abstract class InputEvent implements Parcelable { */ public abstract void setSource(int source); + /** + * Recycles the event. + * This method should only be used by the system since applications do not + * expect {@link KeyEvent} objects to be recycled, although {@link MotionEvent} + * objects are fine. See {@link KeyEvent#recycle()} for details. + * @hide + */ + public abstract void recycle(); + public int describeContents() { return 0; } diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 334c68e..9d00d02 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -81,6 +81,8 @@ public interface WindowManagerPolicy { public final static int FLAG_INJECTED = 0x01000000; public final static int FLAG_TRUSTED = 0x02000000; + public final static int FLAG_FILTERED = 0x04000000; + public final static int FLAG_DISABLE_KEY_REPEAT = 0x08000000; public final static int FLAG_WOKE_HERE = 0x10000000; public final static int FLAG_BRIGHT_HERE = 0x20000000; diff --git a/include/ui/Input.h b/include/ui/Input.h index 8e8b61b..b22986d 100644 --- a/include/ui/Input.h +++ b/include/ui/Input.h @@ -128,6 +128,12 @@ enum { // input device or an application with system-wide event injection permission. POLICY_FLAG_TRUSTED = 0x02000000, + // Indicates that the input event has passed through an input filter. + POLICY_FLAG_FILTERED = 0x04000000, + + // Disables automatic key repeating behavior. + POLICY_FLAG_DISABLE_KEY_REPEAT = 0x08000000, + /* These flags are set by the input reader policy as it intercepts each event. */ // Indicates that the screen was off when the event was received and the event diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp index 6ea068a..8363e8b 100644 --- a/services/input/InputDispatcher.cpp +++ b/services/input/InputDispatcher.cpp @@ -186,7 +186,7 @@ InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& polic mPolicy(policy), mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX), mNextUnblockedEvent(NULL), - mDispatchEnabled(true), mDispatchFrozen(false), + mDispatchEnabled(true), mDispatchFrozen(false), mInputFilterEnabled(false), mFocusedWindow(NULL), mFocusedApplication(NULL), mCurrentInputTargetsValid(false), @@ -725,7 +725,7 @@ bool InputDispatcher::dispatchKeyLocked( if (entry->repeatCount == 0 && entry->action == AKEY_EVENT_ACTION_DOWN && (entry->policyFlags & POLICY_FLAG_TRUSTED) - && !entry->isInjected()) { + && (!(entry->policyFlags & POLICY_FLAG_DISABLE_KEY_REPEAT))) { if (mKeyRepeatState.lastKeyEntry && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) { // We have seen two identical key downs in a row which indicates that the device @@ -2402,7 +2402,18 @@ void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t so bool needWake; { // acquire lock - AutoMutex _l(mLock); + mLock.lock(); + + if (mInputFilterEnabled) { + mLock.unlock(); + + policyFlags |= POLICY_FLAG_FILTERED; + if (!mPolicy->filterInputEvent(&event, policyFlags)) { + return; // event was consumed by the filter + } + + mLock.lock(); + } int32_t repeatCount = 0; KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime, @@ -2410,6 +2421,7 @@ void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t so metaState, repeatCount, downTime); needWake = enqueueInboundEventLocked(newEntry); + mLock.unlock(); } // release lock if (needWake) { @@ -2452,7 +2464,23 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t bool needWake; { // acquire lock - AutoMutex _l(mLock); + mLock.lock(); + + if (mInputFilterEnabled) { + mLock.unlock(); + + MotionEvent event; + event.initialize(deviceId, source, action, flags, edgeFlags, metaState, 0, 0, + xPrecision, yPrecision, downTime, eventTime, + pointerCount, pointerIds, pointerCoords); + + policyFlags |= POLICY_FLAG_FILTERED; + if (!mPolicy->filterInputEvent(&event, policyFlags)) { + return; // event was consumed by the filter + } + + mLock.lock(); + } // Attempt batching and streaming of move events. if (action == AMOTION_EVENT_ACTION_MOVE @@ -2491,6 +2519,7 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t LOGD("Appended motion sample onto batch for most recent " "motion event for this device in the inbound queue."); #endif + mLock.unlock(); return; // done! } @@ -2579,6 +2608,7 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t true /*resumeWithAppendedMotionSample*/); runCommandsLockedInterruptible(); + mLock.unlock(); return; // done! } } @@ -2593,6 +2623,7 @@ NoBatchingOrStreaming:; pointerCount, pointerIds, pointerCoords); needWake = enqueueInboundEventLocked(newEntry); + mLock.unlock(); } // release lock if (needWake) { @@ -2612,16 +2643,17 @@ void InputDispatcher::notifySwitch(nsecs_t when, int32_t switchCode, int32_t swi } int32_t InputDispatcher::injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) { + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags) { #if DEBUG_INBOUND_EVENT_DETAILS LOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, " - "syncMode=%d, timeoutMillis=%d", - event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis); + "syncMode=%d, timeoutMillis=%d, policyFlags=0x%08x", + event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis, policyFlags); #endif nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis); - uint32_t policyFlags = POLICY_FLAG_INJECTED; + policyFlags |= POLICY_FLAG_INJECTED; if (hasInjectionPermission(injectorPid, injectorUid)) { policyFlags |= POLICY_FLAG_TRUSTED; } @@ -2640,7 +2672,9 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, policyFlags |= POLICY_FLAG_VIRTUAL; } - mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags); + if (!(policyFlags & POLICY_FLAG_FILTERED)) { + mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags); + } if (policyFlags & POLICY_FLAG_WOKE_HERE) { flags |= AKEY_EVENT_FLAG_WOKE_HERE; @@ -2664,8 +2698,10 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, return INPUT_EVENT_INJECTION_FAILED; } - nsecs_t eventTime = motionEvent->getEventTime(); - mPolicy->interceptMotionBeforeQueueing(eventTime, /*byref*/ policyFlags); + if (!(policyFlags & POLICY_FLAG_FILTERED)) { + nsecs_t eventTime = motionEvent->getEventTime(); + mPolicy->interceptMotionBeforeQueueing(eventTime, /*byref*/ policyFlags); + } mLock.lock(); const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes(); @@ -2780,7 +2816,8 @@ void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t inject injectionResult, injectionState->injectorPid, injectionState->injectorUid); #endif - if (injectionState->injectionIsAsync) { + if (injectionState->injectionIsAsync + && !(entry->policyFlags & POLICY_FLAG_FILTERED)) { // Log the outcome since the injector did not wait for the injection result. switch (injectionResult) { case INPUT_EVENT_INJECTION_SUCCEEDED: @@ -2982,6 +3019,26 @@ void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) { } } +void InputDispatcher::setInputFilterEnabled(bool enabled) { +#if DEBUG_FOCUS + LOGD("setInputFilterEnabled: enabled=%d", enabled); +#endif + + { // acquire lock + AutoMutex _l(mLock); + + if (mInputFilterEnabled == enabled) { + return; + } + + mInputFilterEnabled = enabled; + resetAndDropEverythingLocked("input filter is being enabled or disabled"); + } // release lock + + // Wake up poll loop since there might be work to do to drop everything. + mLooper->wake(); +} + bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel, const sp<InputChannel>& toChannel) { #if DEBUG_FOCUS diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h index 48e4d43..162e606 100644 --- a/services/input/InputDispatcher.h +++ b/services/input/InputDispatcher.h @@ -176,6 +176,13 @@ public: */ virtual int32_t getMaxEventsPerSecond() = 0; + /* Filters an input event. + * Return true to dispatch the event unmodified, false to consume the event. + * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED + * to injectInputEvent. + */ + virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) = 0; + /* Intercepts a key event immediately before queueing it. * The policy can use this method as an opportunity to perform power management functions * and early event preprocessing such as updating policy flags. @@ -266,7 +273,8 @@ public: * This method may be called on any thread (usually by the input manager). */ virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0; + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags) = 0; /* Sets the list of input windows. * @@ -286,6 +294,14 @@ public: */ virtual void setInputDispatchMode(bool enabled, bool frozen) = 0; + /* Sets whether input event filtering is enabled. + * When enabled, incoming input events are sent to the policy's filterInputEvent + * method instead of being dispatched. The filter is expected to use + * injectInputEvent to inject the events it would like to have dispatched. + * It should include POLICY_FLAG_FILTERED in the policy flags during injection. + */ + virtual void setInputFilterEnabled(bool enabled) = 0; + /* Transfers touch focus from the window associated with one channel to the * window associated with the other channel. * @@ -345,11 +361,13 @@ public: int32_t switchCode, int32_t switchValue, uint32_t policyFlags) ; virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis); + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags); virtual void setInputWindows(const Vector<InputWindow>& inputWindows); virtual void setFocusedApplication(const InputApplication* inputApplication); virtual void setInputDispatchMode(bool enabled, bool frozen); + virtual void setInputFilterEnabled(bool enabled); virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, const sp<InputChannel>& toChannel); @@ -863,6 +881,7 @@ private: // Dispatch state. bool mDispatchEnabled; bool mDispatchFrozen; + bool mInputFilterEnabled; Vector<InputWindow> mWindows; diff --git a/services/input/tests/InputDispatcher_test.cpp b/services/input/tests/InputDispatcher_test.cpp index 2f846c4..3650da0 100644 --- a/services/input/tests/InputDispatcher_test.cpp +++ b/services/input/tests/InputDispatcher_test.cpp @@ -67,6 +67,10 @@ private: return 60; } + virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) { + return true; + } + virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) { } @@ -124,7 +128,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { /*action*/ -1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject key events with undefined action."; // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API. @@ -132,7 +136,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { AKEY_EVENT_ACTION_MULTIPLE, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject key events with ACTION_MULTIPLE."; } @@ -150,7 +154,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with undefined action."; // Rejects pointer down with invalid index. @@ -160,7 +164,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer down index too large."; event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, @@ -169,7 +173,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer down index too small."; // Rejects pointer up with invalid index. @@ -179,7 +183,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer up index too large."; event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, @@ -188,7 +192,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer up index too small."; // Rejects motion events with invalid number of pointers. @@ -197,7 +201,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 0, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with 0 pointers."; event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, @@ -205,7 +209,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ MAX_POINTERS + 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with more than MAX_POINTERS pointers."; // Rejects motion events with invalid pointer ids. @@ -215,7 +219,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer ids less than 0."; pointerIds[0] = MAX_POINTER_ID + 1; @@ -224,7 +228,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer ids greater than MAX_POINTER_ID."; // Rejects motion events with duplicate pointer ids. @@ -235,7 +239,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 2, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with duplicate pointer ids."; } diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp index 60549c6..4c5f239 100644 --- a/services/input/tests/InputReader_test.cpp +++ b/services/input/tests/InputReader_test.cpp @@ -369,7 +369,8 @@ private: } virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) { + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags) { ADD_FAILURE() << "Should never be called by input reader."; return INPUT_EVENT_INJECTION_FAILED; } @@ -386,6 +387,10 @@ private: ADD_FAILURE() << "Should never be called by input reader."; } + virtual void setInputFilterEnabled(bool enabled) { + ADD_FAILURE() << "Should never be called by input reader."; + } + virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, const sp<InputChannel>& toChannel) { ADD_FAILURE() << "Should never be called by input reader."; diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java new file mode 100644 index 0000000..ced8feb --- /dev/null +++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2011 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.accessibility; + +import com.android.server.wm.InputFilter; + +import android.content.Context; +import android.util.Slog; +import android.view.InputEvent; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy; + +/** + * Input filter for accessibility. + * + * Currently just a stub but will eventually implement touch exploration, etc. + */ +public class AccessibilityInputFilter extends InputFilter { + private static final String TAG = "AccessibilityInputFilter"; + private static final boolean DEBUG = true; + + private final Context mContext; + + public AccessibilityInputFilter(Context context) { + super(context.getMainLooper()); + mContext = context; + } + + @Override + public void onInstalled() { + if (DEBUG) { + Slog.d(TAG, "Accessibility input filter installed."); + } + super.onInstalled(); + } + + @Override + public void onUninstalled() { + if (DEBUG) { + Slog.d(TAG, "Accessibility input filter uninstalled."); + } + super.onUninstalled(); + } + + @Override + public void onInputEvent(InputEvent event, int policyFlags) { + if (DEBUG) { + Slog.d(TAG, "Accessibility input filter received input event: " + + event + ", policyFlags=0x" + Integer.toHexString(policyFlags)); + } + + // To prove that this is working as intended, we will silently transform + // Q key presses into non-repeating Z's as part of this stub implementation. + // TODO: Replace with the real thing. + if (event instanceof KeyEvent) { + final KeyEvent keyEvent = (KeyEvent)event; + if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_Q) { + if (keyEvent.getRepeatCount() == 0) { + sendInputEvent(new KeyEvent(keyEvent.getDownTime(), keyEvent.getEventTime(), + keyEvent.getAction(), KeyEvent.KEYCODE_Z, keyEvent.getRepeatCount(), + keyEvent.getMetaState(), keyEvent.getDeviceId(), keyEvent.getScanCode(), + keyEvent.getFlags(), keyEvent.getSource()), + policyFlags | WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT); + } + return; + } + } + + super.onInputEvent(event, policyFlags); + } +} diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index ba74d86..5257fb0 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -19,6 +19,7 @@ package com.android.server.accessibility; import com.android.internal.content.PackageMonitor; import com.android.internal.os.HandlerCaller; import com.android.internal.os.HandlerCaller.SomeArgs; +import com.android.server.wm.WindowManagerService; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; @@ -43,6 +44,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; +import android.os.ServiceManager; import android.provider.Settings; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; @@ -106,6 +108,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private int mHandledFeedbackTypes = 0; private boolean mIsEnabled; + private AccessibilityInputFilter mInputFilter; /** * Handler for delayed event dispatch. @@ -209,6 +212,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } manageServicesLocked(); + updateInputFilterLocked(); } return; @@ -249,6 +253,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub unbindAllServicesLocked(); } updateClientsLocked(); + updateInputFilterLocked(); } } }); @@ -621,6 +626,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } /** + * Installs or removes the accessibility input filter when accessibility is enabled + * or disabled. + */ + private void updateInputFilterLocked() { + WindowManagerService wm = (WindowManagerService)ServiceManager.getService( + Context.WINDOW_SERVICE); + if (wm != null) { + if (mIsEnabled) { + if (mInputFilter == null) { + mInputFilter = new AccessibilityInputFilter(mContext); + } + wm.setInputFilter(mInputFilter); + } else { + wm.setInputFilter(null); + } + } + } + + /** * This class represents an accessibility service. It stores all per service * data required for the service management, provides API for starting/stopping the * service and is responsible for adding/removing the service in the data structures diff --git a/services/java/com/android/server/wm/InputFilter.java b/services/java/com/android/server/wm/InputFilter.java new file mode 100644 index 0000000..78b87fe --- /dev/null +++ b/services/java/com/android/server/wm/InputFilter.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2011 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.wm; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.InputEvent; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy; + +/** + * Filters input events before they are dispatched to the system. + * <p> + * At most one input filter can be installed by calling + * {@link WindowManagerService#setInputFilter}. When an input filter is installed, the + * system's behavior changes as follows: + * <ul> + * <li>Input events are first delivered to the {@link WindowManagerPolicy} + * interception methods before queueing as usual. This critical step takes care of managing + * the power state of the device and handling wake keys.</li> + * <li>Input events are then asynchronously delivered to the input filter's + * {@link #onInputEvent(InputEvent)} method instead of being enqueued for dispatch to + * applications as usual. The input filter only receives input events that were + * generated by input device; the input filter will not receive input events that were + * injected into the system by other means, such as by instrumentation.</li> + * <li>The input filter processes and optionally transforms the stream of events. For example, + * it may transform a sequence of motion events representing an accessibility gesture into + * a different sequence of motion events, key presses or other system-level interactions. + * The input filter can send events to be dispatched by calling + * {@link #sendInputEvent(InputEvent)} and passing appropriate policy flags for the + * input event.</li> + * </ul> + * </p> + * <h3>The importance of input event consistency</h3> + * <p> + * The input filter mechanism is very low-level. At a minimum, it needs to ensure that it + * sends an internally consistent stream of input events to the dispatcher. There are + * very important invariants to be maintained. + * </p><p> + * For example, if a key down is sent, a corresponding key up should also be sent eventually. + * Likewise, for touch events, each pointer must individually go down with + * {@link MotionEvent#ACTION_DOWN} or {@link MotionEvent#ACTION_POINTER_DOWN} and then + * individually go up with {@link MotionEvent#ACTION_POINTER_UP} or {@link MotionEvent#ACTION_UP} + * and the sequence of pointer ids used must be consistent throughout the gesture. + * </p><p> + * Sometimes a filter may wish to cancel a previously dispatched key or motion. It should + * use {@link KeyEvent#FLAG_CANCELED} or {@link MotionEvent#ACTION_CANCEL} accordingly. + * </p><p> + * The input filter must take into account the fact that the input events coming from different + * devices or even different sources all consist of distinct streams of input. + * Use {@link InputEvent#getDeviceId()} and {@link InputEvent#getSource()} to identify + * the source of the event and its semantics. There are be multiple sources of keys, + * touches and other input: they must be kept separate. + * </p> + * <h3>Policy flags</h3> + * <p> + * Input events received from the dispatcher and sent to the dispatcher have policy flags + * associated with them. Policy flags control some functions of the dispatcher. + * </p><p> + * The early policy interception decides whether an input event should be delivered + * to applications or dropped. The policy indicates its decision by setting the + * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} policy flag. The input filter may + * sometimes receive events that do not have this flag set. It should take note of + * the fact that the policy intends to drop the event, clean up its state, and + * then send appropriate cancelation events to the dispatcher if needed. + * </p><p> + * For example, suppose the input filter is processing a gesture and one of the touch events + * it receives does not have the {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag set. + * The input filter should clear its internal state about the gesture and then send key or + * motion events to the dispatcher to cancel any keys or pointers that are down. + * </p><p> + * Corollary: Events that set sent to the dispatcher should usually include the + * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped! + * </p><p> + * It may be prudent to disable automatic key repeating for synthetically generated + * keys by setting the {@link WindowManagerPolicy#FLAG_DISABLE_KEY_REPEAT} policy flag. + * </p> + */ +public abstract class InputFilter { + private static final int MSG_INSTALL = 1; + private static final int MSG_UNINSTALL = 2; + private static final int MSG_INPUT_EVENT = 3; + + private final H mH; + private Host mHost; + + /** + * Creates the input filter. + * + * @param looper The looper to run callbacks on. + */ + public InputFilter(Looper looper) { + mH = new H(looper); + } + + /** + * Called when the input filter is installed. + * This method is guaranteed to be non-reentrant. + * + * @param host The input filter host environment. + */ + final void install(Host host) { + mH.obtainMessage(MSG_INSTALL, host).sendToTarget(); + } + + /** + * Called when the input filter is uninstalled. + * This method is guaranteed to be non-reentrant. + */ + final void uninstall() { + mH.obtainMessage(MSG_UNINSTALL).sendToTarget(); + } + + /** + * Called to enqueue the input event for filtering. + * The event will be recycled after the input filter processes it. + * This method is guaranteed to be non-reentrant. + * + * @param event The input event to enqueue. + */ + final void filterInputEvent(InputEvent event, int policyFlags) { + mH.obtainMessage(MSG_INPUT_EVENT, policyFlags, 0, event).sendToTarget(); + } + + /** + * Sends an input event to the dispatcher. + * + * @param event The input event to publish. + * @param policyFlags The input event policy flags. + */ + public void sendInputEvent(InputEvent event, int policyFlags) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + if (mHost == null) { + throw new IllegalStateException("Cannot send input event because the input filter " + + "is not installed."); + } + mHost.sendInputEvent(event, policyFlags); + } + + /** + * Called when an input event has been received from the dispatcher. + * <p> + * The default implementation sends the input event back to the dispatcher, unchanged. + * </p><p> + * The event will be recycled when this method returns. If you want to keep it around, + * make a copy! + * </p> + * + * @param event The input event that was received. + * @param policyFlags The input event policy flags. + */ + public void onInputEvent(InputEvent event, int policyFlags) { + sendInputEvent(event, policyFlags); + } + + /** + * Called when the filter is installed into the dispatch pipeline. + * <p> + * This method is called before the input filter receives any input events. + * The input filter should take this opportunity to prepare itself. + * </p> + */ + public void onInstalled() { + } + + /** + * Called when the filter is uninstalled from the dispatch pipeline. + * <p> + * This method is called after the input filter receives its last input event. + * The input filter should take this opportunity to clean up. + * </p> + */ + public void onUninstalled() { + } + + private final class H extends Handler { + public H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_INSTALL: + mHost = (Host)msg.obj; + onInstalled(); + break; + + case MSG_UNINSTALL: + try { + onUninstalled(); + } finally { + mHost = null; + } + break; + + case MSG_INPUT_EVENT: { + final InputEvent event = (InputEvent)msg.obj; + try { + onInputEvent(event, msg.arg1); + } finally { + event.recycle(); + } + break; + } + } + } + } + + interface Host { + public void sendInputEvent(InputEvent event, int policyFlags); + } +} diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java index ca1da95..b0978a3 100644 --- a/services/java/com/android/server/wm/InputManager.java +++ b/services/java/com/android/server/wm/InputManager.java @@ -42,6 +42,7 @@ import android.view.KeyEvent; import android.view.Surface; import android.view.ViewConfiguration; import android.view.WindowManager; +import android.view.WindowManagerPolicy; import java.io.File; import java.io.FileNotFoundException; @@ -78,8 +79,10 @@ public class InputManager { private static native void nativeRegisterInputChannel(InputChannel inputChannel, InputWindowHandle inputWindowHandle, boolean monitor); private static native void nativeUnregisterInputChannel(InputChannel inputChannel); + private static native void nativeSetInputFilterEnabled(boolean enable); private static native int nativeInjectInputEvent(InputEvent event, - int injectorPid, int injectorUid, int syncMode, int timeoutMillis); + int injectorPid, int injectorUid, int syncMode, int timeoutMillis, + int policyFlags); private static native void nativeSetInputWindows(InputWindow[] windows); private static native void nativeSetInputDispatchMode(boolean enabled, boolean frozen); private static native void nativeSetSystemUiVisibility(int visibility); @@ -117,6 +120,11 @@ public class InputManager { /** The key is down but is a virtual key press that is being emulated by the system. */ public static final int KEY_STATE_VIRTUAL = 2; + // State for the currently installed input filter. + final Object mInputFilterLock = new Object(); + InputFilter mInputFilter; + InputFilterHost mInputFilterHost; + public InputManager(Context context, WindowManagerService windowManagerService) { this.mContext = context; this.mWindowManagerService = windowManagerService; @@ -268,7 +276,42 @@ public class InputManager { nativeUnregisterInputChannel(inputChannel); } - + + /** + * Sets an input filter that will receive all input events before they are dispatched. + * The input filter may then reinterpret input events or inject new ones. + * + * To ensure consistency, the input dispatcher automatically drops all events + * in progress whenever an input filter is installed or uninstalled. After an input + * filter is uninstalled, it can no longer send input events unless it is reinstalled. + * Any events it attempts to send after it has been uninstalled will be dropped. + * + * @param filter The input filter, or null to remove the current filter. + */ + public void setInputFilter(InputFilter filter) { + synchronized (mInputFilterLock) { + final InputFilter oldFilter = mInputFilter; + if (oldFilter == filter) { + return; // nothing to do + } + + if (oldFilter != null) { + mInputFilter = null; + mInputFilterHost.disconnectLocked(); + mInputFilterHost = null; + oldFilter.uninstall(); + } + + if (filter != null) { + mInputFilter = filter; + mInputFilterHost = new InputFilterHost(); + filter.install(mInputFilterHost); + } + + nativeSetInputFilterEnabled(filter != null); + } + } + /** * Injects an input event into the event system on behalf of an application. * The synchronization mode determines whether the method blocks while waiting for @@ -304,9 +347,10 @@ public class InputManager { throw new IllegalArgumentException("timeoutMillis must be positive"); } - return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis); + return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis, + WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT); } - + /** * Gets information about the input device with the specified id. * @param id The device id. @@ -370,6 +414,27 @@ public class InputManager { } } + private final class InputFilterHost implements InputFilter.Host { + private boolean mDisconnected; + + public void disconnectLocked() { + mDisconnected = true; + } + + public void sendInputEvent(InputEvent event, int policyFlags) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + + synchronized (mInputFilterLock) { + if (!mDisconnected) { + nativeInjectInputEvent(event, 0, 0, INPUT_EVENT_INJECTION_SYNC_NONE, 0, + policyFlags | WindowManagerPolicy.FLAG_FILTERED); + } + } + } + } + private static final class PointerIcon { public Bitmap bitmap; public float hotSpotX; @@ -415,7 +480,7 @@ public class InputManager { /* * Callbacks from native. */ - private class Callbacks { + private final class Callbacks { static final String TAG = "InputManager-Callbacks"; private static final boolean DEBUG_VIRTUAL_KEYS = false; @@ -443,7 +508,19 @@ public class InputManager { return mWindowManagerService.mInputMonitor.notifyANR( inputApplicationHandle, inputWindowHandle); } - + + @SuppressWarnings("unused") + final boolean filterInputEvent(InputEvent event, int policyFlags) { + synchronized (mInputFilterLock) { + if (mInputFilter != null) { + mInputFilter.filterInputEvent(event, policyFlags); + return false; + } + } + event.recycle(); + return true; + } + @SuppressWarnings("unused") public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) { return mWindowManagerService.mInputMonitor.interceptKeyBeforeQueueing( diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 33e6a36..79c4518 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -4604,6 +4604,10 @@ public class WindowManagerService extends IWindowManager.Stub return mInputManager.monitorInput(inputChannelName); } + public void setInputFilter(InputFilter filter) { + mInputManager.setInputFilter(filter); + } + public InputDevice getInputDevice(int deviceId) { return mInputManager.getInputDevice(deviceId); } diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp index 96cf4bd..ab2c125 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -56,6 +56,7 @@ static struct { jmethodID notifyLidSwitchChanged; jmethodID notifyInputChannelBroken; jmethodID notifyANR; + jmethodID filterInputEvent; jmethodID interceptKeyBeforeQueueing; jmethodID interceptMotionBeforeQueueingWhenScreenOff; jmethodID interceptKeyBeforeDispatching; @@ -174,6 +175,7 @@ public: virtual nsecs_t getKeyRepeatTimeout(); virtual nsecs_t getKeyRepeatDelay(); virtual int32_t getMaxEventsPerSecond(); + virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags); virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags); virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags); virtual bool interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle, @@ -638,6 +640,38 @@ bool NativeInputManager::isScreenBright() { return android_server_PowerManagerService_isScreenBright(); } +bool NativeInputManager::filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) { + jobject inputEventObj; + + JNIEnv* env = jniEnv(); + switch (inputEvent->getType()) { + case AINPUT_EVENT_TYPE_KEY: + inputEventObj = android_view_KeyEvent_fromNative(env, + static_cast<const KeyEvent*>(inputEvent)); + break; + case AINPUT_EVENT_TYPE_MOTION: + inputEventObj = android_view_MotionEvent_obtainAsCopy(env, + static_cast<const MotionEvent*>(inputEvent)); + break; + default: + return true; // dispatch the event normally + } + + if (!inputEventObj) { + LOGE("Failed to obtain input event object for filterInputEvent."); + return true; // dispatch the event normally + } + + // The callee is responsible for recycling the event. + jboolean pass = env->CallBooleanMethod(mCallbacksObj, gCallbacksClassInfo.filterInputEvent, + inputEventObj, policyFlags); + if (checkAndClearExceptionFromCallback(env, "filterInputEvent")) { + pass = true; + } + env->DeleteLocalRef(inputEventObj); + return pass; +} + void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) { // Policy: @@ -1005,9 +1039,18 @@ static void android_server_InputManager_nativeUnregisterInputChannel(JNIEnv* env } } +static void android_server_InputManager_nativeSetInputFilterEnabled(JNIEnv* env, jclass clazz, + jboolean enabled) { + if (checkInputManagerUnitialized(env)) { + return; + } + + gNativeInputManager->getInputManager()->getDispatcher()->setInputFilterEnabled(enabled); +} + static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jclass clazz, jobject inputEventObj, jint injectorPid, jint injectorUid, - jint syncMode, jint timeoutMillis) { + jint syncMode, jint timeoutMillis, jint policyFlags) { if (checkInputManagerUnitialized(env)) { return INPUT_EVENT_INJECTION_FAILED; } @@ -1021,7 +1064,8 @@ static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jcla } return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent( - & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis); + & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis, + uint32_t(policyFlags)); } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) { const MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, inputEventObj); if (!motionEvent) { @@ -1030,7 +1074,8 @@ static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jcla } return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent( - motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis); + motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis, + uint32_t(policyFlags)); } else { jniThrowRuntimeException(env, "Invalid input event type."); return INPUT_EVENT_INJECTION_FAILED; @@ -1200,7 +1245,9 @@ static JNINativeMethod gInputManagerMethods[] = { (void*) android_server_InputManager_nativeRegisterInputChannel }, { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V", (void*) android_server_InputManager_nativeUnregisterInputChannel }, - { "nativeInjectInputEvent", "(Landroid/view/InputEvent;IIII)I", + { "nativeSetInputFilterEnabled", "(Z)V", + (void*) android_server_InputManager_nativeSetInputFilterEnabled }, + { "nativeInjectInputEvent", "(Landroid/view/InputEvent;IIIII)I", (void*) android_server_InputManager_nativeInjectInputEvent }, { "nativeSetInputWindows", "([Lcom/android/server/wm/InputWindow;)V", (void*) android_server_InputManager_nativeSetInputWindows }, @@ -1257,6 +1304,9 @@ int register_android_server_InputManager(JNIEnv* env) { "notifyANR", "(Lcom/android/server/wm/InputApplicationHandle;Lcom/android/server/wm/InputWindowHandle;)J"); + GET_METHOD_ID(gCallbacksClassInfo.filterInputEvent, clazz, + "filterInputEvent", "(Landroid/view/InputEvent;I)Z"); + GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeQueueing, clazz, "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;IZ)I"); |