summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Brown <jeffbrown@google.com>2010-11-05 15:02:16 -0700
committerJeff Brown <jeffbrown@google.com>2010-11-08 12:49:43 -0800
commit3915bb845b032dc184dba5e60970b803390ca3ed (patch)
tree198a47c1d4ada990ef04d563b5e0caaec35abc18
parent60029771d26ca3c51288c3d92cab1d3537147acd (diff)
downloadframeworks_base-3915bb845b032dc184dba5e60970b803390ca3ed.zip
frameworks_base-3915bb845b032dc184dba5e60970b803390ca3ed.tar.gz
frameworks_base-3915bb845b032dc184dba5e60970b803390ca3ed.tar.bz2
Tell system server whether the app handled input events.
Refactored ViewRoot, NativeActivity and related classes to tell the dispatcher whether an input event was actually handled by the application. This will be used to move more of the global default key processing into the system server instead of the application. Change-Id: If06b98b6f45c543e5ac5b1eae2b3baf9371fba28
-rw-r--r--core/java/android/app/NativeActivity.java6
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java7
-rw-r--r--core/java/android/view/InputHandler.java4
-rw-r--r--core/java/android/view/InputQueue.java16
-rw-r--r--core/java/android/view/ViewRoot.java433
-rw-r--r--core/java/android/view/WindowManagerPolicy.java29
-rw-r--r--core/java/com/android/internal/view/BaseInputHandler.java9
-rw-r--r--core/jni/android_app_NativeActivity.cpp22
-rw-r--r--core/jni/android_view_InputQueue.cpp21
-rw-r--r--include/android_runtime/android_app_NativeActivity.h2
-rw-r--r--include/ui/InputDispatcher.h12
-rw-r--r--include/ui/InputTransport.h7
-rw-r--r--libs/ui/InputDispatcher.cpp59
-rw-r--r--libs/ui/InputTransport.cpp27
-rw-r--r--libs/ui/tests/InputDispatcher_test.cpp5
-rw-r--r--libs/ui/tests/InputPublisherAndConsumer_test.cpp14
-rw-r--r--native/android/input.cpp2
-rwxr-xr-xpolicy/src/com/android/internal/policy/impl/PhoneWindowManager.java31
-rw-r--r--services/java/com/android/server/InputManager.java8
-rw-r--r--services/java/com/android/server/WindowManagerService.java28
-rw-r--r--services/jni/com_android_server_InputManager.cpp29
21 files changed, 486 insertions, 285 deletions
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index de36f27..a5c49ec 100644
--- a/core/java/android/app/NativeActivity.java
+++ b/core/java/android/app/NativeActivity.java
@@ -344,12 +344,14 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
}
}
- void dispatchUnhandledKeyEvent(KeyEvent event) {
+ boolean dispatchUnhandledKeyEvent(KeyEvent event) {
try {
mDispatchingUnhandledKey = true;
View decor = getWindow().getDecorView();
if (decor != null) {
- decor.dispatchKeyEvent(event);
+ return decor.dispatchKeyEvent(event);
+ } else {
+ return false;
}
} finally {
mDispatchingUnhandledKey = false;
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 26346d2..755e39f 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -219,14 +219,17 @@ public abstract class WallpaperService extends Service {
final InputHandler mInputHandler = new BaseInputHandler() {
@Override
- public void handleMotion(MotionEvent event, Runnable finishedCallback) {
+ public void handleMotion(MotionEvent event,
+ InputQueue.FinishedCallback finishedCallback) {
+ boolean handled = false;
try {
int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
dispatchPointer(event);
+ handled = true;
}
} finally {
- finishedCallback.run();
+ finishedCallback.finished(handled);
}
}
};
diff --git a/core/java/android/view/InputHandler.java b/core/java/android/view/InputHandler.java
index 41a152d..14ce14c 100644
--- a/core/java/android/view/InputHandler.java
+++ b/core/java/android/view/InputHandler.java
@@ -29,7 +29,7 @@ public interface InputHandler {
* @param event The key event data.
* @param finishedCallback The callback to invoke when event processing is finished.
*/
- public void handleKey(KeyEvent event, Runnable finishedCallback);
+ public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback);
/**
* Handle a motion event.
@@ -39,5 +39,5 @@ public interface InputHandler {
* @param event The motion event data.
* @param finishedCallback The callback to invoke when event processing is finished.
*/
- public void handleMotion(MotionEvent event, Runnable finishedCallback);
+ public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback);
}
diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java
index 9e800df..5735b63 100644
--- a/core/java/android/view/InputQueue.java
+++ b/core/java/android/view/InputQueue.java
@@ -53,7 +53,7 @@ public final class InputQueue {
private static native void nativeRegisterInputChannel(InputChannel inputChannel,
InputHandler inputHandler, MessageQueue messageQueue);
private static native void nativeUnregisterInputChannel(InputChannel inputChannel);
- private static native void nativeFinished(long finishedToken);
+ private static native void nativeFinished(long finishedToken, boolean handled);
/** @hide */
public InputQueue(InputChannel channel) {
@@ -116,18 +116,22 @@ public final class InputQueue {
@SuppressWarnings("unused")
private static void dispatchKeyEvent(InputHandler inputHandler,
KeyEvent event, long finishedToken) {
- Runnable finishedCallback = FinishedCallback.obtain(finishedToken);
+ FinishedCallback finishedCallback = FinishedCallback.obtain(finishedToken);
inputHandler.handleKey(event, finishedCallback);
}
@SuppressWarnings("unused")
private static void dispatchMotionEvent(InputHandler inputHandler,
MotionEvent event, long finishedToken) {
- Runnable finishedCallback = FinishedCallback.obtain(finishedToken);
+ FinishedCallback finishedCallback = FinishedCallback.obtain(finishedToken);
inputHandler.handleMotion(event, finishedCallback);
}
- private static class FinishedCallback implements Runnable {
+ /**
+ * A callback that must be invoked to when finished processing an event.
+ * @hide
+ */
+ public static final class FinishedCallback {
private static final boolean DEBUG_RECYCLING = false;
private static final int RECYCLE_MAX_COUNT = 4;
@@ -156,13 +160,13 @@ public final class InputQueue {
}
}
- public void run() {
+ public void finished(boolean handled) {
synchronized (sLock) {
if (mFinishedToken == -1) {
throw new IllegalStateException("Event finished callback already invoked.");
}
- nativeFinished(mFinishedToken);
+ nativeFinished(mFinishedToken, handled);
mFinishedToken = -1;
if (sRecycleCount < RECYCLE_MAX_COUNT) {
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 5b18715..fa7fe80 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -53,6 +53,7 @@ import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.InputQueue.FinishedCallback;
import android.view.View.MeasureSpec;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -1742,34 +1743,14 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
handleFinishedEvent(msg.arg1, msg.arg2 != 0);
break;
case DISPATCH_KEY:
- if (LOCAL_LOGV) Log.v(
- TAG, "Dispatching key "
- + msg.obj + " to " + mView);
deliverKeyEvent((KeyEvent)msg.obj, msg.arg1 != 0);
break;
- case DISPATCH_POINTER: {
- MotionEvent event = (MotionEvent) msg.obj;
- try {
- deliverPointerEvent(event);
- } finally {
- event.recycle();
- if (msg.arg1 != 0) {
- finishInputEvent();
- }
- if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!");
- }
- } break;
- case DISPATCH_TRACKBALL: {
- MotionEvent event = (MotionEvent) msg.obj;
- try {
- deliverTrackballEvent(event);
- } finally {
- event.recycle();
- if (msg.arg1 != 0) {
- finishInputEvent();
- }
- }
- } 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_APP_VISIBILITY:
handleAppVisibility(msg.arg1 != 0);
break;
@@ -1871,7 +1852,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
// system! Bad bad bad!
event = KeyEvent.changeFlags(event, event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
}
- deliverKeyEventToViewHierarchy((KeyEvent)msg.obj, false);
+ deliverKeyEventPostIme((KeyEvent)msg.obj, false);
} break;
case FINISH_INPUT_CONNECTION: {
InputMethodManager imm = InputMethodManager.peekInstance();
@@ -1897,7 +1878,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
}
}
- private void startInputEvent(Runnable finishedCallback) {
+ 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.");
@@ -1906,11 +1887,11 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
mFinishedCallback = finishedCallback;
}
- private void finishInputEvent() {
+ private void finishInputEvent(boolean handled) {
if (LOCAL_LOGV) Log.v(TAG, "Telling window manager input event is finished");
if (mFinishedCallback != null) {
- mFinishedCallback.run();
+ mFinishedCallback.finished(handled);
mFinishedCallback = null;
} else {
Slog.w(TAG, "Attempted to tell the input queue that the current input event "
@@ -2039,105 +2020,134 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
return false;
}
- private void deliverPointerEvent(MotionEvent event) {
+ private void deliverPointerEvent(MotionEvent event, boolean sendDone) {
+ // If there is no view, then the event will not be handled.
+ if (mView == null || !mAdded) {
+ finishPointerEvent(event, sendDone, false);
+ return;
+ }
+
+ // Translate the pointer event for compatibility, if needed.
if (mTranslator != null) {
mTranslator.translateEventInScreenToAppWindow(event);
}
-
- boolean handled;
- if (mView != null && mAdded) {
- // enter touch mode on the down
- boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN;
- if (isDown) {
- ensureTouchMode(true);
- }
- if(Config.LOGV) {
- captureMotionLog("captureDispatchPointer", event);
- }
- if (mCurScrollY != 0) {
- event.offsetLocation(0, mCurScrollY);
- }
- if (MEASURE_LATENCY) {
- lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano());
- }
- // cache for possible drag-initiation
- mLastTouchPoint.x = event.getRawX();
- mLastTouchPoint.y = event.getRawY();
- handled = mView.dispatchTouchEvent(event);
- if (MEASURE_LATENCY) {
- lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano());
- }
- if (!handled && isDown) {
- int edgeSlop = mViewConfiguration.getScaledEdgeSlop();
-
- final int edgeFlags = event.getEdgeFlags();
- int direction = View.FOCUS_UP;
- int x = (int)event.getX();
- int y = (int)event.getY();
- final int[] deltas = new int[2];
-
- if ((edgeFlags & MotionEvent.EDGE_TOP) != 0) {
- direction = View.FOCUS_DOWN;
- if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
- deltas[0] = edgeSlop;
- x += edgeSlop;
- } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
- deltas[0] = -edgeSlop;
- x -= edgeSlop;
- }
- } else if ((edgeFlags & MotionEvent.EDGE_BOTTOM) != 0) {
- direction = View.FOCUS_UP;
- if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
- deltas[0] = edgeSlop;
- x += edgeSlop;
- } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
- deltas[0] = -edgeSlop;
- x -= edgeSlop;
- }
- } else if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
- direction = View.FOCUS_RIGHT;
- } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
- direction = View.FOCUS_LEFT;
- }
+ // Enter touch mode on the down.
+ boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN;
+ if (isDown) {
+ ensureTouchMode(true);
+ }
+ if(Config.LOGV) {
+ captureMotionLog("captureDispatchPointer", event);
+ }
- if (edgeFlags != 0 && mView instanceof ViewGroup) {
- View nearest = FocusFinder.getInstance().findNearestTouchable(
- ((ViewGroup) mView), x, y, direction, deltas);
- if (nearest != null) {
- event.offsetLocation(deltas[0], deltas[1]);
- event.setEdgeFlags(0);
- mView.dispatchTouchEvent(event);
- }
+ // Offset the scroll position.
+ if (mCurScrollY != 0) {
+ event.offsetLocation(0, mCurScrollY);
+ }
+ if (MEASURE_LATENCY) {
+ lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano());
+ }
+
+ // Remember the touch position for possible drag-initiation.
+ mLastTouchPoint.x = event.getRawX();
+ mLastTouchPoint.y = event.getRawY();
+
+ // Dispatch touch to view hierarchy.
+ boolean handled = mView.dispatchTouchEvent(event);
+ if (MEASURE_LATENCY) {
+ lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano());
+ }
+ if (handled) {
+ finishPointerEvent(event, sendDone, true);
+ return;
+ }
+
+ // Apply edge slop and try again, if appropriate.
+ final int edgeFlags = event.getEdgeFlags();
+ if (edgeFlags != 0 && mView instanceof ViewGroup) {
+ final int edgeSlop = mViewConfiguration.getScaledEdgeSlop();
+ int direction = View.FOCUS_UP;
+ int x = (int)event.getX();
+ int y = (int)event.getY();
+ final int[] deltas = new int[2];
+
+ if ((edgeFlags & MotionEvent.EDGE_TOP) != 0) {
+ direction = View.FOCUS_DOWN;
+ if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
+ deltas[0] = edgeSlop;
+ x += edgeSlop;
+ } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
+ deltas[0] = -edgeSlop;
+ x -= edgeSlop;
+ }
+ } else if ((edgeFlags & MotionEvent.EDGE_BOTTOM) != 0) {
+ direction = View.FOCUS_UP;
+ if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
+ deltas[0] = edgeSlop;
+ x += edgeSlop;
+ } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
+ deltas[0] = -edgeSlop;
+ x -= edgeSlop;
+ }
+ } else if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
+ direction = View.FOCUS_RIGHT;
+ } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
+ direction = View.FOCUS_LEFT;
+ }
+
+ View nearest = FocusFinder.getInstance().findNearestTouchable(
+ ((ViewGroup) mView), x, y, direction, deltas);
+ if (nearest != null) {
+ event.offsetLocation(deltas[0], deltas[1]);
+ event.setEdgeFlags(0);
+ if (mView.dispatchTouchEvent(event)) {
+ finishPointerEvent(event, sendDone, true);
+ return;
}
}
}
+
+ // Pointer event was unhandled.
+ finishPointerEvent(event, sendDone, false);
}
- private void deliverTrackballEvent(MotionEvent event) {
+ private void finishPointerEvent(MotionEvent event, boolean sendDone, boolean handled) {
+ event.recycle();
+ if (sendDone) {
+ finishInputEvent(handled);
+ }
+ if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!");
+ }
+
+ private void deliverTrackballEvent(MotionEvent event, boolean sendDone) {
if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event);
- boolean handled = false;
- if (mView != null && mAdded) {
- handled = mView.dispatchTrackballEvent(event);
- if (handled) {
- // If we reach this, we delivered a trackball event to mView and
- // mView consumed it. Because we will not translate the trackball
- // event into a key event, touch mode will not exit, so we exit
- // touch mode here.
- ensureTouchMode(false);
- return;
- }
-
- // Otherwise we could do something here, like changing the focus
- // or something?
+ // If there is no view, then the event will not be handled.
+ if (mView == null || !mAdded) {
+ finishTrackballEvent(event, sendDone, false);
+ return;
+ }
+
+ // Deliver the trackball event to the view.
+ if (mView.dispatchTrackballEvent(event)) {
+ // If we reach this, we delivered a trackball event to mView and
+ // mView consumed it. Because we will not translate the trackball
+ // event into a key event, touch mode will not exit, so we exit
+ // touch mode here.
+ ensureTouchMode(false);
+
+ finishTrackballEvent(event, sendDone, true);
+ mLastTrackballTime = Integer.MIN_VALUE;
+ return;
}
+ // Translate the trackball event into DPAD keys and try to deliver those.
final TrackballAxis x = mTrackballAxisX;
final TrackballAxis y = mTrackballAxisY;
long curTime = SystemClock.uptimeMillis();
- if ((mLastTrackballTime+MAX_TRACKBALL_DELAY) < curTime) {
+ if ((mLastTrackballTime + MAX_TRACKBALL_DELAY) < curTime) {
// It has been too long since the last movement,
// so restart at the beginning.
x.reset(0);
@@ -2226,6 +2236,17 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
}
mLastTrackballTime = curTime;
}
+
+ // Unfortunately we can't tell whether the application consumed the keys, so
+ // we always consider the trackball event handled.
+ finishTrackballEvent(event, sendDone, true);
+ }
+
+ private void finishTrackballEvent(MotionEvent event, boolean sendDone, boolean handled) {
+ event.recycle();
+ if (sendDone) {
+ finishInputEvent(handled);
+ }
}
/**
@@ -2371,123 +2392,137 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
}
private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
- // If mView is null, we just consume the key event because it doesn't
- // make sense to do anything else with it.
- boolean handled = mView == null || mView.dispatchKeyEventPreIme(event);
- if (handled) {
- if (sendDone) {
- finishInputEvent();
- }
+ // 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;
}
- // If it is possible for this window to interact with the input
- // method window, then we want to first dispatch our key events
- // to the input method.
+
+ // 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 && mView != null) {
+ 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);
+ imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);
return;
}
}
- deliverKeyEventToViewHierarchy(event, sendDone);
+
+ // Not dispatching to IME, continue with post IME actions.
+ deliverKeyEventPostIme(event, sendDone);
}
- void handleFinishedEvent(int seq, boolean handled) {
+ 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;
- if (!handled) {
- deliverKeyEventToViewHierarchy(event, sendDone);
- } else if (sendDone) {
- finishInputEvent();
+ if (handled) {
+ finishKeyEvent(event, sendDone, true);
} else {
- Log.w(TAG, "handleFinishedEvent(seq=" + seq
- + " handled=" + handled + " ev=" + event
- + ") neither delivering nor finishing key");
+ deliverKeyEventPostIme(event, sendDone);
}
}
}
- private void deliverKeyEventToViewHierarchy(KeyEvent event, boolean sendDone) {
- try {
- if (mView != null && mAdded) {
- final int action = event.getAction();
- boolean isDown = (action == KeyEvent.ACTION_DOWN);
+ private void deliverKeyEventPostIme(KeyEvent event, boolean sendDone) {
+ // If the view went away, then the event will not be handled.
+ if (mView == null || !mAdded) {
+ finishKeyEvent(event, sendDone, false);
+ return;
+ }
- if (checkForLeavingTouchModeAndConsume(event)) {
- 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);
+ return;
+ }
- if (Config.LOGV) {
- captureKeyLog("captureDispatchKeyEvent", event);
- }
- mFallbackEventHandler.preDispatchKeyEvent(event);
- boolean keyHandled = mView.dispatchKeyEvent(event);
+ if (Config.LOGV) {
+ captureKeyLog("captureDispatchKeyEvent", event);
+ }
- if (!keyHandled) {
- mFallbackEventHandler.dispatchKeyEvent(event);
- }
+ // Deliver the key to the view hierarchy.
+ if (mView.dispatchKeyEvent(event)) {
+ finishKeyEvent(event, sendDone, true);
+ return;
+ }
- if (!keyHandled && isDown) {
- int direction = 0;
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- direction = View.FOCUS_LEFT;
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- direction = View.FOCUS_RIGHT;
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- direction = View.FOCUS_UP;
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- direction = View.FOCUS_DOWN;
- break;
- }
+ // Apply the fallback event policy.
+ if (mFallbackEventHandler.dispatchKeyEvent(event)) {
+ finishKeyEvent(event, sendDone, true);
+ return;
+ }
- if (direction != 0) {
-
- View focused = mView != null ? mView.findFocus() : null;
- if (focused != null) {
- View v = focused.focusSearch(direction);
- boolean focusPassed = false;
- if (v != null && v != focused) {
- // do the math the get the interesting rect
- // of previous focused into the coord system of
- // newly focused view
- focused.getFocusedRect(mTempRect);
- if (mView instanceof ViewGroup) {
- ((ViewGroup) mView).offsetDescendantRectToMyCoords(
- focused, mTempRect);
- ((ViewGroup) mView).offsetRectIntoDescendantCoords(
- v, mTempRect);
- }
- focusPassed = v.requestFocus(direction, mTempRect);
- }
+ // Handle automatic focus changes.
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ int direction = 0;
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ direction = View.FOCUS_LEFT;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ direction = View.FOCUS_RIGHT;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ direction = View.FOCUS_UP;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ direction = View.FOCUS_DOWN;
+ break;
+ }
- if (!focusPassed) {
- mView.dispatchUnhandledMove(focused, direction);
- } else {
- playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
- }
+ if (direction != 0) {
+ View focused = mView != null ? mView.findFocus() : null;
+ if (focused != null) {
+ View v = focused.focusSearch(direction);
+ if (v != null && v != focused) {
+ // do the math the get the interesting rect
+ // of previous focused into the coord system of
+ // newly focused view
+ focused.getFocusedRect(mTempRect);
+ if (mView instanceof ViewGroup) {
+ ((ViewGroup) mView).offsetDescendantRectToMyCoords(
+ focused, mTempRect);
+ ((ViewGroup) mView).offsetRectIntoDescendantCoords(
+ v, mTempRect);
+ }
+ if (v.requestFocus(direction, mTempRect)) {
+ playSoundEffect(
+ SoundEffectConstants.getContantForFocusDirection(direction));
+ finishKeyEvent(event, sendDone, true);
+ return;
}
}
+
+ // Give the focused view a last chance to handle the dpad key.
+ if (mView.dispatchUnhandledMove(focused, direction)) {
+ finishKeyEvent(event, sendDone, true);
+ return;
+ }
}
}
+ }
- } finally {
- if (sendDone) {
- finishInputEvent();
- }
- // Let the exception fall through -- the looper will catch
- // it and take care of the bad app for us.
+ // Key was unhandled.
+ finishKeyEvent(event, sendDone, false);
+ }
+
+ private void finishKeyEvent(KeyEvent event, boolean sendDone, boolean handled) {
+ if (sendDone) {
+ finishInputEvent(handled);
}
}
@@ -2759,15 +2794,15 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
sendMessage(msg);
}
- private Runnable mFinishedCallback;
+ private InputQueue.FinishedCallback mFinishedCallback;
private final InputHandler mInputHandler = new InputHandler() {
- public void handleKey(KeyEvent event, Runnable finishedCallback) {
+ public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback) {
startInputEvent(finishedCallback);
dispatchKey(event, true);
}
- public void handleMotion(MotionEvent event, Runnable finishedCallback) {
+ public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
startInputEvent(finishedCallback);
dispatchMotion(event, true);
}
@@ -2814,7 +2849,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
// TODO
Log.v(TAG, "Dropping unsupported motion event (unimplemented): " + event);
if (sendDone) {
- finishInputEvent();
+ finishInputEvent(false);
}
}
}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index e78d6a8..4deff5e 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -568,12 +568,8 @@ public interface WindowManagerPolicy {
* Called from the input dispatcher thread before a key is dispatched to a window.
*
* <p>Allows you to define
- * behavior for keys that can not be overridden by applications or redirect
- * key events to a different window. This method is called from the
- * input thread, with no locks held.
- *
- * <p>Note that if you change the window a key is dispatched to, the new
- * target window will receive the key event without having input focus.
+ * behavior for keys that can not be overridden by applications.
+ * This method is called from the input thread, with no locks held.
*
* @param win The window that currently has focus. This is where the key
* event will normally go.
@@ -591,6 +587,27 @@ public interface WindowManagerPolicy {
int keyCode, int scanCode, int metaState, int repeatCount, int policyFlags);
/**
+ * Called from the input dispatcher thread when an application did not handle
+ * a key that was dispatched to it.
+ *
+ * <p>Allows you to define default global behavior for keys that were not handled
+ * by applications. This method is called from the input thread, with no locks held.
+ *
+ * @param win The window that currently has focus. This is where the key
+ * event will normally go.
+ * @param action The key event action.
+ * @param flags The key event flags.
+ * @param keyCode The key code.
+ * @param scanCode The key's scan code.
+ * @param metaState bit mask of meta keys that are held.
+ * @param repeatCount Number of times a key down has repeated.
+ * @param policyFlags The policy flags associated with the key.
+ * @return Returns true if the policy consumed the event.
+ */
+ public boolean dispatchUnhandledKey(WindowState win, int action, int flags,
+ int keyCode, int scanCode, int metaState, int repeatCount, int policyFlags);
+
+ /**
* Called when layout of the windows is about to start.
*
* @param displayWidth The current full width of the screen.
diff --git a/core/java/com/android/internal/view/BaseInputHandler.java b/core/java/com/android/internal/view/BaseInputHandler.java
index e943a7d..74b4b06 100644
--- a/core/java/com/android/internal/view/BaseInputHandler.java
+++ b/core/java/com/android/internal/view/BaseInputHandler.java
@@ -17,6 +17,7 @@
package com.android.internal.view;
import android.view.InputHandler;
+import android.view.InputQueue;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -25,11 +26,11 @@ import android.view.MotionEvent;
* @hide
*/
public abstract class BaseInputHandler implements InputHandler {
- public void handleKey(KeyEvent event, Runnable finishedCallback) {
- finishedCallback.run();
+ public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback) {
+ finishedCallback.finished(false);
}
- public void handleMotion(MotionEvent event, Runnable finishedCallback) {
- finishedCallback.run();
+ public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
+ finishedCallback.finished(false);
}
}
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index a586f49..695d50a4 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -195,7 +195,7 @@ int32_t AInputQueue::getEvent(AInputEvent** outEvent) {
mLock.unlock();
if (finishNow) {
- finishEvent(*outEvent, true);
+ finishEvent(*outEvent, true, false);
*outEvent = NULL;
return -1;
} else if (*outEvent != NULL) {
@@ -215,7 +215,7 @@ int32_t AInputQueue::getEvent(AInputEvent** outEvent) {
if (res != android::OK) {
LOGW("channel '%s' ~ Failed to consume input event. status=%d",
mConsumer.getChannel()->getName().string(), res);
- mConsumer.sendFinishedSignal();
+ mConsumer.sendFinishedSignal(false);
return -1;
}
@@ -245,10 +245,12 @@ bool AInputQueue::preDispatchEvent(AInputEvent* event) {
return preDispatchKey((KeyEvent*)event);
}
-void AInputQueue::finishEvent(AInputEvent* event, bool handled) {
- LOG_TRACE("finishEvent: %p handled=%d", event, handled ? 1 : 0);
+void AInputQueue::finishEvent(AInputEvent* event, bool handled, bool didDefaultHandling) {
+ LOG_TRACE("finishEvent: %p handled=%d, didDefaultHandling=%d", event,
+ handled ? 1 : 0, didDefaultHandling ? 1 : 0);
- if (!handled && ((InputEvent*)event)->getType() == AINPUT_EVENT_TYPE_KEY
+ if (!handled && !didDefaultHandling
+ && ((InputEvent*)event)->getType() == AINPUT_EVENT_TYPE_KEY
&& ((KeyEvent*)event)->hasDefaultAction()) {
// The app didn't handle this, but it may have a default action
// associated with it. We need to hand this back to Java to be
@@ -263,7 +265,7 @@ void AInputQueue::finishEvent(AInputEvent* event, bool handled) {
const in_flight_event& inflight(mInFlightEvents[i]);
if (inflight.event == event) {
if (inflight.doFinish) {
- int32_t res = mConsumer.sendFinishedSignal();
+ int32_t res = mConsumer.sendFinishedSignal(handled);
if (res != android::OK) {
LOGW("Failed to send finished signal on channel '%s'. status=%d",
mConsumer.getChannel()->getName().string(), res);
@@ -577,10 +579,11 @@ static int mainWorkCallback(int fd, int events, void* data) {
while ((keyEvent=code->nativeInputQueue->consumeUnhandledEvent()) != NULL) {
jobject inputEventObj = android_view_KeyEvent_fromNative(
code->env, keyEvent);
- code->env->CallVoidMethod(code->clazz,
+ jboolean handled = code->env->CallBooleanMethod(code->clazz,
gNativeActivityClassInfo.dispatchUnhandledKeyEvent, inputEventObj);
checkAndClearExceptionFromCallback(code->env, "dispatchUnhandledKeyEvent");
- code->nativeInputQueue->finishEvent(keyEvent, true);
+ code->env->DeleteLocalRef(inputEventObj);
+ code->nativeInputQueue->finishEvent(keyEvent, handled, true);
}
int seq;
while ((keyEvent=code->nativeInputQueue->consumePreDispatchingEvent(&seq)) != NULL) {
@@ -589,6 +592,7 @@ static int mainWorkCallback(int fd, int events, void* data) {
code->env->CallVoidMethod(code->clazz,
gNativeActivityClassInfo.preDispatchKeyEvent, inputEventObj, seq);
checkAndClearExceptionFromCallback(code->env, "preDispatchKeyEvent");
+ code->env->DeleteLocalRef(inputEventObj);
}
} break;
case CMD_FINISH: {
@@ -1044,7 +1048,7 @@ int register_android_app_NativeActivity(JNIEnv* env)
GET_METHOD_ID(gNativeActivityClassInfo.dispatchUnhandledKeyEvent,
gNativeActivityClassInfo.clazz,
- "dispatchUnhandledKeyEvent", "(Landroid/view/KeyEvent;)V");
+ "dispatchUnhandledKeyEvent", "(Landroid/view/KeyEvent;)Z");
GET_METHOD_ID(gNativeActivityClassInfo.preDispatchKeyEvent,
gNativeActivityClassInfo.clazz,
"preDispatchKeyEvent", "(Landroid/view/KeyEvent;I)V");
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
index 282e9ed..b5a5d2e 100644
--- a/core/jni/android_view_InputQueue.cpp
+++ b/core/jni/android_view_InputQueue.cpp
@@ -61,7 +61,7 @@ public:
status_t unregisterInputChannel(JNIEnv* env, jobject inputChannelObj);
- status_t finished(JNIEnv* env, jlong finishedToken, bool ignoreSpuriousFinish);
+ status_t finished(JNIEnv* env, jlong finishedToken, bool handled, bool ignoreSpuriousFinish);
private:
class Connection : public RefBase {
@@ -211,7 +211,7 @@ status_t NativeInputQueue::unregisterInputChannel(JNIEnv* env, jobject inputChan
"while an input message is still in progress.",
connection->getInputChannelName());
connection->messageInProgress = false;
- connection->inputConsumer.sendFinishedSignal(); // ignoring result
+ connection->inputConsumer.sendFinishedSignal(false); // ignoring result
}
} // release lock
@@ -231,7 +231,8 @@ ssize_t NativeInputQueue::getConnectionIndex(const sp<InputChannel>& inputChanne
return -1;
}
-status_t NativeInputQueue::finished(JNIEnv* env, jlong finishedToken, bool ignoreSpuriousFinish) {
+status_t NativeInputQueue::finished(JNIEnv* env, jlong finishedToken,
+ bool handled, bool ignoreSpuriousFinish) {
int32_t receiveFd;
uint16_t connectionId;
uint16_t messageSeqNum;
@@ -268,7 +269,7 @@ status_t NativeInputQueue::finished(JNIEnv* env, jlong finishedToken, bool ignor
connection->messageInProgress = false;
- status_t status = connection->inputConsumer.sendFinishedSignal();
+ status_t status = connection->inputConsumer.sendFinishedSignal(handled);
if (status) {
LOGW("Failed to send finished signal on channel '%s'. status=%d",
connection->getInputChannelName(), status);
@@ -341,7 +342,7 @@ int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* dat
if (status) {
LOGW("channel '%s' ~ Failed to consume input event. status=%d",
connection->getInputChannelName(), status);
- connection->inputConsumer.sendFinishedSignal();
+ connection->inputConsumer.sendFinishedSignal(false);
return 1;
}
@@ -393,7 +394,7 @@ int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* dat
LOGW("channel '%s' ~ Failed to obtain DVM event object.",
connection->getInputChannelName());
env->DeleteLocalRef(inputHandlerObjLocal);
- q->finished(env, finishedToken, false);
+ q->finished(env, finishedToken, false, false);
return 1;
}
@@ -412,7 +413,7 @@ int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* dat
LOGE_EX(env);
env->ExceptionClear();
- q->finished(env, finishedToken, true /*ignoreSpuriousFinish*/);
+ q->finished(env, finishedToken, false, true /*ignoreSpuriousFinish*/);
}
env->DeleteLocalRef(inputEventObj);
@@ -470,9 +471,9 @@ static void android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv* env, jc
}
static void android_view_InputQueue_nativeFinished(JNIEnv* env, jclass clazz,
- jlong finishedToken) {
+ jlong finishedToken, bool handled) {
status_t status = gNativeInputQueue.finished(
- env, finishedToken, false /*ignoreSpuriousFinish*/);
+ env, finishedToken, handled, false /*ignoreSpuriousFinish*/);
// We ignore the case where an event could not be finished because the input channel
// was no longer registered (DEAD_OBJECT) since it is a common race that can occur
@@ -493,7 +494,7 @@ static JNINativeMethod gInputQueueMethods[] = {
{ "nativeUnregisterInputChannel",
"(Landroid/view/InputChannel;)V",
(void*)android_view_InputQueue_nativeUnregisterInputChannel },
- { "nativeFinished", "(J)V",
+ { "nativeFinished", "(JZ)V",
(void*)android_view_InputQueue_nativeFinished }
};
diff --git a/include/android_runtime/android_app_NativeActivity.h b/include/android_runtime/android_app_NativeActivity.h
index 5dbec59..990143b 100644
--- a/include/android_runtime/android_app_NativeActivity.h
+++ b/include/android_runtime/android_app_NativeActivity.h
@@ -83,7 +83,7 @@ public:
bool preDispatchEvent(AInputEvent* event);
- void finishEvent(AInputEvent* event, bool handled);
+ void finishEvent(AInputEvent* event, bool handled, bool didDefaultHandling);
// ----------------------------------------------------------
diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h
index a5591ba..d09ff41 100644
--- a/include/ui/InputDispatcher.h
+++ b/include/ui/InputDispatcher.h
@@ -304,6 +304,10 @@ public:
virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
+ /* Allows the policy a chance to perform default processing for an unhandled key. */
+ virtual bool dispatchUnhandledKey(const sp<InputChannel>& inputChannel,
+ const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
+
/* Notifies the policy about switch events.
*/
virtual void notifySwitch(nsecs_t when,
@@ -609,6 +613,7 @@ private:
sp<InputChannel> inputChannel;
sp<InputApplicationHandle> inputApplicationHandle;
int32_t userActivityEventType;
+ bool handled;
};
// Generic queue implementation.
@@ -1030,7 +1035,8 @@ private:
EventEntry* eventEntry, const InputTarget* inputTarget,
bool resumeWithAppendedMotionSample);
void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
- void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+ void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ bool handled);
void startNextDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
void drainOutboundQueueLocked(Connection* connection);
@@ -1061,7 +1067,7 @@ private:
void onDispatchCycleStartedLocked(
nsecs_t currentTime, const sp<Connection>& connection);
void onDispatchCycleFinishedLocked(
- nsecs_t currentTime, const sp<Connection>& connection);
+ nsecs_t currentTime, const sp<Connection>& connection, bool handled);
void onDispatchCycleBrokenLocked(
nsecs_t currentTime, const sp<Connection>& connection);
void onANRLocked(
@@ -1073,7 +1079,9 @@ private:
void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry);
void doNotifyANRLockedInterruptible(CommandEntry* commandEntry);
void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry);
+ void doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry);
void doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry);
+ void initializeKeyEvent(KeyEvent* event, const KeyEntry* entry);
// Statistics gathering.
void updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
diff --git a/include/ui/InputTransport.h b/include/ui/InputTransport.h
index dc9e27a..7efb6cc 100644
--- a/include/ui/InputTransport.h
+++ b/include/ui/InputTransport.h
@@ -250,12 +250,13 @@ public:
status_t sendDispatchSignal();
/* Receives the finished signal from the consumer in reply to the original dispatch signal.
+ * Returns whether the consumer handled the message.
*
* Returns OK on success.
* Returns WOULD_BLOCK if there is no signal present.
* Other errors probably indicate that the channel is broken.
*/
- status_t receiveFinishedSignal();
+ status_t receiveFinishedSignal(bool& outHandled);
private:
sp<InputChannel> mChannel;
@@ -305,12 +306,12 @@ public:
status_t consume(InputEventFactoryInterface* factory, InputEvent** outEvent);
/* Sends a finished signal to the publisher to inform it that the current message is
- * finished processing.
+ * finished processing and specifies whether the message was handled by the consumer.
*
* Returns OK on success.
* Errors probably indicate that the channel is broken.
*/
- status_t sendFinishedSignal();
+ status_t sendFinishedSignal(bool handled);
/* Receives the dispatched signal from the publisher.
*
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index f9c0b91..299b1ba 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -1770,13 +1770,14 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
}
void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
- const sp<Connection>& connection) {
+ const sp<Connection>& connection, bool handled) {
#if DEBUG_DISPATCH_CYCLE
LOGD("channel '%s' ~ finishDispatchCycle - %01.1fms since event, "
- "%01.1fms since dispatch",
+ "%01.1fms since dispatch, handled=%s",
connection->getInputChannelName(),
connection->getEventLatencyMillis(currentTime),
- connection->getDispatchLatencyMillis(currentTime));
+ connection->getDispatchLatencyMillis(currentTime),
+ toString(handled));
#endif
if (connection->status == Connection::STATUS_BROKEN
@@ -1784,9 +1785,6 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
return;
}
- // Notify other system components.
- onDispatchCycleFinishedLocked(currentTime, connection);
-
// Reset the publisher since the event has been consumed.
// We do this now so that the publisher can release some of its internal resources
// while waiting for the next dispatch cycle to begin.
@@ -1798,7 +1796,8 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
return;
}
- startNextDispatchCycleLocked(currentTime, connection);
+ // Notify other system components and prepare to start the next dispatch cycle.
+ onDispatchCycleFinishedLocked(currentTime, connection, handled);
}
void InputDispatcher::startNextDispatchCycleLocked(nsecs_t currentTime,
@@ -1898,7 +1897,8 @@ int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data
return 1;
}
- status_t status = connection->inputPublisher.receiveFinishedSignal();
+ bool handled = false;
+ status_t status = connection->inputPublisher.receiveFinishedSignal(handled);
if (status) {
LOGE("channel '%s' ~ Failed to receive finished signal. status=%d",
connection->getInputChannelName(), status);
@@ -1907,7 +1907,7 @@ int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data
return 0; // remove the callback
}
- d->finishDispatchCycleLocked(currentTime, connection);
+ d->finishDispatchCycleLocked(currentTime, connection, handled);
d->runCommandsLockedInterruptible();
return 1;
} // release lock
@@ -2945,7 +2945,11 @@ void InputDispatcher::onDispatchCycleStartedLocked(
}
void InputDispatcher::onDispatchCycleFinishedLocked(
- nsecs_t currentTime, const sp<Connection>& connection) {
+ nsecs_t currentTime, const sp<Connection>& connection, bool handled) {
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doDispatchCycleFinishedLockedInterruptible);
+ commandEntry->connection = connection;
+ commandEntry->handled = handled;
}
void InputDispatcher::onDispatchCycleBrokenLocked(
@@ -3014,9 +3018,7 @@ void InputDispatcher::doNotifyANRLockedInterruptible(
void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
CommandEntry* commandEntry) {
KeyEntry* entry = commandEntry->keyEntry;
- mReusableKeyEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags,
- entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
- entry->downTime, entry->eventTime);
+ initializeKeyEvent(&mReusableKeyEvent, entry);
mLock.unlock();
@@ -3031,6 +3033,31 @@ void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
mAllocator.releaseKeyEntry(entry);
}
+void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(
+ CommandEntry* commandEntry) {
+ sp<Connection> connection = commandEntry->connection;
+ bool handled = commandEntry->handled;
+
+ if (!handled && !connection->outboundQueue.isEmpty()) {
+ DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
+ if (dispatchEntry->inProgress
+ && dispatchEntry->hasForegroundTarget()
+ && dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
+ initializeKeyEvent(&mReusableKeyEvent, keyEntry);
+
+ mLock.unlock();
+
+ mPolicy->dispatchUnhandledKey(connection->inputChannel,
+ & mReusableKeyEvent, keyEntry->policyFlags);
+
+ mLock.lock();
+ }
+ }
+
+ startNextDispatchCycleLocked(now(), connection);
+}
+
void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) {
mLock.unlock();
@@ -3039,6 +3066,12 @@ void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* comman
mLock.lock();
}
+void InputDispatcher::initializeKeyEvent(KeyEvent* event, const KeyEntry* entry) {
+ event->initialize(entry->deviceId, entry->source, entry->action, entry->flags,
+ entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
+ entry->downTime, entry->eventTime);
+}
+
void InputDispatcher::updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
int32_t injectionResult, nsecs_t timeSpentWaitingForApplication) {
// TODO Write some statistics about how long we spend waiting.
diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp
index 2c6346e..1885691 100644
--- a/libs/ui/InputTransport.cpp
+++ b/libs/ui/InputTransport.cpp
@@ -35,8 +35,12 @@ static const int DEFAULT_MESSAGE_BUFFER_SIZE = 16384;
static const char INPUT_SIGNAL_DISPATCH = 'D';
// Signal sent by the consumer to the producer to inform it that it has finished
-// consuming the most recent message.
-static const char INPUT_SIGNAL_FINISHED = 'f';
+// consuming the most recent message and it handled it.
+static const char INPUT_SIGNAL_FINISHED_HANDLED = 'f';
+
+// Signal sent by the consumer to the producer to inform it that it has finished
+// consuming the most recent message but it did not handle it.
+static const char INPUT_SIGNAL_FINISHED_UNHANDLED = 'u';
// --- InputChannel ---
@@ -497,7 +501,7 @@ status_t InputPublisher::sendDispatchSignal() {
return mChannel->sendSignal(INPUT_SIGNAL_DISPATCH);
}
-status_t InputPublisher::receiveFinishedSignal() {
+status_t InputPublisher::receiveFinishedSignal(bool& outHandled) {
#if DEBUG_TRANSPORT_ACTIONS
LOGD("channel '%s' publisher ~ receiveFinishedSignal",
mChannel->getName().string());
@@ -506,9 +510,14 @@ status_t InputPublisher::receiveFinishedSignal() {
char signal;
status_t result = mChannel->receiveSignal(& signal);
if (result) {
+ outHandled = false;
return result;
}
- if (signal != INPUT_SIGNAL_FINISHED) {
+ if (signal == INPUT_SIGNAL_FINISHED_HANDLED) {
+ outHandled = true;
+ } else if (signal == INPUT_SIGNAL_FINISHED_UNHANDLED) {
+ outHandled = false;
+ } else {
LOGE("channel '%s' publisher ~ Received unexpected signal '%c' from consumer",
mChannel->getName().string(), signal);
return UNKNOWN_ERROR;
@@ -626,13 +635,15 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent*
return OK;
}
-status_t InputConsumer::sendFinishedSignal() {
+status_t InputConsumer::sendFinishedSignal(bool handled) {
#if DEBUG_TRANSPORT_ACTIONS
- LOGD("channel '%s' consumer ~ sendFinishedSignal",
- mChannel->getName().string());
+ LOGD("channel '%s' consumer ~ sendFinishedSignal: handled=%d",
+ mChannel->getName().string(), handled);
#endif
- return mChannel->sendSignal(INPUT_SIGNAL_FINISHED);
+ return mChannel->sendSignal(handled
+ ? INPUT_SIGNAL_FINISHED_HANDLED
+ : INPUT_SIGNAL_FINISHED_UNHANDLED);
}
status_t InputConsumer::receiveDispatchSignal() {
diff --git a/libs/ui/tests/InputDispatcher_test.cpp b/libs/ui/tests/InputDispatcher_test.cpp
index 8874dfe..f352dbf 100644
--- a/libs/ui/tests/InputDispatcher_test.cpp
+++ b/libs/ui/tests/InputDispatcher_test.cpp
@@ -67,6 +67,11 @@ private:
return false;
}
+ virtual bool dispatchUnhandledKey(const sp<InputChannel>& inputChannel,
+ const KeyEvent* keyEvent, uint32_t policyFlags) {
+ return false;
+ }
+
virtual void notifySwitch(nsecs_t when,
int32_t switchCode, int32_t switchValue, uint32_t policyFlags) {
}
diff --git a/libs/ui/tests/InputPublisherAndConsumer_test.cpp b/libs/ui/tests/InputPublisherAndConsumer_test.cpp
index 952b974..c6eac25 100644
--- a/libs/ui/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/ui/tests/InputPublisherAndConsumer_test.cpp
@@ -118,13 +118,16 @@ void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() {
EXPECT_EQ(downTime, keyEvent->getDownTime());
EXPECT_EQ(eventTime, keyEvent->getEventTime());
- status = mConsumer->sendFinishedSignal();
+ status = mConsumer->sendFinishedSignal(true);
ASSERT_EQ(OK, status)
<< "consumer sendFinishedSignal should return OK";
- status = mPublisher->receiveFinishedSignal();
+ bool handled = false;
+ status = mPublisher->receiveFinishedSignal(handled);
ASSERT_EQ(OK, status)
<< "publisher receiveFinishedSignal should return OK";
+ ASSERT_TRUE(handled)
+ << "publisher receiveFinishedSignal should have set handled to consumer's reply";
status = mPublisher->reset();
ASSERT_EQ(OK, status)
@@ -279,13 +282,16 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent(
EXPECT_EQ(samplePointerCoords[offset].orientation, motionEvent->getOrientation(i));
}
- status = mConsumer->sendFinishedSignal();
+ status = mConsumer->sendFinishedSignal(false);
ASSERT_EQ(OK, status)
<< "consumer sendFinishedSignal should return OK";
- status = mPublisher->receiveFinishedSignal();
+ bool handled = true;
+ status = mPublisher->receiveFinishedSignal(handled);
ASSERT_EQ(OK, status)
<< "publisher receiveFinishedSignal should return OK";
+ ASSERT_FALSE(handled)
+ << "publisher receiveFinishedSignal should have set handled to consumer's reply";
status = mPublisher->reset();
ASSERT_EQ(OK, status)
diff --git a/native/android/input.cpp b/native/android/input.cpp
index c753aa5..a96240c 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -271,5 +271,5 @@ int32_t AInputQueue_preDispatchEvent(AInputQueue* queue, AInputEvent* event) {
}
void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled) {
- queue->finishEvent(event, handled != 0);
+ queue->finishEvent(event, handled != 0, false);
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 30aab47..4bc7433 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -253,13 +253,19 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private final InputHandler mPointerLocationInputHandler = new BaseInputHandler() {
@Override
- public void handleMotion(MotionEvent event, Runnable finishedCallback) {
- finishedCallback.run();
-
- synchronized (mLock) {
- if (mPointerLocationView != null) {
- mPointerLocationView.addTouchEvent(event);
+ public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
+ boolean handled = false;
+ try {
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ synchronized (mLock) {
+ if (mPointerLocationView != null) {
+ mPointerLocationView.addTouchEvent(event);
+ handled = true;
+ }
+ }
}
+ } finally {
+ finishedCallback.finished(handled);
}
}
};
@@ -1329,6 +1335,19 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return false;
}
+ /** {@inheritDoc} */
+ @Override
+ public boolean dispatchUnhandledKey(WindowState win, int action, int flags,
+ int keyCode, int scanCode, int metaState, int repeatCount, int policyFlags) {
+ if (false) {
+ Slog.d(TAG, "Unhandled key: win=" + win + ", action=" + action
+ + ", flags=" + flags + ", keyCode=" + keyCode
+ + ", scanCode=" + scanCode + ", metaState=" + metaState
+ + ", repeatCount=" + repeatCount + ", policyFlags=" + policyFlags);
+ }
+ return false;
+ }
+
/**
* A home key -> launch home action was detected. Take the appropriate action
* given the situation with the keyguard.
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
index 13be984..4364c04 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -419,6 +419,14 @@ public class InputManager {
}
@SuppressWarnings("unused")
+ public boolean dispatchUnhandledKey(InputChannel focus, int action,
+ int flags, int keyCode, int scanCode, int metaState, int repeatCount,
+ int policyFlags) {
+ return mWindowManagerService.mInputMonitor.dispatchUnhandledKey(focus,
+ action, flags, keyCode, scanCode, metaState, repeatCount, policyFlags);
+ }
+
+ @SuppressWarnings("unused")
public boolean checkInjectEventsPermission(int injectorPid, int injectorUid) {
return mContext.checkPermission(
android.Manifest.permission.INJECT_EVENTS, injectorPid, injectorUid)
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index c74a27c..1c92da9 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -824,13 +824,15 @@ public class WindowManagerService extends IWindowManager.Stub
DragState mDragState = null;
private final InputHandler mDragInputHandler = new BaseInputHandler() {
@Override
- public void handleMotion(MotionEvent event, Runnable finishedCallback) {
- boolean endDrag = false;
- final float newX = event.getRawX();
- final float newY = event.getRawY();
-
+ public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
+ boolean handled = false;
try {
- if (mDragState != null) {
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0
+ && mDragState != null) {
+ boolean endDrag = false;
+ final float newX = event.getRawX();
+ final float newY = event.getRawY();
+
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
if (DEBUG_DRAG) {
@@ -866,11 +868,13 @@ public class WindowManagerService extends IWindowManager.Stub
mDragState.endDragLw();
}
}
+
+ handled = true;
}
} catch (Exception e) {
Slog.e(TAG, "Exception caught by drag handleMotion", e);
} finally {
- finishedCallback.run();
+ finishedCallback.finished(handled);
}
}
};
@@ -5781,6 +5785,16 @@ public class WindowManagerService extends IWindowManager.Stub
keyCode, scanCode, metaState, repeatCount, policyFlags);
}
+ /* Provides an opportunity for the window manager policy to process a key that
+ * the application did not handle. */
+ public boolean dispatchUnhandledKey(InputChannel focus,
+ int action, int flags, int keyCode, int scanCode, int metaState, int repeatCount,
+ int policyFlags) {
+ WindowState windowState = getWindowStateForInputChannel(focus);
+ return mPolicy.dispatchUnhandledKey(windowState, action, flags,
+ keyCode, scanCode, metaState, repeatCount, policyFlags);
+ }
+
/* Called when the current input focus changes.
* Layer assignment is assumed to be complete by the time this is called.
*/
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index d4c4ba4..3fd6965 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -52,6 +52,7 @@ static struct {
jmethodID notifyANR;
jmethodID interceptKeyBeforeQueueing;
jmethodID interceptKeyBeforeDispatching;
+ jmethodID dispatchUnhandledKey;
jmethodID checkInjectEventsPermission;
jmethodID filterTouchEvents;
jmethodID filterJumpyTouchEvents;
@@ -206,6 +207,8 @@ public:
virtual void interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags);
virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
const KeyEvent* keyEvent, uint32_t policyFlags);
+ virtual bool dispatchUnhandledKey(const sp<InputChannel>& inputChannel,
+ const KeyEvent* keyEvent, uint32_t policyFlags);
virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType);
virtual bool checkInjectEventsPermissionNonReentrant(
int32_t injectorPid, int32_t injectorUid);
@@ -937,6 +940,29 @@ bool NativeInputManager::interceptKeyBeforeDispatching(const sp<InputChannel>& i
}
}
+bool NativeInputManager::dispatchUnhandledKey(const sp<InputChannel>& inputChannel,
+ const KeyEvent* keyEvent, uint32_t policyFlags) {
+ // Policy:
+ // - Ignore untrusted events and do not perform default handling.
+ if (policyFlags & POLICY_FLAG_TRUSTED) {
+ JNIEnv* env = jniEnv();
+
+ // Note: inputChannel may be null.
+ jobject inputChannelObj = getInputChannelObjLocal(env, inputChannel);
+ jboolean handled = env->CallBooleanMethod(mCallbacksObj,
+ gCallbacksClassInfo.dispatchUnhandledKey,
+ inputChannelObj, keyEvent->getAction(), keyEvent->getFlags(),
+ keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(),
+ keyEvent->getRepeatCount(), policyFlags);
+ bool error = checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey");
+
+ env->DeleteLocalRef(inputChannelObj);
+ return handled && ! error;
+ } else {
+ return false;
+ }
+}
+
void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) {
android_server_PowerManagerService_userActivity(eventTime, eventType);
}
@@ -1363,6 +1389,9 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeDispatching, gCallbacksClassInfo.clazz,
"interceptKeyBeforeDispatching", "(Landroid/view/InputChannel;IIIIIII)Z");
+ GET_METHOD_ID(gCallbacksClassInfo.dispatchUnhandledKey, gCallbacksClassInfo.clazz,
+ "dispatchUnhandledKey", "(Landroid/view/InputChannel;IIIIIII)Z");
+
GET_METHOD_ID(gCallbacksClassInfo.checkInjectEventsPermission, gCallbacksClassInfo.clazz,
"checkInjectEventsPermission", "(II)Z");