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

Commit 5d019789 authored by Arthur Hung's avatar Arthur Hung
Browse files

Allow unbuffered MotionEvent dispatch for View (1/2)

MotionEvents sent from InputDispather would be buffered and dispatched
align the vsync by default. And it would provides many of benifits.

But for a high quality gaming experience, low latency input is critical
when we use analogs inputs (e.g mouse or joystick, etc.). So It's
important for gaming applications to process these input events in a
raw way, without them being coalesced on each frame.

- Add new api View.requestUnbufferedDispatch(source) to control which
  input source classes could be unbuffered while handled by the view.

Bug: 135740001
Bug: 136277595
Test: atest ViewUnbufferedTest
Change-Id: If65ed1906f59947dcd1e5062519b643a17d0e8e5
parent 10cab9d8
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -53891,6 +53891,7 @@ package android.view {
    method public boolean requestRectangleOnScreen(android.graphics.Rect);
    method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
    method public final void requestUnbufferedDispatch(android.view.MotionEvent);
    method public final void requestUnbufferedDispatch(int);
    method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int);
    method public void resetPivot();
    method public static int resolveSize(int, int);
+16 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.view;

import android.annotation.IntDef;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -28,6 +29,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.Vibrator;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

@@ -138,6 +141,19 @@ public final class InputDevice implements Parcelable {
     */
    public static final int SOURCE_CLASS_JOYSTICK = 0x00000010;

    /** @hide */
    @IntDef(flag = true, prefix = { "SOURCE_CLASS_" }, value = {
            SOURCE_CLASS_NONE,
            SOURCE_CLASS_BUTTON,
            SOURCE_CLASS_POINTER,
            SOURCE_CLASS_POINTER,
            SOURCE_CLASS_TRACKBALL,
            SOURCE_CLASS_POSITION,
            SOURCE_CLASS_JOYSTICK
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface InputSourceClass {}

    /**
     * The input source is unknown.
     */
+40 −1
Original line number Diff line number Diff line
@@ -110,6 +110,7 @@ import android.view.AccessibilityIterators.ParagraphTextSegmentIterator;
import android.view.AccessibilityIterators.TextSegmentIterator;
import android.view.AccessibilityIterators.WordTextSegmentIterator;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.InputDevice.InputSourceClass;
import android.view.Window.OnContentApplyWindowInsetsListener;
import android.view.WindowInsets.Type;
import android.view.WindowInsetsAnimation.Bounds;
@@ -5204,6 +5205,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    @StyleRes
    private int mExplicitStyle;
    /**
     * Specifies which input source classes should provide unbuffered input events to this view
     *
     * @see View#requestUnbufferedDispatch(int)
     */
    @InputSourceClass
    int mUnbufferedInputSource = InputDevice.SOURCE_CLASS_NONE;
    /**
     * Simple constructor to use when creating a view from code.
     *
@@ -8013,6 +8022,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            mAttachInfo.mKeyDispatchState.reset(this);
        }
        if (mParent != null) {
            mParent.onDescendantUnbufferedRequested();
        }
        notifyEnterOrExitForAutoFillIfNeeded(gainFocus);
    }
@@ -15820,12 +15833,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     * system not batch {@link MotionEvent}s but instead deliver them as soon as they're
     * available. This method should only be called for touch events.
     *
     * <p class="note">This api is not intended for most applications. Buffered dispatch
     * <p class="note">This API is not intended for most applications. Buffered dispatch
     * provides many of benefits, and just requesting unbuffered dispatch on most MotionEvent
     * streams will not improve your input latency. Side effects include: increased latency,
     * jittery scrolls and inability to take advantage of system resampling. Talk to your input
     * professional to see if {@link #requestUnbufferedDispatch(MotionEvent)} is right for
     * you.</p>
     *
     * To receive unbuffered events for arbitrary input device source classes, use
     * {@link #requestUnbufferedDispatch(int)},
     *
     * @see View#requestUnbufferedDispatch(int)
     */
    public final void requestUnbufferedDispatch(MotionEvent event) {
        final int action = event.getAction();
@@ -15837,6 +15855,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        mAttachInfo.mUnbufferedDispatchRequested = true;
    }
    /**
     * Request unbuffered dispatch of the given event source class to this view.
     * This is similar to {@link View#requestUnbufferedDispatch(MotionEvent)}, but does not
     * automatically terminate, and allows the specification of arbitrary input source classes.
     *
     * @param source The combined input source class to request unbuffered dispatch for. All
     *               events coming from these source classes will not be buffered. Set to
     *               {@link InputDevice#SOURCE_CLASS_NONE} in order to return to default behaviour.
     *
     * @see View#requestUnbufferedDispatch(MotionEvent)
     */
    public final void requestUnbufferedDispatch(@InputSourceClass int source) {
        if (mUnbufferedInputSource == source) {
            return;
        }
        mUnbufferedInputSource = source;
        if (mParent != null) {
            mParent.onDescendantUnbufferedRequested();
        }
    }
    private boolean hasSize() {
        return (mBottom > mTop) && (mRight > mLeft);
    }
+26 −0
Original line number Diff line number Diff line
@@ -9004,4 +9004,30 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            getChildAt(i).encode(encoder);
        }
    }

    /** @hide */
    @Override
    public final void onDescendantUnbufferedRequested() {
        // First look at the focused child for focused events
        int focusedChildNonPointerSource = InputDevice.SOURCE_CLASS_NONE;
        if (mFocused != null) {
            focusedChildNonPointerSource = mFocused.mUnbufferedInputSource
                    & (~InputDevice.SOURCE_CLASS_POINTER);
        }
        mUnbufferedInputSource = focusedChildNonPointerSource;

        // Request unbuffered dispatch for pointer events for this view if any child requested
        // unbuffered dispatch for pointer events. This is because we can't expect that the pointer
        // source would dispatch to the focused view.
        for (int i = 0; i < mChildrenCount; i++) {
            final View child = mChildren[i];
            if ((child.mUnbufferedInputSource & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                mUnbufferedInputSource |= InputDevice.SOURCE_CLASS_POINTER;
                break;
            }
        }
        if (mParent != null) {
            mParent.onDescendantUnbufferedRequested();
        }
    }
}
+14 −0
Original line number Diff line number Diff line
@@ -675,4 +675,18 @@ public interface ViewParent {
     */
    default void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
    }

    /**
     * Unbuffered dispatch has been requested by a child of this view parent.
     * This method is called by the View hierarchy to signal ancestors that a View needs to
     * request unbuffered dispatch.
     *
     * @see View#requestUnbufferedDispatch(int)
     * @hide
     */
    default void onDescendantUnbufferedRequested() {
        if (getParent() != null) {
            getParent().onDescendantUnbufferedRequested();
        }
    }
}
Loading