/*
* 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 android.view;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.view.IInputFilter;
import android.view.InputEvent;
import android.view.InputEventConsistencyVerifier;
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 queuing 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 an 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 may 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 cancellation 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 get 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 synthetic key events
* by setting the {@link WindowManagerPolicy#FLAG_DISABLE_KEY_REPEAT} policy flag.
* </p>
*
* @hide
*/
public abstract class InputFilter extends IInputFilter.Stub {
private static final int MSG_INSTALL = 1;
private static final int MSG_UNINSTALL = 2;
private static final int MSG_INPUT_EVENT = 3;
// Consistency verifiers for debugging purposes.
private final InputEventConsistencyVerifier mInboundInputEventConsistencyVerifier =
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new InputEventConsistencyVerifier(this,
InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT,
"InputFilter#InboundInputEventConsistencyVerifier") : null;
private final InputEventConsistencyVerifier mOutboundInputEventConsistencyVerifier =
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new InputEventConsistencyVerifier(this,
InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT,
"InputFilter#OutboundInputEventConsistencyVerifier") : null;
private final H mH;
private IInputFilterHost 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.
*/
public final void install(IInputFilterHost host) {
mH.obtainMessage(MSG_INSTALL, host).sendToTarget();
}
/**
* Called when the input filter is uninstalled.
* This method is guaranteed to be non-reentrant.
*/
public 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 public 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.");
}
if (mOutboundInputEventConsistencyVerifier != null) {
mOutboundInputEventConsistencyVerifier.onInputEvent(event, 0);
}
try {
mHost.sendInputEvent(event, policyFlags);
} catch (RemoteException re) {
/* ignore */
}
}
/**
* 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 = (IInputFilterHost) msg.obj;
if (mInboundInputEventConsistencyVerifier != null) {
mInboundInputEventConsistencyVerifier.reset();
}
if (mOutboundInputEventConsistencyVerifier != null) {
mOutboundInputEventConsistencyVerifier.reset();
}
onInstalled();
break;
case MSG_UNINSTALL:
try {
onUninstalled();
} finally {
mHost = null;
}
break;
case MSG_INPUT_EVENT: {
final InputEvent event = (InputEvent)msg.obj;
try {
if (mInboundInputEventConsistencyVerifier != null) {
mInboundInputEventConsistencyVerifier.onInputEvent(event, 0);
}
onInputEvent(event, msg.arg1);
} finally {
event.recycle();
}
break;
}
}
}
}
}