int mSeq;
View mView;
- View mFocusedView;
- View mRealFocusedView; // this is not set to null in touch mode
- View mOldFocusedView;
View mAccessibilityFocusedHost;
AccessibilityNodeInfo mAccessibilityFocusedVirtualView;
int mLastSystemUiVisibility;
int mClientWindowLayoutFlags;
+ /** @hide */
+ public static final int EVENT_NOT_HANDLED = 0;
+ /** @hide */
+ public static final int EVENT_HANDLED = 1;
+ /** @hide */
+ public static final int EVENT_IN_PROGRESS = 2;
+
// Pool of queued input events.
private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10;
private QueuedInputEvent mQueuedInputEventPool;
boolean mScrollMayChange;
int mSoftInputMode;
- View mLastScrolledFocus;
+ WeakReference<View> mLastScrolledFocus;
int mScrollY;
int mCurScrollY;
Scroller mScroller;
} else if (!mSurface.isValid()) {
// If the surface has been removed, then reset the scroll
// positions.
- mLastScrolledFocus = null;
+ if (mLastScrolledFocus != null) {
+ mLastScrolledFocus.clear();
+ }
mScrollY = mCurScrollY = 0;
if (mScroller != null) {
mScroller.abortAnimation();
if (mView != null) {
if (!mView.hasFocus()) {
mView.requestFocus(View.FOCUS_FORWARD);
- mFocusedView = mRealFocusedView = mView.findFocus();
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: requested focused view="
- + mFocusedView);
+ + mView.findFocus());
} else {
- mRealFocusedView = mView.findFocus();
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: existing focused view="
- + mRealFocusedView);
+ + mView.findFocus());
}
}
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0) {
// requestChildRectangleOnScreen() call (in which case 'rectangle'
// is non-null and we just want to scroll to whatever that
// rectangle is).
- View focus = mRealFocusedView;
-
- // When in touch mode, focus points to the previously focused view,
- // which may have been removed from the view hierarchy. The following
- // line checks whether the view is still in our hierarchy.
- if (focus == null || focus.mAttachInfo != mAttachInfo) {
- mRealFocusedView = null;
+ View focus = mView.findFocus();
+ if (focus == null) {
return false;
}
-
- if (focus != mLastScrolledFocus) {
+ View lastScrolledFocus = (mLastScrolledFocus != null) ? mLastScrolledFocus.get() : null;
+ if (lastScrolledFocus != null && focus != lastScrolledFocus) {
// If the focus has changed, then ignore any requests to scroll
// to a rectangle; first we want to make sure the entire focus
// view is visible.
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Eval scroll: focus=" + focus
+ " rectangle=" + rectangle + " ci=" + ci
+ " vi=" + vi);
- if (focus == mLastScrolledFocus && !mScrollMayChange
- && rectangle == null) {
+ if (focus == lastScrolledFocus && !mScrollMayChange && rectangle == null) {
// Optimization: if the focus hasn't changed since last
// time, and no layout has happened, then just leave things
// as they are.
// We need to determine if the currently focused view is
// within the visible part of the window and, if not, apply
// a pan so it can be seen.
- mLastScrolledFocus = focus;
+ mLastScrolledFocus = new WeakReference<View>(focus);
mScrollMayChange = false;
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Need to scroll?");
// Try to find the rectangle from the focus view.
}
public void requestChildFocus(View child, View focused) {
- checkThread();
-
if (DEBUG_INPUT_RESIZE) {
Log.v(TAG, "Request child focus: focus now " + focused);
}
-
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, focused);
+ checkThread();
scheduleTraversals();
-
- mFocusedView = mRealFocusedView = focused;
}
public void clearChildFocus(View child) {
- checkThread();
-
if (DEBUG_INPUT_RESIZE) {
Log.v(TAG, "Clearing child focus");
}
-
- mOldFocusedView = mFocusedView;
-
- // Invoke the listener only if there is no view to take focus
- if (focusSearch(null, View.FOCUS_FORWARD) == null) {
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, null);
- }
-
- mFocusedView = mRealFocusedView = null;
+ checkThread();
+ scheduleTraversals();
}
@Override
// the one case where will transfer focus away from the current one
// is if the current view is a view group that prefers to give focus
// to its children first AND the view is a descendant of it.
- mFocusedView = mView.findFocus();
- boolean descendantsHaveDibsOnFocus =
- (mFocusedView instanceof ViewGroup) &&
- (((ViewGroup) mFocusedView).getDescendantFocusability() ==
- ViewGroup.FOCUS_AFTER_DESCENDANTS);
- if (descendantsHaveDibsOnFocus && isViewDescendantOf(v, mFocusedView)) {
- // If a view gets the focus, the listener will be invoked from requestChildFocus()
- v.requestFocus();
+ View focused = mView.findFocus();
+ if (focused instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) focused;
+ if (group.getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS
+ && isViewDescendantOf(v, focused)) {
+ v.requestFocus();
+ }
}
}
}
// set yet.
final View focused = mView.findFocus();
if (focused != null && !focused.isFocusableInTouchMode()) {
-
final ViewGroup ancestorToTakeFocus =
findAncestorToTakeFocusInTouchMode(focused);
if (ancestorToTakeFocus != null) {
return ancestorToTakeFocus.requestFocus();
} else {
// nothing appropriate to have focus in touch mode, clear it out
- mView.unFocus();
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null);
- mFocusedView = null;
- mOldFocusedView = null;
+ focused.unFocus();
return true;
}
}
private boolean leaveTouchMode() {
if (mView != null) {
if (mView.hasFocus()) {
- // i learned the hard way to not trust mFocusedView :)
- mFocusedView = mView.findFocus();
- if (!(mFocusedView instanceof ViewGroup)) {
+ View focusedView = mView.findFocus();
+ if (!(focusedView instanceof ViewGroup)) {
// some view has focus, let it keep it
return false;
- } else if (((ViewGroup)mFocusedView).getDescendantFocusability() !=
+ } else if (((ViewGroup) focusedView).getDescendantFocusability() !=
ViewGroup.FOCUS_AFTER_DESCENDANTS) {
// some view group has focus, and doesn't prefer its children
// over itself for focus, so let them keep it.
return false;
}
- private void deliverInputEvent(QueuedInputEvent q) {
+ private int deliverInputEvent(QueuedInputEvent q) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent");
try {
if (q.mEvent instanceof KeyEvent) {
- deliverKeyEvent(q);
+ return deliverKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
- deliverPointerEvent(q);
+ return deliverPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
- deliverTrackballEvent(q);
+ return deliverTrackballEvent(q);
+ } else {
+ return deliverGenericMotionEvent(q);
+ }
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+
+ private int deliverInputEventPostIme(QueuedInputEvent q) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEventPostIme");
+ try {
+ if (q.mEvent instanceof KeyEvent) {
+ return deliverKeyEventPostIme(q);
+ } else {
+ final int source = q.mEvent.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ return deliverTrackballEventPostIme(q);
} else {
- deliverGenericMotionEvent(q);
+ return deliverGenericMotionEventPostIme(q);
}
}
} finally {
}
}
- private void deliverPointerEvent(QueuedInputEvent q) {
+ private int deliverPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
final boolean isTouchEvent = event.isTouchEvent();
if (mInputEventConsistencyVerifier != null) {
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
- finishInputEvent(q, false);
- return;
+ return EVENT_NOT_HANDLED;
}
// Translate the pointer event for compatibility, if needed.
if (MEASURE_LATENCY) {
lt.sample("B Dispatched PointerEvents ", System.nanoTime() - event.getEventTimeNano());
}
- if (handled) {
- finishInputEvent(q, true);
- return;
- }
-
- // Pointer event was unhandled.
- finishInputEvent(q, false);
+ return handled ? EVENT_HANDLED : EVENT_NOT_HANDLED;
}
- private void deliverTrackballEvent(QueuedInputEvent q) {
+ private int deliverTrackballEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
if (DEBUG_IMF)
Log.v(TAG, "Sending trackball event to IME: seq="
+ seq + " event=" + event);
- imm.dispatchTrackballEvent(mView.getContext(), seq, event,
+ int result = imm.dispatchTrackballEvent(mView.getContext(), seq, event,
mInputMethodCallback);
- return;
+ if (result != EVENT_NOT_HANDLED) {
+ return result;
+ }
}
}
}
// Not dispatching to IME, continue with post IME actions.
- deliverTrackballEventPostIme(q);
+ return deliverTrackballEventPostIme(q);
}
- private void deliverTrackballEventPostIme(QueuedInputEvent q) {
+ private int deliverTrackballEventPostIme(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent) q.mEvent;
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
- finishInputEvent(q, false);
- return;
+ return EVENT_NOT_HANDLED;
}
// Deliver the trackball event to the view.
// event into a key event, touch mode will not exit, so we exit
// touch mode here.
ensureTouchMode(false);
-
- finishInputEvent(q, true);
mLastTrackballTime = Integer.MIN_VALUE;
- return;
+ return EVENT_HANDLED;
}
// Translate the trackball event into DPAD keys and try to deliver those.
// Unfortunately we can't tell whether the application consumed the keys, so
// we always consider the trackball event handled.
- finishInputEvent(q, true);
+ return EVENT_HANDLED;
}
- private void deliverGenericMotionEvent(QueuedInputEvent q) {
+ private int deliverGenericMotionEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
if (DEBUG_IMF)
Log.v(TAG, "Sending generic motion event to IME: seq="
+ seq + " event=" + event);
- imm.dispatchGenericMotionEvent(mView.getContext(), seq, event,
+ int result = imm.dispatchGenericMotionEvent(mView.getContext(), seq, event,
mInputMethodCallback);
- return;
+ if (result != EVENT_NOT_HANDLED) {
+ return result;
+ }
}
}
}
// Not dispatching to IME, continue with post IME actions.
- deliverGenericMotionEventPostIme(q);
+ return deliverGenericMotionEventPostIme(q);
}
- private void deliverGenericMotionEventPostIme(QueuedInputEvent q) {
+ private int deliverGenericMotionEventPostIme(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent) q.mEvent;
final int source = event.getSource();
final boolean isJoystick = (source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0;
//Convert TouchPad motion into a TrackBall event
mSimulatedTrackball.updateTrackballDirection(this, event);
}
- finishInputEvent(q, false);
- return;
+ return EVENT_NOT_HANDLED;
}
// Deliver the event to the view.
//Convert TouchPad motion into a TrackBall event
mSimulatedTrackball.updateTrackballDirection(this, event);
}
- finishInputEvent(q, true);
- return;
+ return EVENT_HANDLED;
}
if (isJoystick) {
// Translate the joystick event into DPAD keys and try to deliver
// those.
updateJoystickDirection(event, true);
- finishInputEvent(q, true);
- } else if (isTouchPad) {
+ return EVENT_HANDLED;
+ }
+ if (isTouchPad) {
//Convert TouchPad motion into a TrackBall event
mSimulatedTrackball.updateTrackballDirection(this, event);
- finishInputEvent(q, true);
- } else {
- finishInputEvent(q, false);
+ return EVENT_HANDLED;
}
+ return EVENT_NOT_HANDLED;
}
private void updateJoystickDirection(MotionEvent event, boolean synthesizeNewKeys) {
return false;
}
- private void deliverKeyEvent(QueuedInputEvent q) {
+ private int deliverKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
// Perform predispatching before the IME.
if (mView.dispatchKeyEventPreIme(event)) {
- finishInputEvent(q, true);
- return;
+ return EVENT_HANDLED;
}
// Dispatch to the IME before propagating down the view hierarchy.
final int seq = event.getSequenceNumber();
if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="
+ seq + " event=" + event);
- imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);
- return;
+ int result = imm.dispatchKeyEvent(mView.getContext(), seq, event,
+ mInputMethodCallback);
+ if (result != EVENT_NOT_HANDLED) {
+ return result;
+ }
}
}
}
// Not dispatching to IME, continue with post IME actions.
- deliverKeyEventPostIme(q);
+ return deliverKeyEventPostIme(q);
}
- void handleImeFinishedEvent(int seq, boolean handled) {
- final QueuedInputEvent q = mCurrentInputEvent;
- if (q != null && q.mEvent.getSequenceNumber() == seq) {
- if (DEBUG_IMF) {
- Log.v(TAG, "IME finished event: seq=" + seq
- + " handled=" + handled + " event=" + q);
- }
- if (handled) {
- finishInputEvent(q, true);
- } else {
- if (q.mEvent instanceof KeyEvent) {
- KeyEvent event = (KeyEvent)q.mEvent;
- if (event.getAction() != KeyEvent.ACTION_UP) {
- // If the window doesn't currently have input focus, then drop
- // this event. This could be an event that came back from the
- // IME dispatch but the window has lost focus in the meantime.
- if (!mAttachInfo.mHasWindowFocus) {
- Slog.w(TAG, "Dropping event due to no window focus: " + event);
- finishInputEvent(q, true);
- return;
- }
- }
- deliverKeyEventPostIme(q);
- } else {
- MotionEvent event = (MotionEvent)q.mEvent;
- if (event.getAction() != MotionEvent.ACTION_CANCEL
- && event.getAction() != MotionEvent.ACTION_UP) {
- // If the window doesn't currently have input focus, then drop
- // this event. This could be an event that came back from the
- // IME dispatch but the window has lost focus in the meantime.
- if (!mAttachInfo.mHasWindowFocus) {
- Slog.w(TAG, "Dropping event due to no window focus: " + event);
- finishInputEvent(q, true);
- return;
- }
- }
- final int source = q.mEvent.getSource();
- if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
- deliverTrackballEventPostIme(q);
- } else {
- deliverGenericMotionEventPostIme(q);
- }
- }
- }
- } else {
- if (DEBUG_IMF) {
- Log.v(TAG, "IME finished event: seq=" + seq
- + " handled=" + handled + ", event not found!");
- }
- }
- }
-
- private void deliverKeyEventPostIme(QueuedInputEvent q) {
+ private int deliverKeyEventPostIme(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
// If the view went away, then the event will not be handled.
if (mView == null || !mAdded) {
- finishInputEvent(q, false);
- return;
+ return EVENT_NOT_HANDLED;
}
// If the key's purpose is to exit touch mode then we consume it and consider it handled.
if (checkForLeavingTouchModeAndConsume(event)) {
- finishInputEvent(q, true);
- return;
+ return EVENT_HANDLED;
}
// Make sure the fallback event policy sees all keys that will be delivered to the
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
- finishInputEvent(q, true);
- return;
+ return EVENT_HANDLED;
}
// If the Control modifier is held, try to interpret the key as a shortcut.
&& event.getRepeatCount() == 0
&& !KeyEvent.isModifierKey(event.getKeyCode())) {
if (mView.dispatchKeyShortcutEvent(event)) {
- finishInputEvent(q, true);
- return;
+ return EVENT_HANDLED;
}
}
// Apply the fallback event policy.
if (mFallbackEventHandler.dispatchKeyEvent(event)) {
- finishInputEvent(q, true);
- return;
+ return EVENT_HANDLED;
}
// Handle automatic focus changes.
if (v.requestFocus(direction, mTempRect)) {
playSoundEffect(SoundEffectConstants
.getContantForFocusDirection(direction));
- finishInputEvent(q, true);
- return;
+ return EVENT_HANDLED;
}
}
// Give the focused view a last chance to handle the dpad key.
if (mView.dispatchUnhandledMove(focused, direction)) {
- finishInputEvent(q, true);
- return;
+ return EVENT_HANDLED;
}
}
}
}
// Key was unhandled.
- finishInputEvent(q, false);
+ return EVENT_NOT_HANDLED;
}
/* drag/drop */
mFirstPendingInputEvent = q.mNext;
q.mNext = null;
mCurrentInputEvent = q;
- deliverInputEvent(q);
+
+ final int result = deliverInputEvent(q);
+ if (result != EVENT_IN_PROGRESS) {
+ finishCurrentInputEvent(result == EVENT_HANDLED);
+ }
}
// We are done processing all input events that we can process right now
}
}
- private void finishInputEvent(QueuedInputEvent q, boolean handled) {
- if (q != mCurrentInputEvent) {
- throw new IllegalStateException("finished input event out of order");
+ void handleImeFinishedEvent(int seq, boolean handled) {
+ final QueuedInputEvent q = mCurrentInputEvent;
+ if (q != null && q.mEvent.getSequenceNumber() == seq) {
+ if (DEBUG_IMF) {
+ Log.v(TAG, "IME finished event: seq=" + seq
+ + " handled=" + handled + " event=" + q);
+ }
+
+ if (!handled) {
+ // If the window doesn't currently have input focus, then drop
+ // this event. This could be an event that came back from the
+ // IME dispatch but the window has lost focus in the meantime.
+ if (!mAttachInfo.mHasWindowFocus && !isTerminalInputEvent(q.mEvent)) {
+ Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent);
+ } else {
+ final int result = deliverInputEventPostIme(q);
+ if (result == EVENT_HANDLED) {
+ handled = true;
+ }
+ }
+ }
+ finishCurrentInputEvent(handled);
+
+ // Immediately start processing the next input event.
+ doProcessInputEvents();
+ } else {
+ if (DEBUG_IMF) {
+ Log.v(TAG, "IME finished event: seq=" + seq
+ + " handled=" + handled + ", event not found!");
+ }
}
+ }
+
+ private void finishCurrentInputEvent(boolean handled) {
+ final QueuedInputEvent q = mCurrentInputEvent;
+ mCurrentInputEvent = null;
if (q.mReceiver != null) {
q.mReceiver.finishInputEvent(q.mEvent, handled);
}
recycleQueuedInputEvent(q);
+ }
- mCurrentInputEvent = null;
- if (mFirstPendingInputEvent != null) {
- scheduleProcessInputEvents();
+ private static boolean isTerminalInputEvent(InputEvent event) {
+ if (event instanceof KeyEvent) {
+ final KeyEvent keyEvent = (KeyEvent)event;
+ return keyEvent.getAction() == KeyEvent.ACTION_UP;
+ } else {
+ final MotionEvent motionEvent = (MotionEvent)event;
+ final int action = motionEvent.getAction();
+ return action == MotionEvent.ACTION_UP
+ || action == MotionEvent.ACTION_CANCEL
+ || action == MotionEvent.ACTION_HOVER_EXIT;
}
}