summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server')
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityInputFilter.java86
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityManagerService.java24
-rw-r--r--services/java/com/android/server/wm/InputFilter.java231
-rw-r--r--services/java/com/android/server/wm/InputManager.java89
-rw-r--r--services/java/com/android/server/wm/WindowManagerService.java4
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);
}