Merge "Add basic ANR test" into rvc-dev
This commit is contained in:
commit
0979ea52c1
7 changed files with 274 additions and 71 deletions
|
@ -61,6 +61,11 @@ public:
|
|||
return mInfo.token ? mInfo.dispatchingTimeout : defaultValue;
|
||||
}
|
||||
|
||||
inline std::chrono::nanoseconds getDispatchingTimeout(
|
||||
std::chrono::nanoseconds defaultValue) const {
|
||||
return mInfo.token ? std::chrono::nanoseconds(mInfo.dispatchingTimeout) : defaultValue;
|
||||
}
|
||||
|
||||
inline sp<IBinder> getApplicationToken() const {
|
||||
return mInfo.token;
|
||||
}
|
||||
|
|
|
@ -222,6 +222,11 @@ public:
|
|||
return mInfo.token ? mInfo.dispatchingTimeout : defaultValue;
|
||||
}
|
||||
|
||||
inline std::chrono::nanoseconds getDispatchingTimeout(
|
||||
std::chrono::nanoseconds defaultValue) const {
|
||||
return mInfo.token ? std::chrono::nanoseconds(mInfo.dispatchingTimeout) : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the state of this object be updated to reflect
|
||||
* the most current available information about the application.
|
||||
|
|
|
@ -102,6 +102,12 @@ EventEntry::~EventEntry() {
|
|||
releaseInjectionState();
|
||||
}
|
||||
|
||||
std::string EventEntry::getDescription() const {
|
||||
std::string result;
|
||||
appendDescription(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void EventEntry::release() {
|
||||
refCount -= 1;
|
||||
if (refCount == 0) {
|
||||
|
|
|
@ -83,6 +83,8 @@ struct EventEntry {
|
|||
|
||||
virtual void appendDescription(std::string& msg) const = 0;
|
||||
|
||||
std::string getDescription() const;
|
||||
|
||||
protected:
|
||||
EventEntry(int32_t id, Type type, nsecs_t eventTime, uint32_t policyFlags);
|
||||
virtual ~EventEntry();
|
||||
|
|
|
@ -78,7 +78,7 @@ namespace android::inputdispatcher {
|
|||
|
||||
// Default input dispatching timeout if there is no focused application or paused window
|
||||
// from which to determine an appropriate dispatching timeout.
|
||||
constexpr nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec
|
||||
constexpr std::chrono::nanoseconds DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5s;
|
||||
|
||||
// Amount of time to allow for all pending events to be processed when an app switch
|
||||
// key is on the way. This is used to preempt input dispatch and drop input events
|
||||
|
@ -1295,11 +1295,9 @@ int32_t InputDispatcher::handleTargetsNotReadyLocked(
|
|||
}
|
||||
} else {
|
||||
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
|
||||
if (DEBUG_FOCUS) {
|
||||
ALOGD("Waiting for application to become ready for input: %s. Reason: %s",
|
||||
getApplicationWindowLabel(applicationHandle, windowHandle).c_str(), reason);
|
||||
}
|
||||
nsecs_t timeout;
|
||||
ALOGI("Waiting for application to become ready for input: %s. Reason: %s",
|
||||
getApplicationWindowLabel(applicationHandle, windowHandle).c_str(), reason);
|
||||
std::chrono::nanoseconds timeout;
|
||||
if (windowHandle != nullptr) {
|
||||
timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
|
||||
} else if (applicationHandle != nullptr) {
|
||||
|
@ -1311,7 +1309,7 @@ int32_t InputDispatcher::handleTargetsNotReadyLocked(
|
|||
|
||||
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
|
||||
mInputTargetWaitStartTime = currentTime;
|
||||
mInputTargetWaitTimeoutTime = currentTime + timeout;
|
||||
mInputTargetWaitTimeoutTime = currentTime + timeout.count();
|
||||
mInputTargetWaitTimeoutExpired = false;
|
||||
mInputTargetWaitApplicationToken.clear();
|
||||
|
||||
|
@ -1353,10 +1351,10 @@ void InputDispatcher::removeWindowByTokenLocked(const sp<IBinder>& token) {
|
|||
}
|
||||
|
||||
void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(
|
||||
nsecs_t newTimeout, const sp<IBinder>& inputConnectionToken) {
|
||||
if (newTimeout > 0) {
|
||||
nsecs_t timeoutExtension, const sp<IBinder>& inputConnectionToken) {
|
||||
if (timeoutExtension > 0) {
|
||||
// Extend the timeout.
|
||||
mInputTargetWaitTimeoutTime = now() + newTimeout;
|
||||
mInputTargetWaitTimeoutTime = now() + timeoutExtension;
|
||||
} else {
|
||||
// Give up.
|
||||
mInputTargetWaitTimeoutExpired = true;
|
||||
|
@ -4048,11 +4046,12 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
|
|||
const int32_t displayId = it.first;
|
||||
const sp<InputApplicationHandle>& applicationHandle = it.second;
|
||||
dump += StringPrintf(INDENT2 "displayId=%" PRId32
|
||||
", name='%s', dispatchingTimeout=%0.3fms\n",
|
||||
", name='%s', dispatchingTimeout=%" PRId64 "ms\n",
|
||||
displayId, applicationHandle->getName().c_str(),
|
||||
applicationHandle->getDispatchingTimeout(
|
||||
DEFAULT_INPUT_DISPATCHING_TIMEOUT) /
|
||||
1000000.0);
|
||||
ns2ms(applicationHandle
|
||||
->getDispatchingTimeout(
|
||||
DEFAULT_INPUT_DISPATCHING_TIMEOUT)
|
||||
.count()));
|
||||
}
|
||||
} else {
|
||||
dump += StringPrintf(INDENT "FocusedApplications: <none>\n");
|
||||
|
@ -4132,9 +4131,10 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
|
|||
windowInfo->windowXScale, windowInfo->windowYScale);
|
||||
dumpRegion(dump, windowInfo->touchableRegion);
|
||||
dump += StringPrintf(", inputFeatures=0x%08x", windowInfo->inputFeatures);
|
||||
dump += StringPrintf(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
|
||||
dump += StringPrintf(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%" PRId64
|
||||
"ms\n",
|
||||
windowInfo->ownerPid, windowInfo->ownerUid,
|
||||
windowInfo->dispatchingTimeout / 1000000.0);
|
||||
ns2ms(windowInfo->dispatchingTimeout));
|
||||
}
|
||||
} else {
|
||||
dump += INDENT2 "Windows: <none>\n";
|
||||
|
@ -4167,7 +4167,7 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
|
|||
for (EventEntry* entry : mRecentQueue) {
|
||||
dump += INDENT2;
|
||||
entry->appendDescription(dump);
|
||||
dump += StringPrintf(", age=%0.1fms\n", (currentTime - entry->eventTime) * 0.000001f);
|
||||
dump += StringPrintf(", age=%" PRId64 "ms\n", ns2ms(currentTime - entry->eventTime));
|
||||
}
|
||||
} else {
|
||||
dump += INDENT "RecentQueue: <empty>\n";
|
||||
|
@ -4178,8 +4178,8 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
|
|||
dump += INDENT "PendingEvent:\n";
|
||||
dump += INDENT2;
|
||||
mPendingEvent->appendDescription(dump);
|
||||
dump += StringPrintf(", age=%0.1fms\n",
|
||||
(currentTime - mPendingEvent->eventTime) * 0.000001f);
|
||||
dump += StringPrintf(", age=%" PRId64 "ms\n",
|
||||
ns2ms(currentTime - mPendingEvent->eventTime));
|
||||
} else {
|
||||
dump += INDENT "PendingEvent: <none>\n";
|
||||
}
|
||||
|
@ -4190,7 +4190,7 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
|
|||
for (EventEntry* entry : mInboundQueue) {
|
||||
dump += INDENT2;
|
||||
entry->appendDescription(dump);
|
||||
dump += StringPrintf(", age=%0.1fms\n", (currentTime - entry->eventTime) * 0.000001f);
|
||||
dump += StringPrintf(", age=%" PRId64 "ms\n", ns2ms(currentTime - entry->eventTime));
|
||||
}
|
||||
} else {
|
||||
dump += INDENT "InboundQueue: <empty>\n";
|
||||
|
@ -4225,9 +4225,10 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
|
|||
for (DispatchEntry* entry : connection->outboundQueue) {
|
||||
dump.append(INDENT4);
|
||||
entry->eventEntry->appendDescription(dump);
|
||||
dump += StringPrintf(", targetFlags=0x%08x, resolvedAction=%d, age=%0.1fms\n",
|
||||
dump += StringPrintf(", targetFlags=0x%08x, resolvedAction=%d, age=%" PRId64
|
||||
"ms\n",
|
||||
entry->targetFlags, entry->resolvedAction,
|
||||
(currentTime - entry->eventEntry->eventTime) * 0.000001f);
|
||||
ns2ms(currentTime - entry->eventEntry->eventTime));
|
||||
}
|
||||
} else {
|
||||
dump += INDENT3 "OutboundQueue: <empty>\n";
|
||||
|
@ -4240,10 +4241,10 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
|
|||
dump += INDENT4;
|
||||
entry->eventEntry->appendDescription(dump);
|
||||
dump += StringPrintf(", targetFlags=0x%08x, resolvedAction=%d, "
|
||||
"age=%0.1fms, wait=%0.1fms\n",
|
||||
"age=%" PRId64 "ms, wait=%" PRId64 "ms\n",
|
||||
entry->targetFlags, entry->resolvedAction,
|
||||
(currentTime - entry->eventEntry->eventTime) * 0.000001f,
|
||||
(currentTime - entry->deliveryTime) * 0.000001f);
|
||||
ns2ms(currentTime - entry->eventEntry->eventTime),
|
||||
ns2ms(currentTime - entry->deliveryTime));
|
||||
}
|
||||
} else {
|
||||
dump += INDENT3 "WaitQueue: <empty>\n";
|
||||
|
@ -4254,16 +4255,16 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
|
|||
}
|
||||
|
||||
if (isAppSwitchPendingLocked()) {
|
||||
dump += StringPrintf(INDENT "AppSwitch: pending, due in %0.1fms\n",
|
||||
(mAppSwitchDueTime - now()) / 1000000.0);
|
||||
dump += StringPrintf(INDENT "AppSwitch: pending, due in %" PRId64 "ms\n",
|
||||
ns2ms(mAppSwitchDueTime - now()));
|
||||
} else {
|
||||
dump += INDENT "AppSwitch: not pending\n";
|
||||
}
|
||||
|
||||
dump += INDENT "Configuration:\n";
|
||||
dump += StringPrintf(INDENT2 "KeyRepeatDelay: %0.1fms\n", mConfig.keyRepeatDelay * 0.000001f);
|
||||
dump += StringPrintf(INDENT2 "KeyRepeatTimeout: %0.1fms\n",
|
||||
mConfig.keyRepeatTimeout * 0.000001f);
|
||||
dump += StringPrintf(INDENT2 "KeyRepeatDelay: %" PRId64 "ms\n", ns2ms(mConfig.keyRepeatDelay));
|
||||
dump += StringPrintf(INDENT2 "KeyRepeatTimeout: %" PRId64 "ms\n",
|
||||
ns2ms(mConfig.keyRepeatTimeout));
|
||||
}
|
||||
|
||||
void InputDispatcher::dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) {
|
||||
|
@ -4365,8 +4366,7 @@ status_t InputDispatcher::unregisterInputChannelLocked(const sp<InputChannel>& i
|
|||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
[[maybe_unused]] const bool removed = removeByValue(mConnectionsByFd, connection);
|
||||
ALOG_ASSERT(removed);
|
||||
removeConnectionLocked(connection);
|
||||
mInputChannelsByToken.erase(inputChannel->getConnectionToken());
|
||||
|
||||
if (connection->monitor) {
|
||||
|
@ -4468,7 +4468,7 @@ std::optional<int32_t> InputDispatcher::findGestureMonitorDisplayByTokenLocked(
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
sp<Connection> InputDispatcher::getConnectionLocked(const sp<IBinder>& inputConnectionToken) {
|
||||
sp<Connection> InputDispatcher::getConnectionLocked(const sp<IBinder>& inputConnectionToken) const {
|
||||
if (inputConnectionToken == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -4483,6 +4483,10 @@ sp<Connection> InputDispatcher::getConnectionLocked(const sp<IBinder>& inputConn
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void InputDispatcher::removeConnectionLocked(const sp<Connection>& connection) {
|
||||
removeByValue(mConnectionsByFd, connection);
|
||||
}
|
||||
|
||||
void InputDispatcher::onDispatchCycleFinishedLocked(nsecs_t currentTime,
|
||||
const sp<Connection>& connection, uint32_t seq,
|
||||
bool handled) {
|
||||
|
@ -4587,12 +4591,12 @@ void InputDispatcher::doNotifyAnrLockedInterruptible(CommandEntry* commandEntry)
|
|||
commandEntry->inputChannel ? commandEntry->inputChannel->getConnectionToken() : nullptr;
|
||||
mLock.unlock();
|
||||
|
||||
nsecs_t newTimeout =
|
||||
const nsecs_t timeoutExtension =
|
||||
mPolicy->notifyAnr(commandEntry->inputApplicationHandle, token, commandEntry->reason);
|
||||
|
||||
mLock.lock();
|
||||
|
||||
resumeAfterTargetsNotReadyTimeoutLocked(newTimeout, token);
|
||||
resumeAfterTargetsNotReadyTimeoutLocked(timeoutExtension, token);
|
||||
}
|
||||
|
||||
void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
|
||||
|
@ -4647,11 +4651,8 @@ void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(CommandEntry* c
|
|||
|
||||
const nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
|
||||
if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
|
||||
std::string msg =
|
||||
StringPrintf("Window '%s' spent %0.1fms processing the last input event: ",
|
||||
connection->getWindowName().c_str(), eventDuration * 0.000001f);
|
||||
dispatchEntry->eventEntry->appendDescription(msg);
|
||||
ALOGI("%s", msg.c_str());
|
||||
ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
|
||||
ns2ms(eventDuration), dispatchEntry->eventEntry->getDescription().c_str());
|
||||
}
|
||||
reportDispatchStatistics(std::chrono::nanoseconds(eventDuration), *connection, handled);
|
||||
|
||||
|
|
|
@ -197,6 +197,11 @@ private:
|
|||
// All registered connections mapped by channel file descriptor.
|
||||
std::unordered_map<int, sp<Connection>> mConnectionsByFd GUARDED_BY(mLock);
|
||||
|
||||
sp<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
|
||||
REQUIRES(mLock);
|
||||
|
||||
void removeConnectionLocked(const sp<Connection>& connection) REQUIRES(mLock);
|
||||
|
||||
struct IBinderHash {
|
||||
std::size_t operator()(const sp<IBinder>& b) const {
|
||||
return std::hash<IBinder*>{}(b.get());
|
||||
|
@ -209,7 +214,6 @@ private:
|
|||
std::optional<int32_t> findGestureMonitorDisplayByTokenLocked(const sp<IBinder>& token)
|
||||
REQUIRES(mLock);
|
||||
|
||||
sp<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) REQUIRES(mLock);
|
||||
|
||||
// Input channels that will receive a copy of all input events sent to the provided display.
|
||||
std::unordered_map<int32_t, std::vector<Monitor>> mGlobalMonitorsByDisplay GUARDED_BY(mLock);
|
||||
|
|
|
@ -17,12 +17,14 @@
|
|||
#include "../dispatcher/InputDispatcher.h"
|
||||
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/thread_annotations.h>
|
||||
#include <binder/Binder.h>
|
||||
#include <input/Input.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <linux/input.h>
|
||||
#include <cinttypes>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
|
@ -119,6 +121,33 @@ public:
|
|||
<< "Expected onPointerDownOutsideFocus to not have been called";
|
||||
}
|
||||
|
||||
// This function must be called soon after the expected ANR timer starts,
|
||||
// because we are also checking how much time has passed.
|
||||
void assertNotifyAnrWasCalled(std::chrono::nanoseconds timeout,
|
||||
const sp<InputApplicationHandle>& expectedApplication,
|
||||
const sp<IBinder>& expectedToken) {
|
||||
const std::chrono::time_point start = std::chrono::steady_clock::now();
|
||||
std::unique_lock lock(mLock);
|
||||
std::chrono::duration timeToWait = timeout + 100ms; // provide some slack
|
||||
android::base::ScopedLockAssertion assumeLocked(mLock);
|
||||
|
||||
// If there is an ANR, Dispatcher won't be idle because there are still events
|
||||
// in the waitQueue that we need to check on. So we can't wait for dispatcher to be idle
|
||||
// before checking if ANR was called.
|
||||
// Since dispatcher is not guaranteed to call notifyAnr right away, we need to provide
|
||||
// it some time to act. 100ms seems reasonable.
|
||||
mNotifyAnr.wait_for(lock, timeToWait,
|
||||
[this]() REQUIRES(mLock) { return mNotifyAnrWasCalled; });
|
||||
const std::chrono::duration waited = std::chrono::steady_clock::now() - start;
|
||||
ASSERT_TRUE(mNotifyAnrWasCalled);
|
||||
// Ensure that the ANR didn't get raised too early. We can't be too strict here because
|
||||
// the dispatcher started counting before this function was called
|
||||
ASSERT_TRUE(timeout - 100ms < waited); // check (waited < timeout + 100ms) done by wait_for
|
||||
mNotifyAnrWasCalled = false;
|
||||
ASSERT_EQ(expectedApplication, mLastAnrApplication);
|
||||
ASSERT_EQ(expectedToken, mLastAnrWindowToken);
|
||||
}
|
||||
|
||||
void setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) {
|
||||
mConfig.keyRepeatTimeout = timeout;
|
||||
mConfig.keyRepeatDelay = delay;
|
||||
|
@ -131,14 +160,26 @@ private:
|
|||
sp<IBinder> mOnPointerDownToken GUARDED_BY(mLock);
|
||||
std::optional<NotifySwitchArgs> mLastNotifySwitch GUARDED_BY(mLock);
|
||||
|
||||
// ANR handling
|
||||
bool mNotifyAnrWasCalled GUARDED_BY(mLock) = false;
|
||||
sp<InputApplicationHandle> mLastAnrApplication GUARDED_BY(mLock);
|
||||
sp<IBinder> mLastAnrWindowToken GUARDED_BY(mLock);
|
||||
std::condition_variable mNotifyAnr;
|
||||
std::chrono::nanoseconds mAnrTimeout = 0ms;
|
||||
|
||||
virtual void notifyConfigurationChanged(nsecs_t when) override {
|
||||
std::scoped_lock lock(mLock);
|
||||
mConfigurationChangedTime = when;
|
||||
}
|
||||
|
||||
virtual nsecs_t notifyAnr(const sp<InputApplicationHandle>&, const sp<IBinder>&,
|
||||
const std::string&) override {
|
||||
return 0;
|
||||
virtual nsecs_t notifyAnr(const sp<InputApplicationHandle>& application,
|
||||
const sp<IBinder>& windowToken, const std::string&) override {
|
||||
std::scoped_lock lock(mLock);
|
||||
mLastAnrApplication = application;
|
||||
mLastAnrWindowToken = windowToken;
|
||||
mNotifyAnrWasCalled = true;
|
||||
mNotifyAnr.notify_all();
|
||||
return mAnrTimeout.count();
|
||||
}
|
||||
|
||||
virtual void notifyInputChannelBroken(const sp<IBinder>&) override {}
|
||||
|
@ -309,6 +350,20 @@ protected:
|
|||
mFakePolicy.clear();
|
||||
mDispatcher.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for debugging when writing the test
|
||||
*/
|
||||
void dumpDispatcherState() {
|
||||
std::string dump;
|
||||
mDispatcher->dump(dump);
|
||||
std::stringstream ss(dump);
|
||||
std::string to;
|
||||
|
||||
while (std::getline(ss, to, '\n')) {
|
||||
ALOGE("%s", to.c_str());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -502,13 +557,20 @@ static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 5s;
|
|||
|
||||
class FakeApplicationHandle : public InputApplicationHandle {
|
||||
public:
|
||||
FakeApplicationHandle() {}
|
||||
FakeApplicationHandle() {
|
||||
mInfo.name = "Fake Application";
|
||||
mInfo.token = new BBinder();
|
||||
mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT.count();
|
||||
}
|
||||
virtual ~FakeApplicationHandle() {}
|
||||
|
||||
virtual bool updateInfo() override {
|
||||
mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT.count();
|
||||
return true;
|
||||
}
|
||||
|
||||
void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
|
||||
mInfo.dispatchingTimeout = timeout.count();
|
||||
}
|
||||
};
|
||||
|
||||
class FakeInputReceiver {
|
||||
|
@ -519,6 +581,20 @@ public:
|
|||
}
|
||||
|
||||
InputEvent* consume() {
|
||||
InputEvent* event;
|
||||
std::optional<uint32_t> consumeSeq = receiveEvent(&event);
|
||||
if (!consumeSeq) {
|
||||
return nullptr;
|
||||
}
|
||||
finishEvent(*consumeSeq);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive an event without acknowledging it.
|
||||
* Return the sequence number that could later be used to send finished signal.
|
||||
*/
|
||||
std::optional<uint32_t> receiveEvent(InputEvent** outEvent = nullptr) {
|
||||
uint32_t consumeSeq;
|
||||
InputEvent* event;
|
||||
|
||||
|
@ -535,23 +611,29 @@ public:
|
|||
|
||||
if (status == WOULD_BLOCK) {
|
||||
// Just means there's no event available.
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (status != OK) {
|
||||
ADD_FAILURE() << mName.c_str() << ": consumer consume should return OK.";
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
}
|
||||
if (event == nullptr) {
|
||||
ADD_FAILURE() << "Consumed correctly, but received NULL event from consumer";
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
}
|
||||
if (outEvent != nullptr) {
|
||||
*outEvent = event;
|
||||
}
|
||||
return consumeSeq;
|
||||
}
|
||||
|
||||
status = mConsumer->sendFinishedSignal(consumeSeq, true);
|
||||
if (status != OK) {
|
||||
ADD_FAILURE() << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
|
||||
}
|
||||
return event;
|
||||
/**
|
||||
* To be used together with "receiveEvent" to complete the consumption of an event.
|
||||
*/
|
||||
void finishEvent(uint32_t consumeSeq) {
|
||||
const status_t status = mConsumer->sendFinishedSignal(consumeSeq, true);
|
||||
ASSERT_EQ(OK, status) << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
|
||||
}
|
||||
|
||||
void consumeEvent(int32_t expectedEventType, int32_t expectedAction, int32_t expectedDisplayId,
|
||||
|
@ -668,6 +750,10 @@ public:
|
|||
|
||||
void setFocus(bool hasFocus) { mInfo.hasFocus = hasFocus; }
|
||||
|
||||
void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
|
||||
mInfo.dispatchingTimeout = timeout.count();
|
||||
}
|
||||
|
||||
void setFrame(const Rect& frame) {
|
||||
mInfo.frameLeft = frame.left;
|
||||
mInfo.frameTop = frame.top;
|
||||
|
@ -740,6 +826,19 @@ public:
|
|||
expectedFlags);
|
||||
}
|
||||
|
||||
std::optional<uint32_t> receiveEvent() {
|
||||
if (mInputReceiver == nullptr) {
|
||||
ADD_FAILURE() << "Invalid receive event on window with no receiver";
|
||||
return std::nullopt;
|
||||
}
|
||||
return mInputReceiver->receiveEvent();
|
||||
}
|
||||
|
||||
void finishEvent(uint32_t sequenceNum) {
|
||||
ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver";
|
||||
mInputReceiver->finishEvent(sequenceNum);
|
||||
}
|
||||
|
||||
InputEvent* consume() {
|
||||
if (mInputReceiver == nullptr) {
|
||||
return nullptr;
|
||||
|
@ -765,16 +864,15 @@ private:
|
|||
|
||||
std::atomic<int32_t> FakeWindowHandle::sId{1};
|
||||
|
||||
static int32_t injectKeyDown(const sp<InputDispatcher>& dispatcher,
|
||||
int32_t displayId = ADISPLAY_ID_NONE) {
|
||||
static int32_t injectKey(const sp<InputDispatcher>& dispatcher, int32_t action, int32_t repeatCount,
|
||||
int32_t displayId = ADISPLAY_ID_NONE) {
|
||||
KeyEvent event;
|
||||
nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
|
||||
|
||||
// Define a valid key down event.
|
||||
event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, displayId,
|
||||
INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /* flags */ 0, AKEYCODE_A, KEY_A,
|
||||
AMETA_NONE,
|
||||
/* repeatCount */ 0, currentTime, currentTime);
|
||||
INVALID_HMAC, action, /* flags */ 0, AKEYCODE_A, KEY_A, AMETA_NONE,
|
||||
repeatCount, currentTime, currentTime);
|
||||
|
||||
// Inject event until dispatch out.
|
||||
return dispatcher->injectInputEvent(
|
||||
|
@ -783,10 +881,16 @@ static int32_t injectKeyDown(const sp<InputDispatcher>& dispatcher,
|
|||
INJECT_EVENT_TIMEOUT, POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
|
||||
}
|
||||
|
||||
static int32_t injectMotionEvent(const sp<InputDispatcher>& dispatcher, int32_t action,
|
||||
int32_t source, int32_t displayId, int32_t x, int32_t y,
|
||||
int32_t xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION,
|
||||
int32_t yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION) {
|
||||
static int32_t injectKeyDown(const sp<InputDispatcher>& dispatcher,
|
||||
int32_t displayId = ADISPLAY_ID_NONE) {
|
||||
return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 0, displayId);
|
||||
}
|
||||
|
||||
static int32_t injectMotionEvent(
|
||||
const sp<InputDispatcher>& dispatcher, int32_t action, int32_t source, int32_t displayId,
|
||||
const PointF& position,
|
||||
const PointF& cursorPosition = {AMOTION_EVENT_INVALID_CURSOR_POSITION,
|
||||
AMOTION_EVENT_INVALID_CURSOR_POSITION}) {
|
||||
MotionEvent event;
|
||||
PointerProperties pointerProperties[1];
|
||||
PointerCoords pointerCoords[1];
|
||||
|
@ -796,8 +900,8 @@ static int32_t injectMotionEvent(const sp<InputDispatcher>& dispatcher, int32_t
|
|||
pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
|
||||
|
||||
pointerCoords[0].clear();
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, position.x);
|
||||
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, position.y);
|
||||
|
||||
nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
|
||||
// Define a valid motion down event.
|
||||
|
@ -806,7 +910,7 @@ static int32_t injectMotionEvent(const sp<InputDispatcher>& dispatcher, int32_t
|
|||
/* flags */ 0,
|
||||
/* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
|
||||
/* xScale */ 1, /* yScale */ 1, /* xOffset */ 0, /* yOffset */ 0,
|
||||
/* xPrecision */ 0, /* yPrecision */ 0, xCursorPosition, yCursorPosition,
|
||||
/* xPrecision */ 0, /* yPrecision */ 0, cursorPosition.x, cursorPosition.y,
|
||||
currentTime, currentTime,
|
||||
/*pointerCount*/ 1, pointerProperties, pointerCoords);
|
||||
|
||||
|
@ -819,14 +923,12 @@ static int32_t injectMotionEvent(const sp<InputDispatcher>& dispatcher, int32_t
|
|||
|
||||
static int32_t injectMotionDown(const sp<InputDispatcher>& dispatcher, int32_t source,
|
||||
int32_t displayId, const PointF& location = {100, 200}) {
|
||||
return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_DOWN, source, displayId, location.x,
|
||||
location.y);
|
||||
return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_DOWN, source, displayId, location);
|
||||
}
|
||||
|
||||
static int32_t injectMotionUp(const sp<InputDispatcher>& dispatcher, int32_t source,
|
||||
int32_t displayId, const PointF& location = {100, 200}) {
|
||||
return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_UP, source, displayId, location.x,
|
||||
location.y);
|
||||
return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_UP, source, displayId, location);
|
||||
}
|
||||
|
||||
static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLAY_ID_NONE) {
|
||||
|
@ -1051,7 +1153,7 @@ TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) {
|
|||
// left window. This event should be dispatched to the left window.
|
||||
ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
|
||||
injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE,
|
||||
ADISPLAY_ID_DEFAULT, 610, 400, 599, 400));
|
||||
ADISPLAY_ID_DEFAULT, {610, 400}, {599, 400}));
|
||||
windowLeft->consumeMotionDown(ADISPLAY_ID_DEFAULT);
|
||||
windowRight->assertNoEvents();
|
||||
}
|
||||
|
@ -2185,4 +2287,82 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleWindowsFirstTouchWithSc
|
|||
consumeMotionEvent(mWindow1, AMOTION_EVENT_ACTION_MOVE, expectedPoints);
|
||||
}
|
||||
|
||||
class InputDispatcherSingleWindowAnr : public InputDispatcherTest {
|
||||
virtual void SetUp() override {
|
||||
InputDispatcherTest::SetUp();
|
||||
|
||||
mApplication = new FakeApplicationHandle();
|
||||
mApplication->setDispatchingTimeout(20ms);
|
||||
mWindow =
|
||||
new FakeWindowHandle(mApplication, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
|
||||
mWindow->setFrame(Rect(0, 0, 30, 30));
|
||||
mWindow->setDispatchingTimeout(10ms);
|
||||
mWindow->setFocus(true);
|
||||
// Adding FLAG_NOT_TOUCH_MODAL to ensure taps outside this window are not sent to this
|
||||
// window.
|
||||
mWindow->setLayoutParamFlags(InputWindowInfo::FLAG_NOT_TOUCH_MODAL);
|
||||
|
||||
// Set focused application.
|
||||
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
|
||||
|
||||
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
|
||||
mWindow->consumeFocusEvent(true);
|
||||
}
|
||||
|
||||
virtual void TearDown() override {
|
||||
InputDispatcherTest::TearDown();
|
||||
mWindow.clear();
|
||||
}
|
||||
|
||||
protected:
|
||||
sp<FakeApplicationHandle> mApplication;
|
||||
sp<FakeWindowHandle> mWindow;
|
||||
static constexpr PointF WINDOW_LOCATION = {20, 20};
|
||||
|
||||
void tapOnWindow() {
|
||||
ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
|
||||
injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
|
||||
WINDOW_LOCATION));
|
||||
ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
|
||||
injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
|
||||
WINDOW_LOCATION));
|
||||
}
|
||||
};
|
||||
|
||||
// Send an event to the app and have the app not respond right away.
|
||||
// Make sure that ANR is raised
|
||||
TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) {
|
||||
ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
|
||||
injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
|
||||
WINDOW_LOCATION));
|
||||
|
||||
// Also, overwhelm the socket to make sure ANR starts
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
|
||||
ADISPLAY_ID_DEFAULT, {WINDOW_LOCATION.x, WINDOW_LOCATION.y + i});
|
||||
}
|
||||
|
||||
std::optional<uint32_t> sequenceNum = mWindow->receiveEvent(); // ACTION_DOWN
|
||||
ASSERT_TRUE(sequenceNum);
|
||||
const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
|
||||
mFakePolicy->assertNotifyAnrWasCalled(timeout, nullptr /*application*/, mWindow->getToken());
|
||||
ASSERT_TRUE(mDispatcher->waitForIdle());
|
||||
}
|
||||
|
||||
// Send a key to the app and have the app not respond right away.
|
||||
TEST_F(InputDispatcherSingleWindowAnr, OnKeyDown_BasicAnr) {
|
||||
// Inject a key, and don't respond - expect that ANR is called.
|
||||
ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectKeyDown(mDispatcher));
|
||||
std::optional<uint32_t> sequenceNum = mWindow->receiveEvent();
|
||||
ASSERT_TRUE(sequenceNum);
|
||||
|
||||
// Start ANR process by sending a 2nd key, which would trigger the check for whether
|
||||
// waitQueue is empty
|
||||
injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 1);
|
||||
|
||||
const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
|
||||
mFakePolicy->assertNotifyAnrWasCalled(timeout, mApplication, mWindow->getToken());
|
||||
ASSERT_TRUE(mDispatcher->waitForIdle());
|
||||
}
|
||||
|
||||
} // namespace android::inputdispatcher
|
||||
|
|
Loading…
Reference in a new issue