Merge "Send input timeline from app to InputDispatcher" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-03-31 10:11:49 +00:00
committed by Android (Google) Code Review
6 changed files with 177 additions and 33 deletions

View File

@@ -54,6 +54,8 @@ public abstract class InputEventReceiver {
InputChannel inputChannel, MessageQueue messageQueue); InputChannel inputChannel, MessageQueue messageQueue);
private static native void nativeDispose(long receiverPtr); private static native void nativeDispose(long receiverPtr);
private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled); 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, private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr,
long frameTimeNanos); long frameTimeNanos);
private static native String nativeDump(long receiverPtr, String prefix); 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) { public final void reportTimeline(int inputEventId, long gpuCompletedTime, long presentTime) {
Trace.traceBegin(Trace.TRACE_TAG_INPUT, "reportLatencyInfo"); Trace.traceBegin(Trace.TRACE_TAG_INPUT, "reportTimeline");
// TODO(b/169866723) : send this data to InputDispatcher via InputChannel nativeReportTimeline(mReceiverPtr, inputEventId, gpuCompletedTime, presentTime);
Trace.traceEnd(Trace.TRACE_TAG_INPUT); Trace.traceEnd(Trace.TRACE_TAG_INPUT);
} }

View File

@@ -111,6 +111,16 @@ public abstract class InputEventSender {
public void onInputEventFinished(int seq, boolean handled) { public void onInputEventFinished(int seq, boolean handled) {
} }
/**
* 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. * Sends an input event.
* Must be called on the same Looper thread to which the sender is attached. * 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) { private void dispatchInputEventFinished(int seq, boolean handled) {
onInputEventFinished(seq, handled); onInputEventFinished(seq, handled);
} }
// Called from native code.
@SuppressWarnings("unused")
private void dispatchTimelineReported(
int inputEventId, long gpuCompletedTime, long presentTime) {
onTimelineReported(inputEventId, gpuCompletedTime, presentTime);
}
} }

View File

@@ -8601,7 +8601,7 @@ public final class ViewRootImpl implements ViewParent,
return; return;
} }
final long gpuCompletedTime = data[FrameMetrics.Index.GPU_COMPLETED]; final long gpuCompletedTime = data[FrameMetrics.Index.GPU_COMPLETED];
mReceiver.reportLatencyInfo(inputEventId, gpuCompletedTime, presentTime); mReceiver.reportTimeline(inputEventId, gpuCompletedTime, presentTime);
} }
} }
HardwareRendererObserver mHardwareRendererObserver; HardwareRendererObserver mHardwareRendererObserver;

View File

@@ -27,6 +27,7 @@
#include <input/InputTransport.h> #include <input/InputTransport.h>
#include <log/log.h> #include <log/log.h>
#include <utils/Looper.h> #include <utils/Looper.h>
#include <variant>
#include <vector> #include <vector>
#include "android_os_MessageQueue.h" #include "android_os_MessageQueue.h"
#include "android_view_InputChannel.h" #include "android_view_InputChannel.h"
@@ -80,6 +81,7 @@ public:
status_t initialize(); status_t initialize();
void dispose(); void dispose();
status_t finishInputEvent(uint32_t seq, bool handled); 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, status_t consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime,
bool* outConsumedBatch); bool* outConsumedBatch);
std::string dump(const char* prefix); std::string dump(const char* prefix);
@@ -93,13 +95,19 @@ private:
bool handled; bool handled;
}; };
struct Timeline {
int32_t inputEventId;
std::array<nsecs_t, GraphicsTimeline::SIZE> timeline;
};
typedef std::variant<Finish, Timeline> OutboundEvent;
jobject mReceiverWeakGlobal; jobject mReceiverWeakGlobal;
InputConsumer mInputConsumer; InputConsumer mInputConsumer;
sp<MessageQueue> mMessageQueue; sp<MessageQueue> mMessageQueue;
PreallocatedInputEventFactory mInputEventFactory; PreallocatedInputEventFactory mInputEventFactory;
bool mBatchedInputEventPending; bool mBatchedInputEventPending;
int mFdEvents; int mFdEvents;
std::vector<Finish> mFinishQueue; std::vector<OutboundEvent> mOutboundQueue;
void setFdEvents(int events); void setFdEvents(int events);
@@ -152,7 +160,23 @@ status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled)
.seq = seq, .seq = seq,
.handled = handled, .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(); 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 * 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 * 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 * 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. * InputPublisher are 'outbound / outgoing' events.
* *
* NativeInputEventReceiver owns (and acts like) an InputConsumer. So the finish events are outbound * 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 * InputPublisher. If no events are remaining, let the looper know so that it doesn't wake up
* unnecessarily. * unnecessarily.
*/ */
status_t NativeInputEventReceiver::processOutboundEvents() { status_t NativeInputEventReceiver::processOutboundEvents() {
while (!mFinishQueue.empty()) { while (!mOutboundQueue.empty()) {
const Finish& finish = *mFinishQueue.begin(); OutboundEvent& outbound = *mOutboundQueue.begin();
status_t status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled); 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) { if (status == OK) {
// Successful send. Erase the entry and keep trying to send more // Successful send. Erase the entry and keep trying to send more
mFinishQueue.erase(mFinishQueue.begin()); mOutboundQueue.erase(mOutboundQueue.begin());
continue; continue;
} }
@@ -198,7 +234,7 @@ status_t NativeInputEventReceiver::processOutboundEvents() {
if (status == WOULD_BLOCK) { if (status == WOULD_BLOCK) {
if (kDebugDispatchCycle) { if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Remaining outbound events: %zu.", ALOGD("channel '%s' ~ Remaining outbound events: %zu.",
getInputChannelName().c_str(), mFinishQueue.size()); getInputChannelName().c_str(), mOutboundQueue.size());
} }
setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT); setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT);
return WOULD_BLOCK; // try again later return WOULD_BLOCK; // try again later
@@ -425,12 +461,23 @@ std::string NativeInputEventReceiver::dump(const char* prefix) {
out += android::base::StringPrintf("mBatchedInputEventPending: %s\n", out += android::base::StringPrintf("mBatchedInputEventPending: %s\n",
toString(mBatchedInputEventPending)); toString(mBatchedInputEventPending));
out = out + "mFinishQueue:\n"; out = out + "mOutboundQueue:\n";
for (const Finish& finish : mFinishQueue) { for (const OutboundEvent& outbound : mOutboundQueue) {
out += android::base::StringPrintf(" seq=%" PRIu32 " handled=%s\n", finish.seq, 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)); 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"; out = out + " <empty>\n";
} }
return addPrefix(out, prefix); 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, static jboolean nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jlong receiverPtr,
jlong frameTimeNanos) { jlong frameTimeNanos) {
sp<NativeInputEventReceiver> receiver = sp<NativeInputEventReceiver> receiver =
@@ -520,6 +586,7 @@ static const JNINativeMethod gMethods[] = {
(void*)nativeInit}, (void*)nativeInit},
{"nativeDispose", "(J)V", (void*)nativeDispose}, {"nativeDispose", "(J)V", (void*)nativeDispose},
{"nativeFinishInputEvent", "(JIZ)V", (void*)nativeFinishInputEvent}, {"nativeFinishInputEvent", "(JIZ)V", (void*)nativeFinishInputEvent},
{"nativeReportTimeline", "(JIJJ)V", (void*)nativeReportTimeline},
{"nativeConsumeBatchedInputEvents", "(JJ)Z", (void*)nativeConsumeBatchedInputEvents}, {"nativeConsumeBatchedInputEvents", "(JJ)Z", (void*)nativeConsumeBatchedInputEvents},
{"nativeDump", "(JLjava/lang/String;)Ljava/lang/String;", (void*)nativeDump}, {"nativeDump", "(JLjava/lang/String;)Ljava/lang/String;", (void*)nativeDump},
}; };

