diff options
Diffstat (limited to 'services/java/com/android')
5 files changed, 428 insertions, 6 deletions
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); } |