Merge "Ensure input events are processed in-order in the application."

This commit is contained in:
Jeff Brown
2011-12-01 20:11:15 -08:00
committed by Android (Google) Code Review
8 changed files with 348 additions and 468 deletions

View File

@@ -18,7 +18,6 @@ package android.service.wallpaper;
import com.android.internal.os.HandlerCaller;
import com.android.internal.view.BaseIWindow;
import com.android.internal.view.BaseInputHandler;
import com.android.internal.view.BaseSurfaceHolder;
import android.annotation.SdkConstant;
@@ -45,6 +44,7 @@ import android.view.Gravity;
import android.view.IWindowSession;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputHandler;
import android.view.InputQueue;
import android.view.MotionEvent;
@@ -229,15 +229,15 @@ public abstract class WallpaperService extends Service {
};
final InputHandler mInputHandler = new BaseInputHandler() {
final InputHandler mInputHandler = new InputHandler() {
@Override
public void handleMotion(MotionEvent event,
public void handleInputEvent(InputEvent event,
InputQueue.FinishedCallback finishedCallback) {
boolean handled = false;
try {
int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
dispatchPointer(event);
if (event instanceof MotionEvent
&& (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
dispatchPointer((MotionEvent)event);
handled = true;
}
} finally {

View File

@@ -20,24 +20,16 @@ package android.view;
* Handles input messages that arrive on an input channel.
* @hide
*/
public interface InputHandler {
public class InputHandler {
/**
* Handle a key event.
* Handle an input event.
* It is the responsibility of the callee to ensure that the finished callback is
* eventually invoked when the event processing is finished and the input system
* can send the next event.
* @param event The key event data.
* @param event The input event.
* @param finishedCallback The callback to invoke when event processing is finished.
*/
public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback);
/**
* Handle a motion event.
* It is the responsibility of the callee to ensure that the finished callback is
* eventually invoked when the event processing is finished and the input system
* can send the next event.
* @param event The motion event data.
* @param finishedCallback The callback to invoke when event processing is finished.
*/
public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback);
public void handleInputEvent(InputEvent event, InputQueue.FinishedCallback finishedCallback) {
finishedCallback.finished(false);
}
}

View File

@@ -114,19 +114,12 @@ public final class InputQueue {
}
@SuppressWarnings("unused")
private static void dispatchKeyEvent(InputHandler inputHandler,
KeyEvent event, long finishedToken) {
private static void dispatchInputEvent(InputHandler inputHandler,
InputEvent event, long finishedToken) {
FinishedCallback finishedCallback = FinishedCallback.obtain(finishedToken);
inputHandler.handleKey(event, finishedCallback);
inputHandler.handleInputEvent(event, finishedCallback);
}
@SuppressWarnings("unused")
private static void dispatchMotionEvent(InputHandler inputHandler,
MotionEvent event, long finishedToken) {
FinishedCallback finishedCallback = FinishedCallback.obtain(finishedToken);
inputHandler.handleMotion(event, finishedCallback);
}
/**
* A callback that must be invoked to when finished processing an event.
* @hide

View File

@@ -154,9 +154,6 @@ public final class ViewRootImpl extends Handler implements ViewParent,
final TypedValue mTmpValue = new TypedValue();
final InputMethodCallback mInputMethodCallback;
final SparseArray<Object> mPendingEvents = new SparseArray<Object>();
int mPendingEventSeq = 0;
final Thread mThread;
final WindowLeaked mLocation;
@@ -219,7 +216,17 @@ public final class ViewRootImpl extends Handler implements ViewParent,
boolean mNewSurfaceNeeded;
boolean mHasHadWindowFocus;
boolean mLastWasImTarget;
InputEventMessage mPendingInputEvents = null;
// Pool of queued input events.
private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10;
private QueuedInputEvent mQueuedInputEventPool;
private int mQueuedInputEventPoolSize;
private int mQueuedInputEventNextSeq;
// Input event queue.
QueuedInputEvent mFirstPendingInputEvent;
QueuedInputEvent mCurrentInputEvent;
boolean mProcessInputEventsPending;
boolean mWindowAttributesChanged = false;
int mWindowAttributesChangesFlag = 0;
@@ -841,23 +848,11 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
private void processInputEvents(boolean outOfOrder) {
while (mPendingInputEvents != null) {
handleMessage(mPendingInputEvents.mMessage);
InputEventMessage tmpMessage = mPendingInputEvents;
mPendingInputEvents = mPendingInputEvents.mNext;
tmpMessage.recycle();
if (outOfOrder) {
removeMessages(PROCESS_INPUT_EVENTS);
}
}
}
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
processInputEvents(true);
processInputEvents();
if (DBG) {
System.out.println("======================================");
@@ -2366,7 +2361,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public final static int DISPATCH_TRACKBALL = 1007;
public final static int DISPATCH_APP_VISIBILITY = 1008;
public final static int DISPATCH_GET_NEW_SURFACE = 1009;
public final static int FINISHED_EVENT = 1010;
public final static int IME_FINISHED_EVENT = 1010;
public final static int DISPATCH_KEY_FROM_IME = 1011;
public final static int FINISH_INPUT_CONNECTION = 1012;
public final static int CHECK_FOCUS = 1013;
@@ -2380,7 +2375,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 1021;
public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 1022;
public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 1023;
public final static int PROCESS_INPUT_EVENTS = 1024;
public final static int DO_PROCESS_INPUT_EVENTS = 1024;
@Override
public String getMessageName(Message message) {
@@ -2405,8 +2400,8 @@ public final class ViewRootImpl extends Handler implements ViewParent,
return "DISPATCH_APP_VISIBILITY";
case DISPATCH_GET_NEW_SURFACE:
return "DISPATCH_GET_NEW_SURFACE";
case FINISHED_EVENT:
return "FINISHED_EVENT";
case IME_FINISHED_EVENT:
return "IME_FINISHED_EVENT";
case DISPATCH_KEY_FROM_IME:
return "DISPATCH_KEY_FROM_IME";
case FINISH_INPUT_CONNECTION:
@@ -2433,8 +2428,8 @@ public final class ViewRootImpl extends Handler implements ViewParent,
return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
case PROCESS_INPUT_EVENTS:
return "PROCESS_INPUT_EVENTS";
case DO_PROCESS_INPUT_EVENTS:
return "DO_PROCESS_INPUT_EVENTS";
}
return super.getMessageName(message);
}
@@ -2483,23 +2478,12 @@ public final class ViewRootImpl extends Handler implements ViewParent,
mProfile = false;
}
break;
case FINISHED_EVENT:
handleFinishedEvent(msg.arg1, msg.arg2 != 0);
case IME_FINISHED_EVENT:
handleImeFinishedEvent(msg.arg1, msg.arg2 != 0);
break;
case DISPATCH_KEY:
deliverKeyEvent((KeyEvent)msg.obj, msg.arg1 != 0);
break;
case DISPATCH_POINTER:
deliverPointerEvent((MotionEvent) msg.obj, msg.arg1 != 0);
break;
case DISPATCH_TRACKBALL:
deliverTrackballEvent((MotionEvent) msg.obj, msg.arg1 != 0);
break;
case DISPATCH_GENERIC_MOTION:
deliverGenericMotionEvent((MotionEvent) msg.obj, msg.arg1 != 0);
break;
case PROCESS_INPUT_EVENTS:
processInputEvents(false);
case DO_PROCESS_INPUT_EVENTS:
mProcessInputEventsPending = false;
processInputEvents();
break;
case DISPATCH_APP_VISIBILITY:
handleAppVisibility(msg.arg1 != 0);
@@ -2621,7 +2605,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
//noinspection UnusedAssignment
event = KeyEvent.changeFlags(event, event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
}
deliverKeyEventPostIme((KeyEvent)msg.obj, false);
enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME);
} break;
case FINISH_INPUT_CONNECTION: {
InputMethodManager imm = InputMethodManager.peekInstance();
@@ -2683,70 +2667,6 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
private void startInputEvent(InputQueue.FinishedCallback finishedCallback) {
if (mFinishedCallback != null) {
Slog.w(TAG, "Received a new input event from the input queue but there is "
+ "already an unfinished input event in progress.");
}
if (ViewDebug.DEBUG_LATENCY) {
mInputEventReceiveTimeNanos = System.nanoTime();
mInputEventDeliverTimeNanos = 0;
mInputEventDeliverPostImeTimeNanos = 0;
}
mFinishedCallback = finishedCallback;
}
private void finishInputEvent(InputEvent event, boolean handled) {
if (LOCAL_LOGV) Log.v(TAG, "Telling window manager input event is finished");
if (mFinishedCallback == null) {
Slog.w(TAG, "Attempted to tell the input queue that the current input event "
+ "is finished but there is no input event actually in progress.");
return;
}
if (ViewDebug.DEBUG_LATENCY) {
final long now = System.nanoTime();
final long eventTime = event.getEventTimeNano();
final StringBuilder msg = new StringBuilder();
msg.append("Latency: Spent ");
msg.append((now - mInputEventReceiveTimeNanos) * 0.000001f);
msg.append("ms processing ");
if (event instanceof KeyEvent) {
final KeyEvent keyEvent = (KeyEvent)event;
msg.append("key event, action=");
msg.append(KeyEvent.actionToString(keyEvent.getAction()));
} else {
final MotionEvent motionEvent = (MotionEvent)event;
msg.append("motion event, action=");
msg.append(MotionEvent.actionToString(motionEvent.getAction()));
msg.append(", historySize=");
msg.append(motionEvent.getHistorySize());
}
msg.append(", handled=");
msg.append(handled);
msg.append(", received at +");
msg.append((mInputEventReceiveTimeNanos - eventTime) * 0.000001f);
if (mInputEventDeliverTimeNanos != 0) {
msg.append("ms, delivered at +");
msg.append((mInputEventDeliverTimeNanos - eventTime) * 0.000001f);
}
if (mInputEventDeliverPostImeTimeNanos != 0) {
msg.append("ms, delivered post IME at +");
msg.append((mInputEventDeliverPostImeTimeNanos - eventTime) * 0.000001f);
}
msg.append("ms, finished at +");
msg.append((now - eventTime) * 0.000001f);
msg.append("ms.");
Log.d(TAG, msg.toString());
}
mFinishedCallback.finished(handled);
mFinishedCallback = null;
}
/**
* Something in the current window tells us we need to change the touch mode. For
* example, we are not in touch mode, and the user touches the screen.
@@ -2868,11 +2788,27 @@ public final class ViewRootImpl extends Handler implements ViewParent,
return false;
}
private void deliverPointerEvent(MotionEvent event, boolean sendDone) {
private void deliverInputEvent(QueuedInputEvent q) {
if (ViewDebug.DEBUG_LATENCY) {
mInputEventDeliverTimeNanos = System.nanoTime();
q.mDeliverTimeNanos = System.nanoTime();
}
if (q.mEvent instanceof KeyEvent) {
deliverKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
deliverPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
deliverTrackballEvent(q);
} else {
deliverGenericMotionEvent(q);
}
}
}
private void deliverPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
final boolean isTouchEvent = event.isTouchEvent();
if (mInputEventConsistencyVerifier != null) {
if (isTouchEvent) {
@@ -2884,7 +2820,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishMotionEvent(event, sendDone, false);
finishInputEvent(q, false);
return;
}
@@ -2919,41 +2855,23 @@ public final class ViewRootImpl extends Handler implements ViewParent,
lt.sample("B Dispatched PointerEvents ", System.nanoTime() - event.getEventTimeNano());
}
if (handled) {
finishMotionEvent(event, sendDone, true);
finishInputEvent(q, true);
return;
}
// Pointer event was unhandled.
finishMotionEvent(event, sendDone, false);
finishInputEvent(q, false);
}
private void finishMotionEvent(MotionEvent event, boolean sendDone, boolean handled) {
event.recycle();
if (sendDone) {
finishInputEvent(event, handled);
}
//noinspection ConstantConditions
if (LOCAL_LOGV || WATCH_POINTER) {
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
Log.i(TAG, "Done dispatching!");
}
}
}
private void deliverTrackballEvent(MotionEvent event, boolean sendDone) {
if (ViewDebug.DEBUG_LATENCY) {
mInputEventDeliverTimeNanos = System.nanoTime();
}
if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event);
private void deliverTrackballEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
}
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishMotionEvent(event, sendDone, false);
finishInputEvent(q, false);
return;
}
@@ -2965,7 +2883,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
// touch mode here.
ensureTouchMode(false);
finishMotionEvent(event, sendDone, true);
finishInputEvent(q, true);
mLastTrackballTime = Integer.MIN_VALUE;
return;
}
@@ -2989,18 +2907,18 @@ public final class ViewRootImpl extends Handler implements ViewParent,
case MotionEvent.ACTION_DOWN:
x.reset(2);
y.reset(2);
deliverKeyEvent(new KeyEvent(curTime, curTime,
dispatchKey(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD), false);
InputDevice.SOURCE_KEYBOARD));
break;
case MotionEvent.ACTION_UP:
x.reset(2);
y.reset(2);
deliverKeyEvent(new KeyEvent(curTime, curTime,
dispatchKey(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD), false);
InputDevice.SOURCE_KEYBOARD));
break;
}
@@ -3051,38 +2969,35 @@ public final class ViewRootImpl extends Handler implements ViewParent,
+ keycode);
movement--;
int repeatCount = accelMovement - movement;
deliverKeyEvent(new KeyEvent(curTime, curTime,
dispatchKey(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_MULTIPLE, keycode, repeatCount, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD), false);
InputDevice.SOURCE_KEYBOARD));
}
while (movement > 0) {
if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: "
+ keycode);
movement--;
curTime = SystemClock.uptimeMillis();
deliverKeyEvent(new KeyEvent(curTime, curTime,
dispatchKey(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_DOWN, keycode, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD), false);
deliverKeyEvent(new KeyEvent(curTime, curTime,
InputDevice.SOURCE_KEYBOARD));
dispatchKey(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_UP, keycode, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD), false);
}
InputDevice.SOURCE_KEYBOARD));
}
mLastTrackballTime = curTime;
}
// Unfortunately we can't tell whether the application consumed the keys, so
// we always consider the trackball event handled.
finishMotionEvent(event, sendDone, true);
finishInputEvent(q, true);
}
private void deliverGenericMotionEvent(MotionEvent event, boolean sendDone) {
if (ViewDebug.DEBUG_LATENCY) {
mInputEventDeliverTimeNanos = System.nanoTime();
}
private void deliverGenericMotionEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
}
@@ -3095,7 +3010,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
if (isJoystick) {
updateJoystickDirection(event, false);
}
finishMotionEvent(event, sendDone, false);
finishInputEvent(q, false);
return;
}
@@ -3104,16 +3019,16 @@ public final class ViewRootImpl extends Handler implements ViewParent,
if (isJoystick) {
updateJoystickDirection(event, false);
}
finishMotionEvent(event, sendDone, true);
finishInputEvent(q, true);
return;
}
if (isJoystick) {
// Translate the joystick event into DPAD keys and try to deliver those.
updateJoystickDirection(event, true);
finishMotionEvent(event, sendDone, true);
finishInputEvent(q, true);
} else {
finishMotionEvent(event, sendDone, false);
finishInputEvent(q, false);
}
}
@@ -3135,9 +3050,9 @@ public final class ViewRootImpl extends Handler implements ViewParent,
if (xDirection != mLastJoystickXDirection) {
if (mLastJoystickXKeyCode != 0) {
deliverKeyEvent(new KeyEvent(time, time,
dispatchKey(new KeyEvent(time, time,
KeyEvent.ACTION_UP, mLastJoystickXKeyCode, 0, metaState,
deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false);
deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
mLastJoystickXKeyCode = 0;
}
@@ -3146,17 +3061,17 @@ public final class ViewRootImpl extends Handler implements ViewParent,
if (xDirection != 0 && synthesizeNewKeys) {
mLastJoystickXKeyCode = xDirection > 0
? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
deliverKeyEvent(new KeyEvent(time, time,
dispatchKey(new KeyEvent(time, time,
KeyEvent.ACTION_DOWN, mLastJoystickXKeyCode, 0, metaState,
deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false);
deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
}
}
if (yDirection != mLastJoystickYDirection) {
if (mLastJoystickYKeyCode != 0) {
deliverKeyEvent(new KeyEvent(time, time,
dispatchKey(new KeyEvent(time, time,
KeyEvent.ACTION_UP, mLastJoystickYKeyCode, 0, metaState,
deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false);
deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
mLastJoystickYKeyCode = 0;
}
@@ -3165,9 +3080,9 @@ public final class ViewRootImpl extends Handler implements ViewParent,
if (yDirection != 0 && synthesizeNewKeys) {
mLastJoystickYKeyCode = yDirection > 0
? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
deliverKeyEvent(new KeyEvent(time, time,
dispatchKey(new KeyEvent(time, time,
KeyEvent.ACTION_DOWN, mLastJoystickYKeyCode, 0, metaState,
deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false);
deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
}
}
}
@@ -3258,91 +3173,80 @@ public final class ViewRootImpl extends Handler implements ViewParent,
return false;
}
int enqueuePendingEvent(Object event, boolean sendDone) {
int seq = mPendingEventSeq+1;
if (seq < 0) seq = 0;
mPendingEventSeq = seq;
mPendingEvents.put(seq, event);
return sendDone ? seq : -seq;
}
Object retrievePendingEvent(int seq) {
if (seq < 0) seq = -seq;
Object event = mPendingEvents.get(seq);
if (event != null) {
mPendingEvents.remove(seq);
}
return event;
}
private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
if (ViewDebug.DEBUG_LATENCY) {
mInputEventDeliverTimeNanos = System.nanoTime();
}
private void deliverKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishKeyEvent(event, sendDone, false);
return;
}
if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView);
// Perform predispatching before the IME.
if (mView.dispatchKeyEventPreIme(event)) {
finishKeyEvent(event, sendDone, true);
return;
}
// Dispatch to the IME before propagating down the view hierarchy.
// The IME will eventually call back into handleFinishedEvent.
if (mLastWasImTarget) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
int seq = enqueuePendingEvent(event, sendDone);
if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="
+ seq + " event=" + event);
imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);
if ((q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishInputEvent(q, false);
return;
}
if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView);
// Perform predispatching before the IME.
if (mView.dispatchKeyEventPreIme(event)) {
finishInputEvent(q, true);
return;
}
// Dispatch to the IME before propagating down the view hierarchy.
// The IME will eventually call back into handleImeFinishedEvent.
if (mLastWasImTarget) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="
+ q.mSeq + " event=" + event);
imm.dispatchKeyEvent(mView.getContext(), q.mSeq, event, mInputMethodCallback);
return;
}
}
}
// Not dispatching to IME, continue with post IME actions.
deliverKeyEventPostIme(event, sendDone);
deliverKeyEventPostIme(q);
}
private void handleFinishedEvent(int seq, boolean handled) {
final KeyEvent event = (KeyEvent)retrievePendingEvent(seq);
if (DEBUG_IMF) Log.v(TAG, "IME finished event: seq=" + seq
+ " handled=" + handled + " event=" + event);
if (event != null) {
final boolean sendDone = seq >= 0;
void handleImeFinishedEvent(int seq, boolean handled) {
final QueuedInputEvent q = mCurrentInputEvent;
if (q != null && q.mSeq == seq) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (DEBUG_IMF) {
Log.v(TAG, "IME finished event: seq=" + seq
+ " handled=" + handled + " event=" + event);
}
if (handled) {
finishKeyEvent(event, sendDone, true);
finishInputEvent(q, true);
} else {
deliverKeyEventPostIme(event, sendDone);
deliverKeyEventPostIme(q);
}
} else {
if (DEBUG_IMF) {
Log.v(TAG, "IME finished event: seq=" + seq
+ " handled=" + handled + ", event not found!");
}
}
}
private void deliverKeyEventPostIme(KeyEvent event, boolean sendDone) {
private void deliverKeyEventPostIme(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (ViewDebug.DEBUG_LATENCY) {
mInputEventDeliverPostImeTimeNanos = System.nanoTime();
q.mDeliverPostImeTimeNanos = System.nanoTime();
}
// If the view went away, then the event will not be handled.
if (mView == null || !mAdded) {
finishKeyEvent(event, sendDone, false);
finishInputEvent(q, false);
return;
}
// If the key's purpose is to exit touch mode then we consume it and consider it handled.
if (checkForLeavingTouchModeAndConsume(event)) {
finishKeyEvent(event, sendDone, true);
finishInputEvent(q, true);
return;
}
@@ -3352,7 +3256,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
finishKeyEvent(event, sendDone, true);
finishInputEvent(q, true);
return;
}
@@ -3361,14 +3265,14 @@ public final class ViewRootImpl extends Handler implements ViewParent,
&& event.isCtrlPressed()
&& !KeyEvent.isModifierKey(event.getKeyCode())) {
if (mView.dispatchKeyShortcutEvent(event)) {
finishKeyEvent(event, sendDone, true);
finishInputEvent(q, true);
return;
}
}
// Apply the fallback event policy.
if (mFallbackEventHandler.dispatchKeyEvent(event)) {
finishKeyEvent(event, sendDone, true);
finishInputEvent(q, true);
return;
}
@@ -3423,14 +3327,14 @@ public final class ViewRootImpl extends Handler implements ViewParent,
if (v.requestFocus(direction, mTempRect)) {
playSoundEffect(
SoundEffectConstants.getContantForFocusDirection(direction));
finishKeyEvent(event, sendDone, true);
finishInputEvent(q, true);
return;
}
}
// Give the focused view a last chance to handle the dpad key.
if (mView.dispatchUnhandledMove(focused, direction)) {
finishKeyEvent(event, sendDone, true);
finishInputEvent(q, true);
return;
}
}
@@ -3438,13 +3342,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
// Key was unhandled.
finishKeyEvent(event, sendDone, false);
}
private void finishKeyEvent(KeyEvent event, boolean sendDone, boolean handled) {
if (sendDone) {
finishInputEvent(event, handled);
}
finishInputEvent(q, false);
}
/* drag/drop */
@@ -3769,8 +3667,8 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
public void dispatchFinishedEvent(int seq, boolean handled) {
Message msg = obtainMessage(FINISHED_EVENT);
void dispatchImeFinishedEvent(int seq, boolean handled) {
Message msg = obtainMessage(IME_FINISHED_EVENT);
msg.arg1 = seq;
msg.arg2 = handled ? 1 : 0;
sendMessage(msg);
@@ -3799,152 +3697,185 @@ public final class ViewRootImpl extends Handler implements ViewParent,
sendMessage(msg);
}
private long mInputEventReceiveTimeNanos;
private long mInputEventDeliverTimeNanos;
private long mInputEventDeliverPostImeTimeNanos;
private InputQueue.FinishedCallback mFinishedCallback;
private final InputHandler mInputHandler = new InputHandler() {
public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback) {
startInputEvent(finishedCallback);
dispatchKey(event, true);
/**
* Represents a pending input event that is waiting in a queue.
*
* Input events are processed in serial order by the timestamp specified by
* {@link InputEvent#getEventTime()}. In general, the input dispatcher delivers
* one input event to the application at a time and waits for the application
* to finish handling it before delivering the next one.
*
* However, because the application or IME can synthesize and inject multiple
* key events at a time without going through the input dispatcher, we end up
* needing a queue on the application's side.
*/
private static final class QueuedInputEvent {
public static final int FLAG_DELIVER_POST_IME = 1 << 0;
public QueuedInputEvent mNext;
public InputEvent mEvent;
public InputQueue.FinishedCallback mFinishedCallback;
public int mFlags;
public int mSeq;
// Used for latency calculations.
public long mReceiveTimeNanos;
public long mDeliverTimeNanos;
public long mDeliverPostImeTimeNanos;
}
private QueuedInputEvent obtainQueuedInputEvent(InputEvent event,
InputQueue.FinishedCallback finishedCallback, int flags) {
QueuedInputEvent q = mQueuedInputEventPool;
if (q != null) {
mQueuedInputEventPoolSize -= 1;
mQueuedInputEventPool = q.mNext;
q.mNext = null;
} else {
q = new QueuedInputEvent();
}
public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
startInputEvent(finishedCallback);
dispatchMotion(event, true);
q.mEvent = event;
q.mFinishedCallback = finishedCallback;
q.mFlags = flags;
q.mSeq = mQueuedInputEventNextSeq++;
return q;
}
private void recycleQueuedInputEvent(QueuedInputEvent q) {
q.mEvent = null;
q.mFinishedCallback = null;
if (mQueuedInputEventPoolSize < MAX_QUEUED_INPUT_EVENT_POOL_SIZE) {
mQueuedInputEventPoolSize += 1;
q.mNext = mQueuedInputEventPool;
mQueuedInputEventPool = q;
}
}
void enqueueInputEvent(InputEvent event,
InputQueue.FinishedCallback finishedCallback, int flags) {
QueuedInputEvent q = obtainQueuedInputEvent(event, finishedCallback, flags);
if (ViewDebug.DEBUG_LATENCY) {
q.mReceiveTimeNanos = System.nanoTime();
q.mDeliverTimeNanos = 0;
q.mDeliverPostImeTimeNanos = 0;
}
// Always enqueue the input event in order, regardless of its time stamp.
// We do this because the application or the IME may inject key events
// in response to touch events and we want to ensure that the injected keys
// are processed in the order they were received and we cannot trust that
// the time stamp of injected events are monotonic.
QueuedInputEvent last = mFirstPendingInputEvent;
if (last == null) {
mFirstPendingInputEvent = q;
} else {
while (last.mNext != null) {
last = last.mNext;
}
last.mNext = q;
}
scheduleProcessInputEvents();
}
private void scheduleProcessInputEvents() {
if (!mProcessInputEventsPending) {
mProcessInputEventsPending = true;
sendEmptyMessage(DO_PROCESS_INPUT_EVENTS);
}
}
void processInputEvents() {
while (mCurrentInputEvent == null && mFirstPendingInputEvent != null) {
QueuedInputEvent q = mFirstPendingInputEvent;
mFirstPendingInputEvent = q.mNext;
q.mNext = null;
mCurrentInputEvent = q;
deliverInputEvent(q);
}
// We are done processing all input events that we can process right now
// so we can clear the pending flag immediately.
if (mProcessInputEventsPending) {
mProcessInputEventsPending = false;
removeMessages(DO_PROCESS_INPUT_EVENTS);
}
}
private void finishInputEvent(QueuedInputEvent q, boolean handled) {
if (q != mCurrentInputEvent) {
throw new IllegalStateException("finished input event out of order");
}
if (ViewDebug.DEBUG_LATENCY) {
final long now = System.nanoTime();
final long eventTime = q.mEvent.getEventTimeNano();
final StringBuilder msg = new StringBuilder();
msg.append("Spent ");
msg.append((now - q.mReceiveTimeNanos) * 0.000001f);
msg.append("ms processing ");
if (q.mEvent instanceof KeyEvent) {
final KeyEvent keyEvent = (KeyEvent)q.mEvent;
msg.append("key event, action=");
msg.append(KeyEvent.actionToString(keyEvent.getAction()));
} else {
final MotionEvent motionEvent = (MotionEvent)q.mEvent;
msg.append("motion event, action=");
msg.append(MotionEvent.actionToString(motionEvent.getAction()));
msg.append(", historySize=");
msg.append(motionEvent.getHistorySize());
}
msg.append(", handled=");
msg.append(handled);
msg.append(", received at +");
msg.append((q.mReceiveTimeNanos - eventTime) * 0.000001f);
if (q.mDeliverTimeNanos != 0) {
msg.append("ms, delivered at +");
msg.append((q.mDeliverTimeNanos - eventTime) * 0.000001f);
}
if (q.mDeliverPostImeTimeNanos != 0) {
msg.append("ms, delivered post IME at +");
msg.append((q.mDeliverPostImeTimeNanos - eventTime) * 0.000001f);
}
msg.append("ms, finished at +");
msg.append((now - eventTime) * 0.000001f);
msg.append("ms.");
Log.d(ViewDebug.DEBUG_LATENCY_TAG, msg.toString());
}
if (q.mFinishedCallback != null) {
q.mFinishedCallback.finished(handled);
}
if (q.mEvent instanceof MotionEvent) {
// Event though key events are also recyclable, we only recycle motion events.
// Historically, key events were not recyclable and applications expect
// them to be immutable. We only ever recycle key events behind the
// scenes where an application never sees them (so, not here).
q.mEvent.recycle();
}
recycleQueuedInputEvent(q);
mCurrentInputEvent = null;
if (mFirstPendingInputEvent != null) {
scheduleProcessInputEvents();
}
}
private final InputHandler mInputHandler = new InputHandler() {
public void handleInputEvent(InputEvent event,
InputQueue.FinishedCallback finishedCallback) {
enqueueInputEvent(event, finishedCallback, 0);
}
};
/**
* Utility class used to queue up input events which are then handled during
* performTraversals(). Doing it this way allows us to ensure that we are up to date with
* all input events just prior to drawing, instead of placing those events on the regular
* handler queue, potentially behind a drawing event.
*/
static class InputEventMessage {
Message mMessage;
InputEventMessage mNext;
private static final Object sPoolSync = new Object();
private static InputEventMessage sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 10;
private InputEventMessage(Message m) {
mMessage = m;
mNext = null;
}
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static InputEventMessage obtain(Message msg) {
synchronized (sPoolSync) {
if (sPool != null) {
InputEventMessage m = sPool;
sPool = m.mNext;
m.mNext = null;
sPoolSize--;
m.mMessage = msg;
return m;
}
}
return new InputEventMessage(msg);
}
/**
* Return the message to the pool.
*/
public void recycle() {
mMessage.recycle();
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
mNext = sPool;
sPool = this;
sPoolSize++;
}
}
}
}
/**
* Place the input event message at the end of the current pending list
*/
private void enqueueInputEvent(Message msg, long when) {
InputEventMessage inputMessage = InputEventMessage.obtain(msg);
if (mPendingInputEvents == null) {
mPendingInputEvents = inputMessage;
} else {
InputEventMessage currMessage = mPendingInputEvents;
while (currMessage.mNext != null) {
currMessage = currMessage.mNext;
}
currMessage.mNext = inputMessage;
}
sendEmptyMessageAtTime(PROCESS_INPUT_EVENTS, when);
}
public void dispatchKey(KeyEvent event) {
dispatchKey(event, false);
}
private void dispatchKey(KeyEvent event, boolean sendDone) {
//noinspection ConstantConditions
if (false && event.getAction() == KeyEvent.ACTION_DOWN) {
if (event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) {
if (DBG) Log.d("keydisp", "===================================================");
if (DBG) Log.d("keydisp", "Focused view Hierarchy is:");
debug();
if (DBG) Log.d("keydisp", "===================================================");
}
}
Message msg = obtainMessage(DISPATCH_KEY);
msg.obj = event;
msg.arg1 = sendDone ? 1 : 0;
if (LOCAL_LOGV) Log.v(
TAG, "sending key " + event + " to " + mView);
enqueueInputEvent(msg, event.getEventTime());
}
private void dispatchMotion(MotionEvent event, boolean sendDone) {
int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
dispatchPointer(event, sendDone);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
dispatchTrackball(event, sendDone);
} else {
dispatchGenericMotion(event, sendDone);
}
}
private void dispatchPointer(MotionEvent event, boolean sendDone) {
Message msg = obtainMessage(DISPATCH_POINTER);
msg.obj = event;
msg.arg1 = sendDone ? 1 : 0;
enqueueInputEvent(msg, event.getEventTime());
}
private void dispatchTrackball(MotionEvent event, boolean sendDone) {
Message msg = obtainMessage(DISPATCH_TRACKBALL);
msg.obj = event;
msg.arg1 = sendDone ? 1 : 0;
enqueueInputEvent(msg, event.getEventTime());
}
private void dispatchGenericMotion(MotionEvent event, boolean sendDone) {
Message msg = obtainMessage(DISPATCH_GENERIC_MOTION);
msg.obj = event;
msg.arg1 = sendDone ? 1 : 0;
enqueueInputEvent(msg, event.getEventTime());
enqueueInputEvent(event, null, 0);
}
public void dispatchAppVisibility(boolean visible) {
@@ -4126,7 +4057,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public void finishedEvent(int seq, boolean handled) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchFinishedEvent(seq, handled);
viewAncestor.dispatchImeFinishedEvent(seq, handled);
}
}