View File

@@ -45,6 +45,7 @@ static struct {
jclass clazz; jclass clazz;
jmethodID dispatchInputEventFinished; jmethodID dispatchInputEventFinished;
jmethodID dispatchTimelineReported;
} gInputEventSenderClassInfo; } gInputEventSenderClassInfo;
@@ -75,8 +76,9 @@ private:
} }
int handleEvent(int receiveFd, int events, void* data) override; int handleEvent(int receiveFd, int events, void* data) override;
status_t receiveFinishedSignals(JNIEnv* env); status_t processConsumerResponse(JNIEnv* env);
bool notifyFinishedSignal(JNIEnv* env, jobject sender, const InputPublisher::Finished& finished, bool notifyConsumerResponse(JNIEnv* env, jobject sender,
const InputPublisher::ConsumerResponse& response,
bool skipCallbacks); bool skipCallbacks);
}; };
@@ -188,12 +190,12 @@ int NativeInputEventSender::handleEvent(int receiveFd, int events, void* data) {
} }
JNIEnv* env = AndroidRuntime::getJNIEnv(); JNIEnv* env = AndroidRuntime::getJNIEnv();
status_t status = receiveFinishedSignals(env); status_t status = processConsumerResponse(env);
mMessageQueue->raiseAndClearException(env, "handleReceiveCallback"); mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
return status == OK || status == NO_MEMORY ? 1 : 0; return status == OK || status == NO_MEMORY ? 1 : 0;
} }
status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { status_t NativeInputEventSender::processConsumerResponse(JNIEnv* env) {
if (kDebugDispatchCycle) { if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Receiving finished signals.", getInputChannelName().c_str()); 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 bool skipCallbacks = false; // stop calling Java functions after an exception occurs
for (;;) { for (;;) {
Result<InputPublisher::Finished> result = mInputPublisher.receiveFinishedSignal(); Result<InputPublisher::ConsumerResponse> result = mInputPublisher.receiveConsumerResponse();
if (!result.ok()) { if (!result.ok()) {
const status_t status = result.error().code(); const status_t status = result.error().code();
if (status == WOULD_BLOCK) { if (status == WOULD_BLOCK) {
return OK; 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); getInputChannelName().c_str(), status);
return status; return status;
} }
const bool notified = notifyFinishedSignal(env, senderObj.get(), *result, skipCallbacks); const bool notified = notifyConsumerResponse(env, senderObj.get(), *result, skipCallbacks);
if (!notified) { if (!notified) {
skipCallbacks = true; skipCallbacks = true;
} }
@@ -225,16 +227,49 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) {
} }
/** /**
* Invoke the Java function dispatchInputEventFinished for the received "Finished" signal. * Invoke the corresponding Java function for the different variants of response.
* Set the variable 'skipCallbacks' to 'true' if a Java exception occurred. * 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'. * Java function will only be called if 'skipCallbacks' is originally 'false'.
* *
* Return "false" if an exception occurred while calling the Java function * Return "false" if an exception occurred while calling the Java function
* "true" otherwise * "true" otherwise
*/ */
bool NativeInputEventSender::notifyFinishedSignal(JNIEnv* env, jobject sender, bool NativeInputEventSender::notifyConsumerResponse(
const InputPublisher::Finished& finished, JNIEnv* env, jobject sender, const InputPublisher::ConsumerResponse& response,
bool skipCallbacks) { 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); auto it = mPublishedSeqMap.find(finished.seq);
if (it == mPublishedSeqMap.end()) { if (it == mPublishedSeqMap.end()) {
ALOGW("Received 'finished' signal for unknown seq number = %" PRIu32, finished.seq); 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( gInputEventSenderClassInfo.dispatchInputEventFinished = GetMethodIDOrDie(
env, gInputEventSenderClassInfo.clazz, "dispatchInputEventFinished", "(IZ)V"); env, gInputEventSenderClassInfo.clazz, "dispatchInputEventFinished", "(IZ)V");
gInputEventSenderClassInfo.dispatchTimelineReported =
GetMethodIDOrDie(env, gInputEventSenderClassInfo.clazz, "dispatchTimelineReported",
"(IJJ)V");
return res; return res;
} }

View File

@@ -75,16 +75,26 @@ class TestInputEventReceiver(channel: InputChannel, looper: Looper) :
class TestInputEventSender(channel: InputChannel, looper: Looper) : class TestInputEventSender(channel: InputChannel, looper: Looper) :
InputEventSender(channel, looper) { InputEventSender(channel, looper) {
data class FinishedSignal(val seq: Int, val handled: Boolean) 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 mFinishedSignals = LinkedBlockingQueue<FinishedSignal>()
private val mTimelines = LinkedBlockingQueue<Timeline>()
override fun onInputEventFinished(seq: Int, handled: Boolean) { override fun onInputEventFinished(seq: Int, handled: Boolean) {
mFinishedSignals.put(FinishedSignal(seq, handled)) mFinishedSignals.put(FinishedSignal(seq, handled))
} }
override fun onTimelineReported(inputEventId: Int, gpuCompletedTime: Long, presentTime: Long) {
mTimelines.put(Timeline(inputEventId, gpuCompletedTime, presentTime))
}
fun getFinishedSignal(): FinishedSignal { fun getFinishedSignal(): FinishedSignal {
return getEvent(mFinishedSignals) return getEvent(mFinishedSignals)
} }
fun getTimeline(): Timeline {
return getEvent(mTimelines)
}
} }
class InputEventSenderAndReceiverTest { class InputEventSenderAndReceiverTest {
@@ -125,4 +135,14 @@ class InputEventSenderAndReceiverTest {
// Check sender // Check sender
assertEquals(TestInputEventSender.FinishedSignal(seq, handled = true), finishedSignal) 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)
}
} }