blob: c800d6ad4ecc003607726c380b456d97cccb5291 [file] [log] [blame]
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "FakeWindows.h"
#include <gtest/gtest.h>
namespace android {
// --- FakeInputReceiver ---
FakeInputReceiver::FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel,
const std::string name)
: mConsumer(std::move(clientChannel)), mName(name) {}
std::unique_ptr<InputEvent> FakeInputReceiver::consume(std::chrono::milliseconds timeout,
bool handled) {
auto [consumeSeq, event] = receiveEvent(timeout);
if (!consumeSeq) {
return nullptr;
}
finishEvent(*consumeSeq, handled);
return std::move(event);
}
std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> FakeInputReceiver::receiveEvent(
std::chrono::milliseconds timeout) {
uint32_t consumeSeq;
std::unique_ptr<InputEvent> event;
std::chrono::time_point start = std::chrono::steady_clock::now();
status_t status = WOULD_BLOCK;
while (status == WOULD_BLOCK) {
InputEvent* rawEventPtr = nullptr;
status = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
&rawEventPtr);
event = std::unique_ptr<InputEvent>(rawEventPtr);
std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
if (elapsed > timeout) {
break;
}
}
if (status == WOULD_BLOCK) {
// Just means there's no event available.
return std::make_pair(std::nullopt, nullptr);
}
if (status != OK) {
ADD_FAILURE() << mName.c_str() << ": consumer consume should return OK.";
return std::make_pair(std::nullopt, nullptr);
}
if (event == nullptr) {
ADD_FAILURE() << "Consumed correctly, but received NULL event from consumer";
}
return std::make_pair(consumeSeq, std::move(event));
}
void FakeInputReceiver::finishEvent(uint32_t consumeSeq, bool handled) {
const status_t status = mConsumer.sendFinishedSignal(consumeSeq, handled);
ASSERT_EQ(OK, status) << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
}
void FakeInputReceiver::sendTimeline(int32_t inputEventId,
std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
const status_t status = mConsumer.sendTimeline(inputEventId, timeline);
ASSERT_EQ(OK, status);
}
void FakeInputReceiver::consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
std::optional<ui::LogicalDisplayId> expectedDisplayId,
std::optional<int32_t> expectedFlags) {
std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
ASSERT_EQ(expectedEventType, event->getType())
<< mName.c_str() << " expected " << ftl::enum_string(expectedEventType)
<< " event, got " << *event;
if (expectedDisplayId.has_value()) {
EXPECT_EQ(expectedDisplayId, event->getDisplayId());
}
switch (expectedEventType) {
case InputEventType::KEY: {
const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*event);
ASSERT_THAT(keyEvent, WithKeyAction(expectedAction));
if (expectedFlags.has_value()) {
EXPECT_EQ(expectedFlags.value(), keyEvent.getFlags());
}
break;
}
case InputEventType::MOTION: {
const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
ASSERT_THAT(motionEvent, WithMotionAction(expectedAction));
if (expectedFlags.has_value()) {
EXPECT_EQ(expectedFlags.value(), motionEvent.getFlags());
}
break;
}
case InputEventType::FOCUS: {
FAIL() << "Use 'consumeFocusEvent' for FOCUS events";
}
case InputEventType::CAPTURE: {
FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events";
}
case InputEventType::TOUCH_MODE: {
FAIL() << "Use 'consumeTouchModeEvent' for TOUCH_MODE events";
}
case InputEventType::DRAG: {
FAIL() << "Use 'consumeDragEvent' for DRAG events";
}
}
}
std::unique_ptr<MotionEvent> FakeInputReceiver::consumeMotion() {
std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
if (event == nullptr) {
ADD_FAILURE() << mName << ": expected a MotionEvent, but didn't get one.";
return nullptr;
}
if (event->getType() != InputEventType::MOTION) {
ADD_FAILURE() << mName << " expected a MotionEvent, got " << *event;
return nullptr;
}
return std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release()));
}
void FakeInputReceiver::consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
std::unique_ptr<MotionEvent> motionEvent = consumeMotion();
ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher;
ASSERT_THAT(*motionEvent, matcher);
}
void FakeInputReceiver::consumeFocusEvent(bool hasFocus, bool inTouchMode) {
std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
ASSERT_EQ(InputEventType::FOCUS, event->getType()) << "Instead of FocusEvent, got " << *event;
ASSERT_EQ(ui::ADISPLAY_ID_NONE, event->getDisplayId())
<< mName.c_str() << ": event displayId should always be NONE.";
FocusEvent& focusEvent = static_cast<FocusEvent&>(*event);
EXPECT_EQ(hasFocus, focusEvent.getHasFocus());
}
void FakeInputReceiver::consumeCaptureEvent(bool hasCapture) {
std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
ASSERT_EQ(InputEventType::CAPTURE, event->getType())
<< "Instead of CaptureEvent, got " << *event;
ASSERT_EQ(ui::ADISPLAY_ID_NONE, event->getDisplayId())
<< mName.c_str() << ": event displayId should always be NONE.";
const auto& captureEvent = static_cast<const CaptureEvent&>(*event);
EXPECT_EQ(hasCapture, captureEvent.getPointerCaptureEnabled());
}
void FakeInputReceiver::consumeDragEvent(bool isExiting, float x, float y) {
std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
ASSERT_EQ(InputEventType::DRAG, event->getType()) << "Instead of DragEvent, got " << *event;
EXPECT_EQ(ui::ADISPLAY_ID_NONE, event->getDisplayId())
<< mName.c_str() << ": event displayId should always be NONE.";
const auto& dragEvent = static_cast<const DragEvent&>(*event);
EXPECT_EQ(isExiting, dragEvent.isExiting());
EXPECT_EQ(x, dragEvent.getX());
EXPECT_EQ(y, dragEvent.getY());
}
void FakeInputReceiver::consumeTouchModeEvent(bool inTouchMode) {
std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType())
<< "Instead of TouchModeEvent, got " << *event;
ASSERT_EQ(ui::ADISPLAY_ID_NONE, event->getDisplayId())
<< mName.c_str() << ": event displayId should always be NONE.";
const auto& touchModeEvent = static_cast<const TouchModeEvent&>(*event);
EXPECT_EQ(inTouchMode, touchModeEvent.isInTouchMode());
}
void FakeInputReceiver::assertNoEvents(std::chrono::milliseconds timeout) {
std::unique_ptr<InputEvent> event = consume(timeout);
if (event == nullptr) {
return;
}
if (event->getType() == InputEventType::KEY) {
KeyEvent& keyEvent = static_cast<KeyEvent&>(*event);
ADD_FAILURE() << "Received key event " << keyEvent;
} else if (event->getType() == InputEventType::MOTION) {
MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
ADD_FAILURE() << "Received motion event " << motionEvent;
} else if (event->getType() == InputEventType::FOCUS) {
FocusEvent& focusEvent = static_cast<FocusEvent&>(*event);
ADD_FAILURE() << "Received focus event, hasFocus = "
<< (focusEvent.getHasFocus() ? "true" : "false");
} else if (event->getType() == InputEventType::CAPTURE) {
const auto& captureEvent = static_cast<CaptureEvent&>(*event);
ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = "
<< (captureEvent.getPointerCaptureEnabled() ? "true" : "false");
} else if (event->getType() == InputEventType::TOUCH_MODE) {
const auto& touchModeEvent = static_cast<TouchModeEvent&>(*event);
ADD_FAILURE() << "Received touch mode event, inTouchMode = "
<< (touchModeEvent.isInTouchMode() ? "true" : "false");
}
FAIL() << mName.c_str()
<< ": should not have received any events, so consume() should return NULL";
}
sp<IBinder> FakeInputReceiver::getToken() {
return mConsumer.getChannel()->getConnectionToken();
}
int FakeInputReceiver::getChannelFd() {
return mConsumer.getChannel()->getFd();
}
// --- FakeWindowHandle ---
std::function<void(const std::unique_ptr<InputEvent>&, const gui::WindowInfo&)>
FakeWindowHandle::sOnEventReceivedCallback{};
std::atomic<int32_t> FakeWindowHandle::sId{1};
FakeWindowHandle::FakeWindowHandle(
const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
const std::unique_ptr<inputdispatcher::InputDispatcher>& dispatcher, const std::string name,
ui::LogicalDisplayId displayId, bool createInputChannel)
: mName(name) {
sp<IBinder> token;
if (createInputChannel) {
base::Result<std::unique_ptr<InputChannel>> channel = dispatcher->createInputChannel(name);
token = (*channel)->getConnectionToken();
mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
}
inputApplicationHandle->updateInfo();
mInfo.applicationInfo = *inputApplicationHandle->getInfo();
mInfo.token = token;
mInfo.id = sId++;
mInfo.name = name;
mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
mInfo.alpha = 1.0;
mInfo.frame = Rect(0, 0, WIDTH, HEIGHT);
mInfo.transform.set(0, 0);
mInfo.globalScaleFactor = 1.0;
mInfo.touchableRegion.clear();
mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT));
mInfo.ownerPid = WINDOW_PID;
mInfo.ownerUid = WINDOW_UID;
mInfo.displayId = displayId;
mInfo.inputConfig = InputConfig::DEFAULT;
}
sp<FakeWindowHandle> FakeWindowHandle::clone(ui::LogicalDisplayId displayId) {
sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)");
handle->mInfo = mInfo;
handle->mInfo.displayId = displayId;
handle->mInfo.id = sId++;
handle->mInputReceiver = mInputReceiver;
return handle;
}
std::unique_ptr<KeyEvent> FakeWindowHandle::consumeKey(bool handled) {
std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED, handled);
if (event == nullptr) {
ADD_FAILURE() << "No event";
return nullptr;
}
if (event->getType() != InputEventType::KEY) {
ADD_FAILURE() << "Instead of key event, got " << event;
return nullptr;
}
return std::unique_ptr<KeyEvent>(static_cast<KeyEvent*>(event.release()));
}
std::unique_ptr<MotionEvent> FakeWindowHandle::consumeMotionEvent(
const ::testing::Matcher<MotionEvent>& matcher) {
std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
if (event == nullptr) {
std::ostringstream matcherDescription;
matcher.DescribeTo(&matcherDescription);
ADD_FAILURE() << "No event (expected " << matcherDescription.str() << ") on " << mName;
return nullptr;
}
if (event->getType() != InputEventType::MOTION) {
ADD_FAILURE() << "Instead of motion event, got " << *event << " on " << mName;
return nullptr;
}
std::unique_ptr<MotionEvent> motionEvent =
std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release()));
if (motionEvent == nullptr) {
return nullptr;
}
EXPECT_THAT(*motionEvent, matcher) << " on " << mName;
return motionEvent;
}
void FakeWindowHandle::assertNoEvents(std::optional<std::chrono::milliseconds> timeout) {
if (mInputReceiver == nullptr && mInfo.inputConfig.test(InputConfig::NO_INPUT_CHANNEL)) {
return; // Can't receive events if the window does not have input channel
}
ASSERT_NE(nullptr, mInputReceiver)
<< "Window without InputReceiver must specify feature NO_INPUT_CHANNEL";
mInputReceiver->assertNoEvents(timeout.value_or(CONSUME_TIMEOUT_NO_EVENT_EXPECTED));
}
std::unique_ptr<InputEvent> FakeWindowHandle::consume(std::chrono::milliseconds timeout,
bool handled) {
if (mInputReceiver == nullptr) {
LOG(FATAL) << "Cannot consume event from a window with no input event receiver";
}
std::unique_ptr<InputEvent> event = mInputReceiver->consume(timeout, handled);
if (event == nullptr) {
ADD_FAILURE() << "Consume failed: no event";
}
if (sOnEventReceivedCallback != nullptr) {
sOnEventReceivedCallback(event, mInfo);
}
return event;
}
std::pair<std::optional<uint32_t /*seq*/>, std::unique_ptr<InputEvent>>
FakeWindowHandle::receive() {
if (mInputReceiver == nullptr) {
ADD_FAILURE() << "Invalid receive event on window with no receiver";
return std::make_pair(std::nullopt, nullptr);
}
auto out = mInputReceiver->receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED);
const auto& [_, event] = out;
if (sOnEventReceivedCallback != nullptr) {
sOnEventReceivedCallback(event, mInfo);
}
return out;
}
} // namespace android