Merge "Send input timeline from app to InputDispatcher" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
b2ee6ea14d
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,16 @@ public abstract class InputEventSender {
|
||||
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.
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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},
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user