Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3915bb84 authored by Jeff Brown's avatar Jeff Brown
Browse files

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
parent 60029771
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -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;
+5 −2
Original line number Diff line number Diff line
@@ -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);
                }
            }
        };
+2 −2
Original line number Diff line number Diff line
@@ -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);
}
+10 −6
Original line number Diff line number Diff line
@@ -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) {
+234 −199
Original line number Diff line number Diff line
@@ -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,15 +2020,19 @@ 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
        // Enter touch mode on the down.
        boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN;
        if (isDown) {
            ensureTouchMode(true);
@@ -2055,23 +2040,33 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
        if(Config.LOGV) {
            captureMotionLog("captureDispatchPointer", event);
        }

        // Offset the scroll position.
        if (mCurScrollY != 0) {
            event.offsetLocation(0, mCurScrollY);
        }
        if (MEASURE_LATENCY) {
            lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano());
        }
            // cache for possible drag-initiation

        // Remember the touch position for possible drag-initiation.
        mLastTouchPoint.x = event.getRawX();
        mLastTouchPoint.y = event.getRawY();
            handled = mView.dispatchTouchEvent(event);

        // Dispatch touch to view hierarchy.
        boolean handled = mView.dispatchTouchEvent(event);
        if (MEASURE_LATENCY) {
            lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano());
        }
            if (!handled && isDown) {
                int edgeSlop = mViewConfiguration.getScaledEdgeSlop();
        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();
@@ -2101,38 +2096,53 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
                direction = View.FOCUS_LEFT;
            }

                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);
                if (mView.dispatchTouchEvent(event)) {
                    finishPointerEvent(event, sendDone, true);
                    return;
                }
            }
        }

        // Pointer event was unhandled.
        finishPointerEvent(event, sendDone, false);
    }

    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) {
    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 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);
                return;
            }

            // Otherwise we could do something here, like changing the focus
            // or something?
            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;

@@ -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,71 +2392,82 @@ 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 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 (!keyHandled) {
                    mFallbackEventHandler.dispatchKeyEvent(event);
        // Deliver the key to the view hierarchy.
        if (mView.dispatchKeyEvent(event)) {
            finishKeyEvent(event, sendDone, true);
            return;
        }

                if (!keyHandled && isDown) {
        // Apply the fallback event policy.
        if (mFallbackEventHandler.dispatchKeyEvent(event)) {
            finishKeyEvent(event, sendDone, true);
            return;
        }

        // Handle automatic focus changes.
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            int direction = 0;
            switch (event.getKeyCode()) {
            case KeyEvent.KEYCODE_DPAD_LEFT:
@@ -2453,11 +2485,9 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
            }

            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
@@ -2469,25 +2499,30 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
                            ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                                    v, mTempRect);
                        }
                                focusPassed = v.requestFocus(direction, mTempRect);
                        if (v.requestFocus(direction, mTempRect)) {
                            playSoundEffect(
                                    SoundEffectConstants.getContantForFocusDirection(direction));
                            finishKeyEvent(event, sendDone, true);
                            return;
                        }
                    }

                            if (!focusPassed) {
                                mView.dispatchUnhandledMove(focused, direction);
                            } else {
                                playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
                    // Give the focused view a last chance to handle the dpad key.
                    if (mView.dispatchUnhandledMove(focused, direction)) {
                        finishKeyEvent(event, sendDone, true);
                        return;
                    }
                }
            }
        }

        // Key was unhandled.
        finishKeyEvent(event, sendDone, false);
    }

        } finally {
    private void finishKeyEvent(KeyEvent event, boolean sendDone, boolean handled) {
        if (sendDone) {
                finishInputEvent();
            }
            // Let the exception fall through -- the looper will catch
            // it and take care of the bad app for us.
            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);
            }
        }
    }
Loading