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

Commit f9d97b54 authored by Egor Pasko's avatar Egor Pasko
Browse files

Introduce View#probablyHasInput

Exposes a hidden (behind @hide) method in android.view.View backed by
InputConsumer::probablyHasInput() from frameworks/native introduced by
ag/25438533.

More specifically: `boolean View#possiblyHasInput()`.

Usage constraints:

0. This is experimental. The usefulness of the hint has not been
   broadly proven, and the functionality can be removed soon if there is
   no robust data to support the usefulness.

1. The high-level API visible to apps would be effectively opt-in: does
   not create any state if not used.

2. An app could use this hint to yield from longer tasks on the UI
   thread (which it is not supposed to create in the first place). This
   is a very niche workaround for Chrome's complexity.

3. Using the Input Hint in WebView should probably be disabled: WebView
   has less control over the embedding app than Chrome, and less
   knowledge when to prioritize for input handling.

4. There is a good question whether probablyHasInput() should guarantee
   to return true only when input is queued. While it is good to return
   precise results, clients should only use this API as an optimization
   hint without sacrificing correctness. Due to the growing complexity
   of input handling on Android, it is not too hard to imagine that in
   some not-too-distant future input would get filtered by the Framework
   on the application side, potentially skipping the contents from the
   ready-to-read file descriptor or an accumulated InputConsumer batch.

Bug: b/314973388
Test: Tested with a custom build of Chrome, it returns true sometimes.
Change-Id: I874767fcea9a0f31ad510919599af50cf33c7980
parent c4f7678f
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ public abstract class InputEventReceiver {
            InputChannel inputChannel, MessageQueue messageQueue);
    private static native void nativeDispose(long receiverPtr);
    private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled);
    private static native boolean nativeProbablyHasInput(long receiverPtr);
    private static native void nativeReportTimeline(long receiverPtr, int inputEventId,
            long gpuCompletedTime, long presentTime);
    private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr,
@@ -91,6 +92,17 @@ public abstract class InputEventReceiver {
        }
    }

    /**
     * Checks the receiver for input availability.
     * May return false negatives.
     */
    public boolean probablyHasInput() {
        if (mReceiverPtr == 0) {
            return false;
        }
        return nativeProbablyHasInput(mReceiverPtr);
    }

    /**
     * Disposes the receiver.
     * Must be called on the same Looper thread to which the receiver is attached.
+30 −0
Original line number Diff line number Diff line
@@ -22782,6 +22782,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        }
    }
    /**
     * Determines whether an unprocessed input event is available on the window.
     *
     * This is only a performance hint (a.k.a. the Input Hint) and may return false negative
     * results.  Callers should not rely on availability of the input event based on the return
     * value of this method.
     *
     * The Input Hint functionality is experimental, and can be removed in the future OS releases.
     *
     * This method only returns nontrivial results on a View that is attached to a Window. Such View
     * can be acquired using `Activity.getWindow().getDecorView()`, and only after the view
     * hierarchy is attached (via {@link android.app.Activity#setContentView(android.view.View)}).
     *
     * In multi-window mode the View can provide the Input Hint only for the window it is attached
     * to. Therefore, checking input availability for the whole application would require asking
     * for the hint from more than one View.
     *
     * The initial implementation does not return false positives, but callers should not rely on
     * it: false positives may occur in future OS releases.
     *
     * @hide
     */
    public boolean probablyHasInput() {
        ViewRootImpl viewRootImpl = getViewRootImpl();
        if (viewRootImpl == null) {
            return false;
        }
        return viewRootImpl.probablyHasInput();
    }
    /**
     * Destroys all hardware rendering resources. This method is invoked
     * when the system needs to reclaim resources. Upon execution of this
+12 −0
Original line number Diff line number Diff line
@@ -10316,6 +10316,18 @@ public final class ViewRootImpl implements ViewParent,
        return false;
    }

    /**
     * Checks the input event receiver for input availability.
     * May return false negatives.
     * @hide
     */
    public boolean probablyHasInput() {
        if (mInputEventReceiver == null) {
            return false;
        }
        return mInputEventReceiver.probablyHasInput();
    }

    /**
     * Adds a scroll capture callback to this window.
     *
+12 −0
Original line number Diff line number Diff line
@@ -82,6 +82,7 @@ public:
    status_t initialize();
    void dispose();
    status_t finishInputEvent(uint32_t seq, bool handled);
    bool probablyHasInput();
    status_t reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime);
    status_t consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime,
            bool* outConsumedBatch);
@@ -165,6 +166,10 @@ status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled)
    return processOutboundEvents();
}

bool NativeInputEventReceiver::probablyHasInput() {
    return mInputConsumer.probablyHasInput();
}

status_t NativeInputEventReceiver::reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime,
                                                  nsecs_t presentTime) {
    if (kDebugDispatchCycle) {
@@ -547,6 +552,12 @@ static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jlong receiverPtr,
    }
}

static bool nativeProbablyHasInput(JNIEnv* env, jclass clazz, jlong receiverPtr) {
    sp<NativeInputEventReceiver> receiver =
            reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
    return receiver->probablyHasInput();
}

static void nativeReportTimeline(JNIEnv* env, jclass clazz, jlong receiverPtr, jint inputEventId,
                                 jlong gpuCompletedTime, jlong presentTime) {
    if (IdGenerator::getSource(inputEventId) != IdGenerator::Source::INPUT_READER) {
@@ -597,6 +608,7 @@ static const JNINativeMethod gMethods[] = {
         (void*)nativeInit},
        {"nativeDispose", "(J)V", (void*)nativeDispose},
        {"nativeFinishInputEvent", "(JIZ)V", (void*)nativeFinishInputEvent},
        {"nativeProbablyHasInput", "(J)Z", (void*)nativeProbablyHasInput},
        {"nativeReportTimeline", "(JIJJ)V", (void*)nativeReportTimeline},
        {"nativeConsumeBatchedInputEvents", "(JJ)Z", (void*)nativeConsumeBatchedInputEvents},
        {"nativeDump", "(JLjava/lang/String;)Ljava/lang/String;", (void*)nativeDump},