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

Commit 32cbc385 authored by Jeff Brown's avatar Jeff Brown
Browse files

Refactor InputQueue as InputEventReceiver.

This change simplifies the code associated with receiving input
events from input channels and makes it more robust.  It also
does a better job of ensuring that input events are properly
recycled (sometimes we dropped them on the floor).

This change also adds a sequence number to all events, which is
handy for determining whether we are looking at the same event or a
new one, particularly when events are recycled.

Change-Id: I4ebd88f73b5f77f3e150778cd550e7f91956aac2
parent db918cf1
Loading
Loading
Loading
Loading
+23 −14
Original line number Original line Diff line number Diff line
@@ -45,8 +45,7 @@ import android.view.IWindowSession;
import android.view.InputChannel;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputEvent;
import android.view.InputHandler;
import android.view.InputEventReceiver;
import android.view.InputQueue;
import android.view.MotionEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.View;
@@ -229,22 +228,27 @@ public abstract class WallpaperService extends Service {
            
            
        };
        };


        final InputHandler mInputHandler = new InputHandler() {
        final class WallpaperInputEventReceiver extends InputEventReceiver {
            public WallpaperInputEventReceiver(InputChannel inputChannel, Looper looper) {
                super(inputChannel, looper);
            }

            @Override
            @Override
            public void handleInputEvent(InputEvent event,
            public void onInputEvent(InputEvent event) {
                    InputQueue.FinishedCallback finishedCallback) {
                boolean handled = false;
                boolean handled = false;
                try {
                try {
                    if (event instanceof MotionEvent
                    if (event instanceof MotionEvent
                            && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                            && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                        dispatchPointer((MotionEvent)event);
                        MotionEvent dup = MotionEvent.obtainNoHistory((MotionEvent)event);
                        dispatchPointer(dup);
                        handled = true;
                        handled = true;
                    }
                    }
                } finally {
                } finally {
                    finishedCallback.finished(handled);
                    finishInputEvent(event, handled);
                }
                }
            }
            }
        };
        }
        WallpaperInputEventReceiver mInputEventReceiver;


        final BaseIWindow mWindow = new BaseIWindow() {
        final BaseIWindow mWindow = new BaseIWindow() {
            @Override
            @Override
@@ -534,6 +538,8 @@ public abstract class WallpaperService extends Service {
                }
                }
                Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);
                Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);
                mCaller.sendMessage(msg);
                mCaller.sendMessage(msg);
            } else {
                event.recycle();
            }
            }
        }
        }


@@ -599,8 +605,8 @@ public abstract class WallpaperService extends Service {
                        }
                        }
                        mCreated = true;
                        mCreated = true;


                        InputQueue.registerInputChannel(mInputChannel, mInputHandler,
                        mInputEventReceiver = new WallpaperInputEventReceiver(
                                Looper.myQueue());
                                mInputChannel, Looper.myLooper());
                    }
                    }
                    
                    
                    mSurfaceHolder.mSurfaceLock.lock();
                    mSurfaceHolder.mSurfaceLock.lock();
