summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2021-03-31 10:11:49 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2021-03-31 10:11:49 +0000
commitb2ee6ea14d8e83da632c63e229a587941b5e4355 (patch)
treeeb530c7365b800a33113b1e7295291784aa85485
parent91aea8ebc846af08d71466946339ae6d9425cdd7 (diff)
parentf4d7318b17fbee00ddf290d17659525c418fe1d2 (diff)
Merge "Send input timeline from app to InputDispatcher" into sc-dev
-rw-r--r--core/java/android/view/InputEventReceiver.java10
-rw-r--r--core/java/android/view/InputEventSender.java17
-rw-r--r--core/java/android/view/ViewRootImpl.java2
-rw-r--r--core/jni/android_view_InputEventReceiver.cpp97
-rw-r--r--core/jni/android_view_InputEventSender.cpp64
-rw-r--r--tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt20
6 files changed, 177 insertions, 33 deletions
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 5e0579d8a672..25dda5b2e0bb 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -54,6 +54,8 @@ 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 void nativeReportTimeline(long receiverPtr, int inputEventId,
+ long gpuCompletedTime, long presentTime);
private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr,
long frameTimeNanos);
private static native String nativeDump(long receiverPtr, String prefix);
@@ -209,11 +211,11 @@ public abstract class InputEventReceiver {
}
/**
- * Report the latency information for a specific input event.
+ * Report the timing / latency information for a specific input event.
*/
- public final void reportLatencyInfo(int inputEventId, long gpuCompletedTime, long presentTime) {
- Trace.traceBegin(Trace.TRACE_TAG_INPUT, "reportLatencyInfo");
- // TODO(b/169866723) : send this data to InputDispatcher via InputChannel
+ public final void reportTimeline(int inputEventId, long gpuCompletedTime, long presentTime) {
+ Trace.traceBegin(Trace.TRACE_TAG_INPUT, "reportTimeline");
+ nativeReportTimeline(mReceiverPtr, inputEventId, gpuCompletedTime, presentTime);
Trace.traceEnd(Trace.TRACE_TAG_INPUT);
}
diff --git a/core/java/android/view/InputEventSender.java b/core/java/android/view/InputEventSender.java
index 40eb438262a1..d14421897860 100644
--- a/core/java/android/view/InputEventSender.java
+++ b/core/java/android/view/InputEventSender.java
@@ -112,6 +112,16 @@ public abstract class InputEventSender {
}
/**
+ * Called when timeline is sent to the publisher.
+ *
+ * @param inputEventId The id of the input event that caused the frame being reported
+ * @param gpuCompletedTime The time when the frame left the app process
+ * @param presentTime The time when the frame was presented on screen
+ */
+ public void onTimelineReported(int inputEventId, long gpuCompletedTime, long presentTime) {
+ }
+
+ /**
* Sends an input event.
* Must be called on the same Looper thread to which the sender is attached.
*
@@ -143,4 +153,11 @@ public abstract class InputEventSender {
private void dispatchInputEventFinished(int seq, boolean handled) {
onInputEventFinished(seq, handled);
}
+
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchTimelineReported(
+ int inputEventId, long gpuCompletedTime, long presentTime) {
+ onTimelineReported(inputEventId, gpuCompletedTime, presentTime);
+ }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c3770f0ef478..e2cf9624e1a5 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -8601,7 +8601,7 @@ public final class ViewRootImpl implements ViewParent,
return;
}
final long gpuCompletedTime = data[FrameMetrics.Index.GPU_COMPLETED];
- mReceiver.reportLatencyInfo(inputEventId, gpuCompletedTime, presentTime);
+ mReceiver.reportTimeline(inputEventId, gpuCompletedTime, presentTime);
}
}
HardwareRendererObserver mHardwareRendererObserver;
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index bfeb01d22bdf..44597cc4c578 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -27,6 +27,7 @@
#include <input/InputTransport.h>
#include <log/log.h>
#include <utils/Looper.h>
+#include <variant>
#include <vector>
#include "android_os_MessageQueue.h"
#include "android_view_InputChannel.h"
@@ -80,6 +81,7 @@ public:
status_t initialize();
void dispose();
status_t finishInputEvent(uint32_t seq, bool handled);
+ status_t reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime);
status_t consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime,
bool* outConsumedBatch);
std::string dump(const char* prefix);
@@ -93,13 +95,19 @@ private:
bool handled;
};
+ struct Timeline {
+ int32_t inputEventId;
+ std::array<nsecs_t, GraphicsTimeline::SIZE> timeline;
+ };
+ typedef std::variant<Finish, Timeline> OutboundEvent;
+
jobject mReceiverWeakGlobal;
InputConsumer mInputConsumer;
sp<MessageQueue> mMessageQueue;
PreallocatedInputEventFactory mInputEventFactory;
bool mBatchedInputEventPending;
int mFdEvents;
- std::vector<Finish> mFinishQueue;
+ std::vector<OutboundEvent> mOutboundQueue;
void setFdEvents(int events);
@@ -152,7 +160,23 @@ status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled)
.seq = seq,
.handled = handled,
};
- mFinishQueue.push_back(finish);
+ mOutboundQueue.push_back(finish);
+ return processOutboundEvents();
+}
+
+status_t NativeInputEventReceiver::reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime,
+ nsecs_t presentTime) {
+ if (kDebugDispatchCycle) {
+ ALOGD("channel '%s' ~ %s", getInputChannelName().c_str(), __func__);
+ }
+ std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
+ graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = gpuCompletedTime;
+ graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = presentTime;
+ Timeline timeline{
+ .inputEventId = inputEventId,
+ .timeline = graphicsTimeline,
+ };
+ mOutboundQueue.push_back(timeline);
return processOutboundEvents();
}
@@ -170,7 +194,7 @@ void NativeInputEventReceiver::setFdEvents(int events) {
/**
* Receiver's primary role is to receive input events, but it has an additional duty of sending
- * 'ack' for events (using the call 'finishInputEvent').
+ * 'ack' for events (using the call 'finishInputEvent') and reporting input event timeline.
*
* If we are looking at the communication between InputPublisher and InputConsumer, we can say that
* from the InputConsumer's perspective, InputMessage's that are sent from publisher to consumer are
@@ -178,19 +202,31 @@ void NativeInputEventReceiver::setFdEvents(int events) {
* InputPublisher are 'outbound / outgoing' events.
*
* NativeInputEventReceiver owns (and acts like) an InputConsumer. So the finish events are outbound
- * from InputEventReceiver (and will be sent to the InputPublisher).
+ * from InputEventReceiver (and will be sent to the InputPublisher). Likewise, timeline events are
+ * outbound events.
*
- * In this function, send as many events from 'mFinishQueue' as possible across the socket to the
+ * In this function, send as many events from 'mOutboundQueue' as possible across the socket to the
* InputPublisher. If no events are remaining, let the looper know so that it doesn't wake up
* unnecessarily.
*/
status_t NativeInputEventReceiver::processOutboundEvents() {
- while (!mFinishQueue.empty()) {
- const Finish& finish = *mFinishQueue.begin();
- status_t status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled);
+ while (!mOutboundQueue.empty()) {
+ OutboundEvent& outbound = *mOutboundQueue.begin();
+ status_t status;
+
+ if (std::holds_alternative<Finish>(outbound)) {
+ const Finish& finish = std::get<Finish>(outbound);
+ status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled);
+ } else if (std::holds_alternative<Timeline>(outbound)) {
+ const Timeline& timeline = std::get<Timeline>(outbound);
+ status = mInputConsumer.sendTimeline(timeline.inputEventId, timeline.timeline);
+ } else {
+ LOG_ALWAYS_FATAL("Unexpected event type in std::variant");
+ status = BAD_VALUE;
+ }
if (status == OK) {
// Successful send. Erase the entry and keep trying to send more
- mFinishQueue.erase(mFinishQueue.begin());
+ mOutboundQueue.erase(mOutboundQueue.begin());
continue;
}
@@ -198,7 +234,7 @@ status_t NativeInputEventReceiver::processOutboundEvents() {
if (status == WOULD_BLOCK) {
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Remaining outbound events: %zu.",
- getInputChannelName().c_str(), mFinishQueue.size());
+ getInputChannelName().c_str(), mOutboundQueue.size());
}
setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT);
return WOULD_BLOCK; // try again later
@@ -425,12 +461,23 @@ std::string NativeInputEventReceiver::dump(const char* prefix) {
out += android::base::StringPrintf("mBatchedInputEventPending: %s\n",
toString(mBatchedInputEventPending));
- out = out + "mFinishQueue:\n";
- for (const Finish& finish : mFinishQueue) {
- out += android::base::StringPrintf(" seq=%" PRIu32 " handled=%s\n", finish.seq,
- toString(finish.handled));
+ out = out + "mOutboundQueue:\n";
+ for (const OutboundEvent& outbound : mOutboundQueue) {
+ if (std::holds_alternative<Finish>(outbound)) {
+ const Finish& finish = std::get<Finish>(outbound);
+ out += android::base::StringPrintf(" Finish: seq=%" PRIu32 " handled=%s\n", finish.seq,
+ toString(finish.handled));
+ } else if (std::holds_alternative<Timeline>(outbound)) {
+ const Timeline& timeline = std::get<Timeline>(outbound);
+ out += android::base::
+ StringPrintf(" Timeline: inputEventId=%" PRId32 " gpuCompletedTime=%" PRId64
+ ", presentTime=%" PRId64 "\n",
+ timeline.inputEventId,
+ timeline.timeline[GraphicsTimeline::GPU_COMPLETED_TIME],
+ timeline.timeline[GraphicsTimeline::PRESENT_TIME]);
+ }
}
- if (mFinishQueue.empty()) {
+ if (mOutboundQueue.empty()) {
out = out + " <empty>\n";
}
return addPrefix(out, prefix);
@@ -489,6 +536,25 @@ static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jlong receiverPtr,
}
}
+static void nativeReportTimeline(JNIEnv* env, jclass clazz, jlong receiverPtr, jint inputEventId,
+ jlong gpuCompletedTime, jlong presentTime) {
+ if (IdGenerator::getSource(inputEventId) != IdGenerator::Source::INPUT_READER) {
+ // skip this event, it did not originate from hardware
+ return;
+ }
+ sp<NativeInputEventReceiver> receiver =
+ reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
+ status_t status = receiver->reportTimeline(inputEventId, gpuCompletedTime, presentTime);
+ if (status == OK || status == WOULD_BLOCK) {
+ return; // normal operation
+ }
+ if (status != DEAD_OBJECT) {
+ std::string message = android::base::StringPrintf("Failed to send timeline. status=%s(%d)",
+ strerror(-status), status);
+ jniThrowRuntimeException(env, message.c_str());
+ }
+}
+
static jboolean nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jlong receiverPtr,
jlong frameTimeNanos) {
sp<NativeInputEventReceiver> receiver =
@@ -520,6 +586,7 @@ static const JNINativeMethod gMethods[] = {
(void*)nativeInit},
{"nativeDispose", "(J)V", (void*)nativeDispose},
{"nativeFinishInputEvent", "(JIZ)V", (void*)nativeFinishInputEvent},
+ {"nativeReportTimeline", "(JIJJ)V", (void*)nativeReportTimeline},
{"nativeConsumeBatchedInputEvents", "(JJ)Z", (void*)nativeConsumeBatchedInputEvents},
{"nativeDump", "(JLjava/lang/String;)Ljava/lang/String;", (void*)nativeDump},
};
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 10927b9e566e..fd1b9981e8af 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -45,6 +45,7 @@ static struct {
jclass clazz;
jmethodID dispatchInputEventFinished;
+ jmethodID dispatchTimelineReported;
} gInputEventSenderClassInfo;
@@ -75,9 +76,10 @@ private:
}
int handleEvent(int receiveFd, int events, void* data) override;
- status_t receiveFinishedSignals(JNIEnv* env);
- bool notifyFinishedSignal(JNIEnv* env, jobject sender, const InputPublisher::Finished& finished,
- bool skipCallbacks);
+ status_t processConsumerResponse(JNIEnv* env);
+ bool notifyConsumerResponse(JNIEnv* env, jobject sender,
+ const InputPublisher::ConsumerResponse& response,
+ bool skipCallbacks);
};
NativeInputEventSender::NativeInputEventSender(JNIEnv* env, jobject senderWeak,
@@ -188,12 +190,12 @@ int NativeInputEventSender::handleEvent(int receiveFd, int events, void* data) {
}
JNIEnv* env = AndroidRuntime::getJNIEnv();
- status_t status = receiveFinishedSignals(env);
+ status_t status = processConsumerResponse(env);
mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
return status == OK || status == NO_MEMORY ? 1 : 0;
}
-status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) {
+status_t NativeInputEventSender::processConsumerResponse(JNIEnv* env) {
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Receiving finished signals.", getInputChannelName().c_str());
}
@@ -206,18 +208,18 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) {
}
bool skipCallbacks = false; // stop calling Java functions after an exception occurs
for (;;) {
- Result<InputPublisher::Finished> result = mInputPublisher.receiveFinishedSignal();
+ Result<InputPublisher::ConsumerResponse> result = mInputPublisher.receiveConsumerResponse();
if (!result.ok()) {
const status_t status = result.error().code();
if (status == WOULD_BLOCK) {
return OK;
}
- ALOGE("channel '%s' ~ Failed to consume finished signals. status=%d",
+ ALOGE("channel '%s' ~ Failed to process consumer response. status=%d",
getInputChannelName().c_str(), status);
return status;
}
- const bool notified = notifyFinishedSignal(env, senderObj.get(), *result, skipCallbacks);
+ const bool notified = notifyConsumerResponse(env, senderObj.get(), *result, skipCallbacks);
if (!notified) {
skipCallbacks = true;
}
@@ -225,16 +227,49 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) {
}
/**
- * Invoke the Java function dispatchInputEventFinished for the received "Finished" signal.
- * Set the variable 'skipCallbacks' to 'true' if a Java exception occurred.
+ * Invoke the corresponding Java function for the different variants of response.
+ * If the response is a Finished object, invoke dispatchInputEventFinished.
+ * If the response is a Timeline object, invoke dispatchTimelineReported.
+ * Set 'skipCallbacks' to 'true' if a Java exception occurred.
* Java function will only be called if 'skipCallbacks' is originally 'false'.
*
* Return "false" if an exception occurred while calling the Java function
* "true" otherwise
*/
-bool NativeInputEventSender::notifyFinishedSignal(JNIEnv* env, jobject sender,
- const InputPublisher::Finished& finished,
- bool skipCallbacks) {
+bool NativeInputEventSender::notifyConsumerResponse(
+ JNIEnv* env, jobject sender, const InputPublisher::ConsumerResponse& response,
+ bool skipCallbacks) {
+ if (std::holds_alternative<InputPublisher::Timeline>(response)) {
+ const InputPublisher::Timeline& timeline = std::get<InputPublisher::Timeline>(response);
+
+ if (kDebugDispatchCycle) {
+ ALOGD("channel '%s' ~ Received timeline, inputEventId=%" PRId32
+ ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
+ getInputChannelName().c_str(), timeline.inputEventId,
+ timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
+ timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+ }
+
+ if (skipCallbacks) {
+ ALOGW("Java exception occurred. Skipping dispatchTimelineReported for "
+ "inputEventId=%" PRId32,
+ timeline.inputEventId);
+ return true;
+ }
+
+ env->CallVoidMethod(sender, gInputEventSenderClassInfo.dispatchTimelineReported,
+ timeline.inputEventId, timeline.graphicsTimeline);
+ if (env->ExceptionCheck()) {
+ ALOGE("Exception dispatching timeline, inputEventId=%" PRId32, timeline.inputEventId);
+ return false;
+ }
+
+ return true;
+ }
+
+ // Must be a Finished event
+ const InputPublisher::Finished& finished = std::get<InputPublisher::Finished>(response);
+
auto it = mPublishedSeqMap.find(finished.seq);
if (it == mPublishedSeqMap.end()) {
ALOGW("Received 'finished' signal for unknown seq number = %" PRIu32, finished.seq);
@@ -340,6 +375,9 @@ int register_android_view_InputEventSender(JNIEnv* env) {
gInputEventSenderClassInfo.dispatchInputEventFinished = GetMethodIDOrDie(
env, gInputEventSenderClassInfo.clazz, "dispatchInputEventFinished", "(IZ)V");
+ gInputEventSenderClassInfo.dispatchTimelineReported =
+ GetMethodIDOrDie(env, gInputEventSenderClassInfo.clazz, "dispatchTimelineReported",
+ "(IJJ)V");
return res;
}
diff --git a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
index b134fe737d05..fc1d83960351 100644
--- a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
+++ b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
@@ -75,16 +75,26 @@ class TestInputEventReceiver(channel: InputChannel, looper: Looper) :
class TestInputEventSender(channel: InputChannel, looper: Looper) :
InputEventSender(channel, looper) {
data class FinishedSignal(val seq: Int, val handled: Boolean)
+ data class Timeline(val inputEventId: Int, val gpuCompletedTime: Long, val presentTime: Long)
private val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>()
+ private val mTimelines = LinkedBlockingQueue<Timeline>()
override fun onInputEventFinished(seq: Int, handled: Boolean) {
mFinishedSignals.put(FinishedSignal(seq, handled))
}
+ override fun onTimelineReported(inputEventId: Int, gpuCompletedTime: Long, presentTime: Long) {
+ mTimelines.put(Timeline(inputEventId, gpuCompletedTime, presentTime))
+ }
+
fun getFinishedSignal(): FinishedSignal {
return getEvent(mFinishedSignals)
}
+
+ fun getTimeline(): Timeline {
+ return getEvent(mTimelines)
+ }
}
class InputEventSenderAndReceiverTest {
@@ -125,4 +135,14 @@ class InputEventSenderAndReceiverTest {
// Check sender
assertEquals(TestInputEventSender.FinishedSignal(seq, handled = true), finishedSignal)
}
+
+ // The timeline case is slightly unusual because it goes from InputConsumer to InputPublisher.
+ @Test
+ fun testSendAndReceiveTimeline() {
+ val sent = TestInputEventSender.Timeline(
+ inputEventId = 1, gpuCompletedTime = 2, presentTime = 3)
+ mReceiver.reportTimeline(sent.inputEventId, sent.gpuCompletedTime, sent.presentTime)
+ val received = mSender.getTimeline()
+ assertEquals(sent, received)
+ }
}