diff options
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"); |