@@ -902,8 +908,9 @@ public abstract class WallpaperService extends Service {
                    if (DEBUG) Log.v(TAG, "Removing window and destroying surface "
                    if (DEBUG) Log.v(TAG, "Removing window and destroying surface "
                            + mSurfaceHolder.getSurface() + " of: " + this);
                            + mSurfaceHolder.getSurface() + " of: " + this);
                    
                    
                    if (mInputChannel != null) {
                    if (mInputEventReceiver != null) {
                        InputQueue.unregisterInputChannel(mInputChannel);
                        mInputEventReceiver.dispose();
                        mInputEventReceiver = null;
                    }
                    }
                    
                    
                    mSession.remove(mWindow);
                    mSession.remove(mWindow);
@@ -970,6 +977,8 @@ public abstract class WallpaperService extends Service {
        public void dispatchPointer(MotionEvent event) {
        public void dispatchPointer(MotionEvent event) {
            if (mEngine != null) {
            if (mEngine != null) {
                mEngine.dispatchPointer(event);
                mEngine.dispatchPointer(event);
            } else {
                event.recycle();
            }
            }
        }
        }


+55 −2
Original line number Original line Diff line number Diff line
@@ -19,6 +19,8 @@ package android.view;
import android.os.Parcel;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Parcelable;


import java.util.concurrent.atomic.AtomicInteger;

/**
/**
 * Common base class for input events.
 * Common base class for input events.
 */
 */
@@ -28,7 +30,20 @@ public abstract class InputEvent implements Parcelable {
    /** @hide */
    /** @hide */
    protected static final int PARCEL_TOKEN_KEY_EVENT = 2;
    protected static final int PARCEL_TOKEN_KEY_EVENT = 2;


    // Next sequence number.
    private static final AtomicInteger mNextSeq = new AtomicInteger();

    /** @hide */
    protected int mSeq;

    /** @hide */
    protected boolean mRecycled;

    private static final boolean TRACK_RECYCLED_LOCATION = false;
    private RuntimeException mRecycledLocation;

    /*package*/ InputEvent() {
    /*package*/ InputEvent() {
        mSeq = mNextSeq.getAndIncrement();
    }
    }


    /**
    /**
@@ -82,7 +97,29 @@ public abstract class InputEvent implements Parcelable {
     * objects are fine.  See {@link KeyEvent#recycle()} for details.
     * objects are fine.  See {@link KeyEvent#recycle()} for details.
     * @hide
     * @hide
     */
     */
    public abstract void recycle();
    public void recycle() {
        if (TRACK_RECYCLED_LOCATION) {
            if (mRecycledLocation != null) {
                throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
            }
            mRecycledLocation = new RuntimeException("Last recycled here");
        } else {
            if (mRecycled) {
                throw new RuntimeException(toString() + " recycled twice!");
            }
            mRecycled = true;
        }
    }

    /**
     * Reinitializes the event on reuse (after recycling).
     * @hide
     */
    protected void prepareForReuse() {
        mRecycled = false;
        mRecycledLocation = null;
        mSeq = mNextSeq.getAndIncrement();
    }


    /**
    /**
     * Gets a private flag that indicates when the system has detected that this input event
     * Gets a private flag that indicates when the system has detected that this input event
@@ -113,6 +150,22 @@ public abstract class InputEvent implements Parcelable {
     */
     */
    public abstract long getEventTimeNano();
    public abstract long getEventTimeNano();


    /**
     * Gets the unique sequence number of this event.
     * Every input event that is created or received by a process has a
     * unique sequence number.  Moreover, a new sequence number is obtained
     * each time an event object is recycled.
     *
     * Sequence numbers are only guaranteed to be locally unique within a process.
     * Sequence numbers are not preserved when events are parceled.
     *
     * @return The unique sequence number of this event.
     * @hide
     */
    public int getSequenceNumber() {
        return mSeq;
    }

    public int describeContents() {
    public int describeContents() {
        return 0;
        return 0;
    }
    }
+6 −5
Original line number Original line Diff line number Diff line
@@ -58,7 +58,7 @@ public final class InputEventConsistencyVerifier {
    // so that the verifier can detect when it has been asked to verify the same event twice.
    // so that the verifier can detect when it has been asked to verify the same event twice.
    // It does not make sense to examine the contents of the last event since it may have
    // It does not make sense to examine the contents of the last event since it may have
    // been recycled.
    // been recycled.
    private InputEvent mLastEvent;
    private int mLastEventSeq;
    private String mLastEventType;
    private String mLastEventType;
    private int mLastNestingLevel;
    private int mLastNestingLevel;


@@ -140,7 +140,7 @@ public final class InputEventConsistencyVerifier {
     * Resets the state of the input event consistency verifier.
     * Resets the state of the input event consistency verifier.
     */
     */
    public void reset() {
    public void reset() {
        mLastEvent = null;
        mLastEventSeq = -1;
        mLastNestingLevel = 0;
        mLastNestingLevel = 0;
        mTrackballDown = false;
        mTrackballDown = false;
        mTrackballUnhandled = false;
        mTrackballUnhandled = false;
@@ -573,17 +573,18 @@ public final class InputEventConsistencyVerifier {


    private boolean startEvent(InputEvent event, int nestingLevel, String eventType) {
    private boolean startEvent(InputEvent event, int nestingLevel, String eventType) {
        // Ignore the event if we already checked it at a higher nesting level.
        // Ignore the event if we already checked it at a higher nesting level.
        if (event == mLastEvent && nestingLevel < mLastNestingLevel
        final int seq = event.getSequenceNumber();
        if (seq == mLastEventSeq && nestingLevel < mLastNestingLevel
                && eventType == mLastEventType) {
                && eventType == mLastEventType) {
            return false;
            return false;
        }
        }


        if (nestingLevel > 0) {
        if (nestingLevel > 0) {
            mLastEvent = event;
            mLastEventSeq = seq;
            mLastEventType = eventType;
            mLastEventType = eventType;
            mLastNestingLevel = nestingLevel;
            mLastNestingLevel = nestingLevel;
        } else {
        } else {
            mLastEvent = null;
            mLastEventSeq = -1;
            mLastEventType = null;
            mLastEventType = null;
            mLastNestingLevel = 0;
            mLastNestingLevel = 0;
        }
        }
+151 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view;

import dalvik.system.CloseGuard;

import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;

/**
 * Provides a low-level mechanism for an application to receive input events.
 * @hide
 */
public abstract class InputEventReceiver {
    private static final String TAG = "InputEventReceiver";

    private final CloseGuard mCloseGuard = CloseGuard.get();

    private int mReceiverPtr;

    // We keep references to the input channel and message queue objects here so that
    // they are not GC'd while the native peer of the receiver is using them.
    private InputChannel mInputChannel;
    private MessageQueue mMessageQueue;

    // The sequence number of the event that is in progress.
    private int mEventSequenceNumberInProgress = -1;

    private static native int nativeInit(InputEventReceiver receiver,
            InputChannel inputChannel, MessageQueue messageQueue);
    private static native void nativeDispose(int receiverPtr);
    private static native void nativeFinishInputEvent(int receiverPtr, boolean handled);

    /**
     * Creates an input event receiver bound to the specified input channel.
     *
     * @param inputChannel The input channel.
     * @param looper The looper to use when invoking callbacks.
     */
    public InputEventReceiver(InputChannel inputChannel, Looper looper) {
        if (inputChannel == null) {
            throw new IllegalArgumentException("inputChannel must not be null");
        }
        if (looper == null) {
            throw new IllegalArgumentException("looper must not be null");
        }

        mInputChannel = inputChannel;
        mMessageQueue = looper.getQueue();
        mReceiverPtr = nativeInit(this, inputChannel, mMessageQueue);

        mCloseGuard.open("dispose");
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            dispose();
        } finally {
            super.finalize();
        }
    }

    /**
     * Disposes the receiver.
     */
    public void dispose() {
        if (mCloseGuard != null) {
            mCloseGuard.close();
        }
        if (mReceiverPtr != 0) {
            nativeDispose(mReceiverPtr);
            mReceiverPtr = 0;
        }
        mInputChannel = null;
        mMessageQueue = null;
    }

    /**
     * Called when an input event is received.
     * The recipient should process the input event and then call {@link #finishInputEvent}
     * to indicate whether the event was handled.  No new input events will be received
     * until {@link #finishInputEvent} is called.
     *
     * @param event The input event that was received.
     */
    public void onInputEvent(InputEvent event) {
        finishInputEvent(event, false);
    }

    /**
     * Finishes an input event and indicates whether it was handled.
     *
     * @param event The input event that was finished.
     * @param handled True if the event was handled.
     */
    public void finishInputEvent(InputEvent event, boolean handled) {
        if (event == null) {
            throw new IllegalArgumentException("event must not be null");
        }
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to finish an input event but the input event "
                    + "receiver has already been disposed.");
        } else {
            if (event.getSequenceNumber() != mEventSequenceNumberInProgress) {
                Log.w(TAG, "Attempted to finish an input event that is not in progress.");
            } else {
                mEventSequenceNumberInProgress = -1;
                nativeFinishInputEvent(mReceiverPtr, handled);
            }
        }
        recycleInputEvent(event);
    }

    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchInputEvent(InputEvent event) {
        mEventSequenceNumberInProgress = event.getSequenceNumber();
        onInputEvent(event);
    }

    private static void recycleInputEvent(InputEvent event) {
        if (event instanceof MotionEvent) {
            // Event though key events are also recyclable, we only recycle motion events.
            // Historically, key events were not recyclable and applications expect
            // them to be immutable.  We only ever recycle key events behind the
            // scenes where an application never sees them (so, not here).
            event.recycle();
        }
    }

    public static interface Factory {
        public InputEventReceiver createInputEventReceiver(
                InputChannel inputChannel, Looper looper);
    }
}
+0 −35
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view;

/**
 * Handles input messages that arrive on an input channel.
 * @hide
 */
public class InputHandler {
    /**
     * Handle an input event.
     * It is the responsibility of the callee to ensure that the finished callback is
     * eventually invoked when the event processing is finished and the input system
     * can send the next event.
     * @param event The input event.
     * @param finishedCallback The callback to invoke when event processing is finished.
     */
    public void handleInputEvent(InputEvent event, InputQueue.FinishedCallback finishedCallback) {
        finishedCallback.finished(false);
    }
}
Loading