/* * 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 #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; namespace android::test { struct ChoreographerSync { ChoreographerSync(DisplayEventReceiver& receiver) : receiver_(receiver) {} ~ChoreographerSync() = default; void notify() const { std::unique_lock 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 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 make() { auto receiver = std::make_unique(); if (!receiver || receiver->initCheck() == NO_INIT) { ALOGE("No display reciever"); return nullptr; } return std::unique_ptr(new ChoreographerSim(std::move(receiver))); } ~ChoreographerSim() { poll_ = false; looper->wake(); choreographer_thread_.join(); } void request_render_wait(std::function 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 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(data)->notify(); return 1; }, const_cast(reinterpret_cast(&choreographer_))); while (poll_) { auto const poll_interval = std::chrono::duration_cast(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 const display_event_receiver_; ChoreographerSync const choreographer_; sp looper; std::thread choreographer_thread_; std::atomic poll_{true}; }; struct Listener : BnRegionSamplingListener { void onSampleCollected(float medianLuma) override { std::unique_lock lk(mutex); received = true; mLuma = medianLuma; cv.notify_all(); }; bool wait_event(std::chrono::milliseconds timeout) { std::unique_lock lk(mutex); return cv.wait_for(lk, timeout, [this] { return received; }); } float luma() { std::unique_lock lk(mutex); return mLuma; } void reset() { std::unique_lock 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 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(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 mSurfaceComposerClient; sp mBackgroundLayer; sp mContentLayer; sp 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 composer = ComposerService::getComposerService(); sp listener = new Listener(); const Rect sampleArea{100, 100, 200, 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. status_t status = composer->addRegionSamplingListener(sampleArea, IInterface::asBinder(composer), listener); ASSERT_EQ(NO_ERROR, status); composer->removeRegionSamplingListener(listener); } TEST_F(RegionSamplingTest, DISABLED_CollectsLuma) { fill_render(rgba_green); sp composer = ComposerService::getComposerService(); sp listener = new Listener(); const Rect sampleArea{100, 100, 200, 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 composer = ComposerService::getComposerService(); sp listener = new Listener(); const Rect sampleArea{100, 100, 200, 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 composer = ComposerService::getComposerService(); sp greenListener = new Listener(); const Rect greenSampleArea{100, 100, 200, 200}; composer->addRegionSamplingListener(greenSampleArea, mTopLayer->getHandle(), greenListener); sp grayListener = new Listener(); const Rect graySampleArea{500, 100, 600, 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 composer = ComposerService::getComposerService(); sp listener = new Listener(); const Rect sampleArea{100, 100, 200, 200}; // Invalid input sampleArea EXPECT_EQ(BAD_VALUE, composer->addRegionSamplingListener(Rect::INVALID_RECT, mTopLayer->getHandle(), listener)); listener->reset(); // Invalid input binder EXPECT_EQ(NO_ERROR, composer->addRegionSamplingListener(sampleArea, NULL, listener)); // Invalid input listener EXPECT_EQ(BAD_VALUE, composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), NULL)); EXPECT_EQ(BAD_VALUE, composer->removeRegionSamplingListener(NULL)); // remove the listener composer->removeRegionSamplingListener(listener); } TEST_F(RegionSamplingTest, DISABLED_TestCallbackAfterRemoveListener) { fill_render(rgba_green); sp composer = ComposerService::getComposerService(); sp listener = new Listener(); const Rect sampleArea{100, 100, 200, 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 composer = ComposerService::getComposerService(); sp listener = new Listener(); Rect sampleArea{100, 100, 200, 200}; // Test: listener in (100, 100). See layer before move, no layer after move. fill_render(rgba_blue); 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_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); 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_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