blob: b18b5442573f9ec102431d5024be3705ca884823 [file] [log] [blame]
/*
* Copyright 2019 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 <gtest/gtest.h>
#include <thread>
#include <android/gui/BnRegionSamplingListener.h>
#include <binder/ProcessState.h>
#include <gui/AidlStatusUtil.h>
#include <gui/DisplayEventReceiver.h>
#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <private/gui/ComposerServiceAIDL.h>
#include <utils/Looper.h>
using namespace std::chrono_literals;
using android::gui::aidl_utils::statusTFromBinderStatus;
namespace android::test {
struct ChoreographerSync {
ChoreographerSync(DisplayEventReceiver& receiver) : receiver_(receiver) {}
~ChoreographerSync() = default;
void notify() const {
std::unique_lock<decltype(mutex_)> lk(mutex_);
auto check_event = [](auto const& ev) -> bool {
return ev.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
};
DisplayEventReceiver::Event ev_;
int evs = receiver_.getEvents(&ev_, 1);
auto vsync_event_found = check_event(ev_);
while (evs) {
evs = receiver_.getEvents(&ev_, 1);
vsync_event_found |= check_event(ev_);
}
if (vsync_event_found) {
notification_arrived_ = true;
cv_.notify_all();
}
}
void wait_vsync_notify() const {
std::unique_lock<decltype(mutex_)> lk(mutex_);
cv_.wait(lk, [this] { return notification_arrived_; });
notification_arrived_ = false;
}
private:
ChoreographerSync(ChoreographerSync const&) = delete;
ChoreographerSync& operator=(ChoreographerSync const&) = delete;
std::mutex mutable mutex_;
std::condition_variable mutable cv_;
bool mutable notification_arrived_ = false;
DisplayEventReceiver& receiver_;
};
struct ChoreographerSim {
static std::unique_ptr<ChoreographerSim> make() {
auto receiver = std::make_unique<DisplayEventReceiver>();
if (!receiver || receiver->initCheck() == NO_INIT) {
ALOGE("No display reciever");
return nullptr;
}
return std::unique_ptr<ChoreographerSim>(new ChoreographerSim(std::move(receiver)));
}
~ChoreographerSim() {
poll_ = false;
looper->wake();
choreographer_thread_.join();
}
void request_render_wait(std::function<void()> const& render_fn) {
display_event_receiver_->requestNextVsync();
choreographer_.wait_vsync_notify();
render_fn();
// Purpose is to make sure that the content is latched by the time we sample.
// Waiting one vsync after queueing could still race with vsync, so wait for two, after
// which the content is pretty reliably on screen.
display_event_receiver_->requestNextVsync();
choreographer_.wait_vsync_notify();
display_event_receiver_->requestNextVsync();
choreographer_.wait_vsync_notify();
}
private:
ChoreographerSim(std::unique_ptr<DisplayEventReceiver> receiver)
: display_event_receiver_{std::move(receiver)},
choreographer_{*display_event_receiver_},
looper{new Looper(false)} {
choreographer_thread_ = std::thread([this] {
auto vsync_notify_fd = display_event_receiver_->getFd();
looper->addFd(vsync_notify_fd, 0, Looper::EVENT_INPUT,
[](int /*fd*/, int /*events*/, void* data) -> int {
if (!data) return 0;
reinterpret_cast<ChoreographerSync*>(data)->notify();
return 1;
},
const_cast<void*>(reinterpret_cast<void const*>(&choreographer_)));
while (poll_) {
auto const poll_interval =
std::chrono::duration_cast<std::chrono::milliseconds>(1s).count();
auto rc = looper->pollOnce(poll_interval);
if ((rc != Looper::POLL_CALLBACK) && (rc != Looper::POLL_WAKE))
ALOGW("Vsync Looper returned: %i\n", rc);
}
});
}
ChoreographerSim(ChoreographerSim const&) = delete;
ChoreographerSim& operator=(ChoreographerSim const&) = delete;
std::unique_ptr<DisplayEventReceiver> const display_event_receiver_;
ChoreographerSync const choreographer_;
sp<Looper> looper;
std::thread choreographer_thread_;
std::atomic<bool> poll_{true};
};
struct Listener : android::gui::BnRegionSamplingListener {
binder::Status onSampleCollected(float medianLuma) override {
std::unique_lock<decltype(mutex)> lk(mutex);
received = true;
mLuma = medianLuma;
cv.notify_all();
return binder::Status::ok();
};
bool wait_event(std::chrono::milliseconds timeout) {
std::unique_lock<decltype(mutex)> lk(mutex);
return cv.wait_for(lk, timeout, [this] { return received; });
}
float luma() {
std::unique_lock<decltype(mutex)> lk(mutex);
return mLuma;
}
void reset() {
std::unique_lock<decltype(mutex)> lk(mutex);
received = false;
}
private:
std::condition_variable cv;
std::mutex mutex;
bool received = false;
float mLuma = -0.0f;
};
// Hoisted to TestSuite setup to avoid flake in test (b/124675919)
std::unique_ptr<ChoreographerSim> gChoreographerSim = nullptr;
struct RegionSamplingTest : ::testing::Test {
protected:
RegionSamplingTest() { ProcessState::self()->startThreadPool(); }
static void SetUpTestSuite() {
gChoreographerSim = ChoreographerSim::make();
ASSERT_NE(gChoreographerSim, nullptr);
}
void SetUp() override {
mSurfaceComposerClient = new SurfaceComposerClient;
ASSERT_EQ(NO_ERROR, mSurfaceComposerClient->initCheck());
mBackgroundLayer =
mSurfaceComposerClient->createSurface(String8("Background RegionSamplingTest"), 0,
0, PIXEL_FORMAT_RGBA_8888,
ISurfaceComposerClient::eFXSurfaceEffect);
uint32_t layerPositionBottom = 0x7E000000;
SurfaceComposerClient::Transaction{}
.setLayer(mBackgroundLayer, layerPositionBottom)
.setPosition(mBackgroundLayer, 100, 100)
.setColor(mBackgroundLayer, half3{0.5, 0.5, 0.5})
.show(mBackgroundLayer)
.apply();
mContentLayer = mSurfaceComposerClient->createSurface(String8("Content RegionSamplingTest"),
300, 300, PIXEL_FORMAT_RGBA_8888, 0);
SurfaceComposerClient::Transaction{}
.setLayer(mContentLayer, layerPositionBottom + 1)
.setPosition(mContentLayer, 100, 100)
.setColor(mContentLayer, half3{0.5, 0.5, 0.5})
.show(mContentLayer)
.apply();
mTopLayer = mSurfaceComposerClient->createSurface(String8("TopLayer RegionSamplingTest"), 0,
0, PIXEL_FORMAT_RGBA_8888, 0);
SurfaceComposerClient::Transaction{}
.setLayer(mTopLayer, layerPositionBottom + 2)
.setPosition(mTopLayer, 0, 0)
.show(mBackgroundLayer)
.apply();
}
void fill_render(uint32_t rgba_value) {
auto surface = mContentLayer->getSurface();
ANativeWindow_Buffer outBuffer;
status_t status = surface->lock(&outBuffer, NULL);
ASSERT_EQ(status, android::OK);
auto b = reinterpret_cast<uint32_t*>(outBuffer.bits);
for (auto i = 0; i < outBuffer.height; i++) {
for (auto j = 0; j < outBuffer.width; j++) {
b[j] = rgba_value;
}
b += outBuffer.stride;
}
gChoreographerSim->request_render_wait([&surface] { surface->unlockAndPost(); });
}
sp<SurfaceComposerClient> mSurfaceComposerClient;
sp<SurfaceControl> mBackgroundLayer;
sp<SurfaceControl> mContentLayer;
sp<SurfaceControl> mTopLayer;
uint32_t const rgba_green = 0xFF00FF00;
float const luma_green = 0.7152;
uint32_t const rgba_blue = 0xFFFF0000;
float const luma_blue = 0.0722;
float const error_margin = 0.01;
float const luma_gray = 0.50;
};
TEST_F(RegionSamplingTest, invalidLayerHandle_doesNotCrash) {
sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> listener = new Listener();
gui::ARect sampleArea;
sampleArea.left = 100;
sampleArea.top = 100;
sampleArea.right = 200;
sampleArea.bottom = 200;
// Passing in composer service as the layer handle should not crash, we'll
// treat it as a layer that no longer exists and silently allow sampling to
// occur.
binder::Status status =
composer->addRegionSamplingListener(sampleArea, IInterface::asBinder(composer),
listener);
ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
composer->removeRegionSamplingListener(listener);
}
TEST_F(RegionSamplingTest, DISABLED_CollectsLuma) {
fill_render(rgba_green);
sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> listener = new Listener();
gui::ARect sampleArea;
sampleArea.left = 100;
sampleArea.top = 100;
sampleArea.right = 200;
sampleArea.bottom = 200;
composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
EXPECT_NEAR(listener->luma(), luma_green, error_margin);
composer->removeRegionSamplingListener(listener);
}
TEST_F(RegionSamplingTest, DISABLED_CollectsChangingLuma) {
fill_render(rgba_green);
sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> listener = new Listener();
gui::ARect sampleArea;
sampleArea.left = 100;
sampleArea.top = 100;
sampleArea.right = 200;
sampleArea.bottom = 200;
composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
EXPECT_NEAR(listener->luma(), luma_green, error_margin);
listener->reset();
fill_render(rgba_blue);
EXPECT_TRUE(listener->wait_event(300ms))
<< "timed out waiting for 2nd luma event to be received";
EXPECT_NEAR(listener->luma(), luma_blue, error_margin);
composer->removeRegionSamplingListener(listener);
}
TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromTwoRegions) {
fill_render(rgba_green);
sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> greenListener = new Listener();
gui::ARect greenSampleArea;
greenSampleArea.left = 100;
greenSampleArea.top = 100;
greenSampleArea.right = 200;
greenSampleArea.bottom = 200;
composer->addRegionSamplingListener(greenSampleArea, mTopLayer->getHandle(), greenListener);
sp<Listener> grayListener = new Listener();
gui::ARect graySampleArea;
graySampleArea.left = 500;
graySampleArea.top = 100;
graySampleArea.right = 600;
graySampleArea.bottom = 200;
composer->addRegionSamplingListener(graySampleArea, mTopLayer->getHandle(), grayListener);
EXPECT_TRUE(grayListener->wait_event(300ms))
<< "timed out waiting for luma event to be received";
EXPECT_NEAR(grayListener->luma(), luma_gray, error_margin);
EXPECT_TRUE(greenListener->wait_event(300ms))
<< "timed out waiting for luma event to be received";
EXPECT_NEAR(greenListener->luma(), luma_green, error_margin);
composer->removeRegionSamplingListener(greenListener);
composer->removeRegionSamplingListener(grayListener);
}
TEST_F(RegionSamplingTest, DISABLED_TestIfInvalidInputParameters) {
sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> listener = new Listener();
gui::ARect invalidRect;
invalidRect.left = Rect::INVALID_RECT.left;
invalidRect.top = Rect::INVALID_RECT.top;
invalidRect.right = Rect::INVALID_RECT.right;
invalidRect.bottom = Rect::INVALID_RECT.bottom;
gui::ARect sampleArea;
sampleArea.left = 100;
sampleArea.top = 100;
sampleArea.right = 200;
sampleArea.bottom = 200;
// Invalid input sampleArea
EXPECT_EQ(BAD_VALUE,
statusTFromBinderStatus(composer->addRegionSamplingListener(invalidRect,
mTopLayer->getHandle(),
listener)));
listener->reset();
// Invalid input binder
EXPECT_EQ(NO_ERROR,
statusTFromBinderStatus(
composer->addRegionSamplingListener(sampleArea, NULL, listener)));
// Invalid input listener
EXPECT_EQ(BAD_VALUE,
statusTFromBinderStatus(composer->addRegionSamplingListener(sampleArea,
mTopLayer->getHandle(),
NULL)));
EXPECT_EQ(BAD_VALUE, statusTFromBinderStatus(composer->removeRegionSamplingListener(NULL)));
// remove the listener
composer->removeRegionSamplingListener(listener);
}
TEST_F(RegionSamplingTest, DISABLED_TestCallbackAfterRemoveListener) {
fill_render(rgba_green);
sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> listener = new Listener();
gui::ARect sampleArea;
sampleArea.left = 100;
sampleArea.top = 100;
sampleArea.right = 200;
sampleArea.bottom = 200;
composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
fill_render(rgba_green);
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
EXPECT_NEAR(listener->luma(), luma_green, error_margin);
listener->reset();
composer->removeRegionSamplingListener(listener);
fill_render(rgba_green);
EXPECT_FALSE(listener->wait_event(100ms))
<< "callback should stop after remove the region sampling listener";
}
TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromMovingLayer) {
sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> listener = new Listener();
Rect sampleArea{100, 100, 200, 200};
gui::ARect sampleAreaA;
sampleAreaA.left = sampleArea.left;
sampleAreaA.top = sampleArea.top;
sampleAreaA.right = sampleArea.right;
sampleAreaA.bottom = sampleArea.bottom;
// Test: listener in (100, 100). See layer before move, no layer after move.
fill_render(rgba_blue);
composer->addRegionSamplingListener(sampleAreaA, mTopLayer->getHandle(), listener);
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
EXPECT_NEAR(listener->luma(), luma_blue, error_margin);
listener->reset();
SurfaceComposerClient::Transaction{}.setPosition(mContentLayer, 600, 600).apply();
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
EXPECT_NEAR(listener->luma(), luma_gray, error_margin);
composer->removeRegionSamplingListener(listener);
// Test: listener offset to (600, 600). No layer before move, see layer after move.
fill_render(rgba_green);
sampleArea.offsetTo(600, 600);
sampleAreaA.left = sampleArea.left;
sampleAreaA.top = sampleArea.top;
sampleAreaA.right = sampleArea.right;
sampleAreaA.bottom = sampleArea.bottom;
composer->addRegionSamplingListener(sampleAreaA, mTopLayer->getHandle(), listener);
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
EXPECT_NEAR(listener->luma(), luma_gray, error_margin);
listener->reset();
SurfaceComposerClient::Transaction{}.setPosition(mContentLayer, 600, 600).apply();
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
EXPECT_NEAR(listener->luma(), luma_green, error_margin);
composer->removeRegionSamplingListener(listener);
}
} // namespace android::test