Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b485c58a authored by Lloyd Pique's avatar Lloyd Pique
Browse files

end2end: Allow tests to create display event receivers [8/N]

Flag: TEST_ONLY
Bug: 372735083
Test: atest surfaceflinger_end2end_tests
Change-Id: I145822aac21e9e2e1fb3ff4e4969e3e37fcf1b7b
parent 2102afa1
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -43,6 +43,8 @@ cc_test {
        "test_framework/hwc3/SingleDisplayRefreshEventGenerator.cpp",
        "test_framework/hwc3/SingleDisplayRefreshSchedule.cpp",
        "test_framework/hwc3/TimeKeeperThread.cpp",
        "test_framework/surfaceflinger/DisplayEventReceiver.cpp",
        "test_framework/surfaceflinger/PollFdThread.cpp",
        "test_framework/surfaceflinger/SFController.cpp",
        "test_framework/surfaceflinger/SimpleBufferPool.cpp",
        "test_framework/surfaceflinger/Surface.cpp",
+252 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 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 <array>
#include <bit>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <utility>

#include <sys/types.h>

#include <android-base/expected.h>
#include <android-base/logging.h>
#include <android/gui/IDisplayEventConnection.h>
#include <android/gui/ISurfaceComposer.h>
#include <binder/IBinder.h>
#include <fmt/format.h>
#include <ftl/finalizer.h>
#include <ftl/flags.h>
#include <ftl/ignore.h>
#include <gui/DisplayEventReceiver.h>
#include <private/gui/BitTube.h>
#include <ui/DisplayId.h>
#include <utils/StrongPointer.h>

#include "test_framework/surfaceflinger/DisplayEventReceiver.h"
#include "test_framework/surfaceflinger/PollFdThread.h"
#include "test_framework/surfaceflinger/events/Hotplug.h"
#include "test_framework/surfaceflinger/events/VSyncTiming.h"

namespace android::surfaceflinger::tests::end2end::test_framework::surfaceflinger {

struct DisplayEventReceiver::Passkey final {};

auto DisplayEventReceiver::make(const sp<gui::ISurfaceComposer>& client, PollFdThread& pollFdThread,
                                gui::ISurfaceComposer::VsyncSource source,
                                const sp<IBinder>& layerHandle,
                                const ftl::Flags<gui::ISurfaceComposer::EventRegistration>& events)
        -> base::expected<std::shared_ptr<DisplayEventReceiver>, std::string> {
    using namespace std::string_literals;

    auto instance = std::make_unique<DisplayEventReceiver>(Passkey{});
    if (instance == nullptr) {
        return base::unexpected("Failed to construct a DisplayEventReceiver"s);
    }
    if (auto result = instance->init(client, pollFdThread, source, layerHandle, events); !result) {
        return base::unexpected("Failed to init a DisplayEventReceiver: " + result.error());
    }
    return std::move(instance);
}

DisplayEventReceiver::DisplayEventReceiver(Passkey passkey) {
    ftl::ignore(passkey);
}

[[nodiscard]] auto DisplayEventReceiver::init(
        const sp<gui::ISurfaceComposer>& client, PollFdThread& pollFdThread,
        gui::ISurfaceComposer::VsyncSource source, const sp<IBinder>& layerHandle,
        const ftl::Flags<gui::ISurfaceComposer::EventRegistration>& events)
        -> base::expected<void, std::string> {
    using namespace std::string_literals;

    sp<gui::IDisplayEventConnection> displayEventConnection;
    auto result = client->createDisplayEventConnection(
            source, std::bit_cast<gui::ISurfaceComposer::EventRegistration>(events.get()),
            layerHandle, &displayEventConnection);

    if (!result.isOk() || displayEventConnection == nullptr) {
        return base::unexpected("failed to create the display event connection"s);
    }
    auto dataChannel = std::make_unique<gui::BitTube>();
    result = displayEventConnection->stealReceiveChannel(dataChannel.get());
    if (!result.isOk()) {
        return base::unexpected("failed to steal the receive channel"s);
    }

    mPollFdThread = &pollFdThread;
    mDisplayEventConnection = std::move(displayEventConnection);
    mDataChannel = std::move(dataChannel);
    pollFdThread.addFileDescriptor(mDataChannel->getFd(), {PollFdThread::PollFlags::IN},
                                   [this]() { processReceivedEvents(); });

    mCleanup = ftl::Finalizer(
            [this]() { mPollFdThread->removeFileDescriptor(mDataChannel->getFd()); });

    LOG(VERBOSE) << "initialized";
    return {};
}

auto DisplayEventReceiver::editCallbacks() -> Callbacks& {
    return mCallbacks;
}

void DisplayEventReceiver::setVsyncRate(int32_t count) const {
    LOG(VERBOSE) << __func__ << " count " << count;
    CHECK(count >= 0);
    mDisplayEventConnection->setVsyncRate(count);
}

void DisplayEventReceiver::requestNextVsync() const {
    LOG(VERBOSE) << __func__;
    mDisplayEventConnection->requestNextVsync();
}

[[nodiscard]] auto DisplayEventReceiver::getLatestVsyncEventData() const
        -> std::optional<ParcelableVsyncEventData> {
    LOG(VERBOSE) << __func__;
    ParcelableVsyncEventData data;
    auto status = mDisplayEventConnection->getLatestVsyncEventData(&data);
    if (!status.isOk()) {
        LOG(ERROR) << "Failed to get latest vsync event data: " << status.toString8();
        return {};
    }
    return data;
}

void DisplayEventReceiver::processReceivedEvents() {
    for (;;) {
        constexpr auto kMaxEventBatchSize = 10;
        std::array<Event, kMaxEventBatchSize> events{};
        const ssize_t eventCount =
                gui::BitTube::recvObjects(mDataChannel.get(), events.data(), events.size());
        LOG(VERBOSE) << __func__ << " received " << eventCount << " events";
        CHECK(eventCount >= 0);
        if (eventCount == 0) {
            return;
        }
        processReceivedEvents({events.data(), static_cast<size_t>(eventCount)});
    }
}

void DisplayEventReceiver::processReceivedEvents(std::span<Event> events) {
    LOG(VERBOSE) << __func__ << " " << events.size() << " events";
    for (const auto& event : events) {
        LOG(VERBOSE) << fmt::format("event {:#x}", fmt::underlying(event.header.type));

        auto timestamp = Timestamp(std::chrono::steady_clock::duration(event.header.timestamp));

        // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access)
        switch (event.header.type) {
            default:
                LOG(FATAL) << fmt::format("Unknown event type {:#x}",
                                          fmt::underlying(event.header.type));
                break;

            case EventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH:
                LOG(FATAL) << "Unexpected DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH event";
                break;

            case EventType::DISPLAY_EVENT_VSYNC:
                onVsync(event.header.displayId, timestamp, event.vsync.count,
                        event.vsync.vsyncData);
                break;

            case EventType::DISPLAY_EVENT_HOTPLUG:
                onHotplug(event.header.displayId, timestamp, event.hotplug.connected,
                          event.hotplug.connectionError);
                break;

            case EventType::DISPLAY_EVENT_MODE_CHANGE:
                onModeChange(event.header.displayId, timestamp, event.modeChange.modeId,
                             std::chrono::nanoseconds(event.modeChange.vsyncPeriod));
                break;

            case EventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE:
                onFrameRateOverride(event.header.displayId, timestamp, event.frameRateOverride.uid,
                                    event.frameRateOverride.frameRateHz);
                break;

            case EventType::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
                onHdcpLevelsChange(event.header.displayId, timestamp,
                                   event.hdcpLevelsChange.connectedLevel,
                                   event.hdcpLevelsChange.maxLevel);
                break;
        }
        // NOLINTEND(cppcoreguidelines-pro-type-union-access)
    }
}

void DisplayEventReceiver::onVsync(DisplayId displayId, Timestamp timestamp, uint32_t count,
                                   VsyncEventData vsyncData) {
    ftl::ignore(displayId, timestamp, count, vsyncData);
    LOG(VERBOSE) << "onVsync() display " << displayId << " timestamp "
                 << timestamp.time_since_epoch() << " count " << count;

    mCallbacks.onVSyncTiming(events::VSyncTiming{
            .receiver = this,
            .sfEventAt = timestamp,
            .count = count,
            .data = vsyncData,
    });
}

void DisplayEventReceiver::onHotplug(DisplayId displayId, Timestamp timestamp, bool connected,
                                     int32_t connectionError) {
    ftl::ignore(displayId, timestamp, connected, connectionError);
    LOG(VERBOSE) << "onHotplug() display " << displayId << " timestamp "
                 << timestamp.time_since_epoch() << " connected " << connected
                 << " connectionError " << connectionError;

    if (connectionError == 0) {
        mCallbacks.onHotplug(events::Hotplug{
                .receiver = this,
                .sfEventAt = timestamp,
                .connected = connected,
        });
    }
}

void DisplayEventReceiver::onModeChange(DisplayId displayId, Timestamp timestamp, int32_t modeId,
                                        std::chrono::nanoseconds vsyncPeriod) {
    ftl::ignore(displayId, timestamp, modeId, vsyncPeriod);
    LOG(VERBOSE) << "onModeChange() display " << displayId << " timestamp "
                 << timestamp.time_since_epoch() << " modeId " << modeId << " vsyncPeriod "
                 << vsyncPeriod;
}

void DisplayEventReceiver::onFrameRateOverride(DisplayId displayId, Timestamp timestamp, uid_t uid,
                                               float framerateHz) {
    ftl::ignore(displayId, timestamp, uid, framerateHz);
    LOG(VERBOSE) << "onFrameRateOverride() display " << displayId << " timestamp "
                 << timestamp.time_since_epoch() << " uid " << uid << " framerateHz "
                 << framerateHz;
}

void DisplayEventReceiver::onHdcpLevelsChange(DisplayId displayId, Timestamp timestamp,
                                              int32_t connectedLevel, int32_t maxLevel) {
    ftl::ignore(displayId, timestamp, connectedLevel, maxLevel);
    LOG(VERBOSE) << "onHdcpLevelsChange() display " << displayId << " timestamp "
                 << timestamp.time_since_epoch() << " connectedLevel " << connectedLevel
                 << " maxLevel " << maxLevel;
}

}  // namespace android::surfaceflinger::tests::end2end::test_framework::surfaceflinger
+118 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 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.
 */

#pragma once

#include <chrono>
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <string>

#include <sys/types.h>

#include <android-base/expected.h>
#include <android/gui/ISurfaceComposer.h>
#include <binder/IBinder.h>
#include <ftl/finalizer.h>
#include <ftl/flags.h>
#include <gui/DisplayEventReceiver.h>
#include <gui/VsyncEventData.h>
#include <ui/DisplayId.h>
#include <utils/StrongPointer.h>

#include "test_framework/surfaceflinger/PollFdThread.h"
#include "test_framework/surfaceflinger/events/Hotplug.h"
#include "test_framework/surfaceflinger/events/VSyncTiming.h"

namespace android::surfaceflinger::tests::end2end::test_framework::surfaceflinger {

class PollFdThread;

class DisplayEventReceiver final {
    struct Passkey;  // Uses the passkey idiom to restrict construction.

  public:
    // The callbacks provided from a surface instance.
    struct Callbacks final {
        using Timestamp = std::chrono::steady_clock::time_point;

        // The vsync timing display event from SurfaceFlinger (slightly simplified).
        events::VSyncTiming::AsyncConnector onVSyncTiming;

        // The hotplug display event from SurfaceFlinger (slightly simplified).
        events::Hotplug::AsyncConnector onHotplug;
    };

    [[nodiscard]] static auto make(
            const sp<gui::ISurfaceComposer>& client, PollFdThread& pollFdThread,
            gui::ISurfaceComposer::VsyncSource source, const sp<IBinder>& layerHandle,
            const ftl::Flags<gui::ISurfaceComposer::EventRegistration>& events)
            -> base::expected<std::shared_ptr<DisplayEventReceiver>, std::string>;

    explicit DisplayEventReceiver(Passkey pass);

    // Allows the callbacks to be set by the caller, by modifying the values in the returned
    // structure.
    [[nodiscard]] auto editCallbacks() -> Callbacks&;

    // Sets the vsync rate for events.
    void setVsyncRate(int32_t count) const;

    // Requests a vsync callback.
    void requestNextVsync() const;

    // Obtains the latest vsync data immediately.
    [[nodiscard]] auto getLatestVsyncEventData() const -> std::optional<ParcelableVsyncEventData>;

  private:
    using Event = android::DisplayEventReceiver::Event;
    using EventType = android::DisplayEventType;
    using DisplayId = PhysicalDisplayId;
    using Timestamp = std::chrono::steady_clock::time_point;
    using VsyncEventData = android::VsyncEventData;

    [[nodiscard]] auto init(const sp<gui::ISurfaceComposer>& client, PollFdThread& pollFdThread,
                            gui::ISurfaceComposer::VsyncSource source,
                            const sp<IBinder>& layerHandle,
                            const ftl::Flags<gui::ISurfaceComposer::EventRegistration>& events)
            -> base::expected<void, std::string>;

    void processReceivedEvents();
    void processReceivedEvents(std::span<Event> events);
    void onVsync(DisplayId displayId, Timestamp timestamp, uint32_t count,
                 VsyncEventData vsyncData);
    void onHotplug(DisplayId displayId, Timestamp timestamp, bool connected,
                   int32_t connectionError);
    static void onModeChange(DisplayId displayId, Timestamp timestamp, int32_t modeId,
                             std::chrono::nanoseconds vsyncPeriod);
    static void onFrameRateOverride(DisplayId displayId, Timestamp timestamp, uid_t uid,
                                    float framerateHz);
    static void onHdcpLevelsChange(DisplayId displayId, Timestamp timestamp, int32_t connectedLevel,
                                   int32_t maxLevel);

    Callbacks mCallbacks;

    sp<gui::IDisplayEventConnection> mDisplayEventConnection;
    std::unique_ptr<gui::BitTube> mDataChannel;
    PollFdThread* mPollFdThread = nullptr;

    // Finalizers should be last so their destructors are invoked first.
    ftl::FinalizerFtl mCleanup;
};

}  // namespace android::surfaceflinger::tests::end2end::test_framework::surfaceflinger
+172 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 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 <unistd.h>
#include <array>
#include <atomic>
#include <cerrno>
#include <cstdint>
#include <cstring>
#include <memory>
#include <mutex>
#include <string>
#include <system_error>
#include <thread>
#include <unordered_map>
#include <utility>

#include <bits/epoll_event.h>
#include <linux/eventfd.h>
#include <linux/eventpoll.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>

#include <android-base/expected.h>
#include <android-base/logging.h>
#include <android-base/thread_annotations.h>  // NOLINT(misc-include-cleaner)
#include <android-base/unique_fd.h>
#include <fmt/format.h>
#include <ftl/finalizer.h>
#include <ftl/ignore.h>

#include "test_framework/core/AsyncFunction.h"
#include "test_framework/surfaceflinger/PollFdThread.h"

namespace android::surfaceflinger::tests::end2end::test_framework::surfaceflinger {

static_assert(EPOLLIN == static_cast<int>(PollFdThread::PollFlags::IN));
static_assert(EPOLLOUT == static_cast<int>(PollFdThread::PollFlags::OUT));
static_assert(EPOLLERR == static_cast<int>(PollFdThread::PollFlags::ERR));

struct PollFdThread::Passkey final {};

auto PollFdThread::make() -> base::expected<std::shared_ptr<PollFdThread>, std::string> {
    using namespace std::string_literals;

    auto instance = std::make_unique<PollFdThread>(Passkey{});
    if (instance == nullptr) {
        return base::unexpected("Failed to construct a PollFdThread"s);
    }
    if (auto result = instance->init(); !result) {
        return base::unexpected("Failed to init a PollFdThread: " + result.error());
    }
    return std::move(instance);
}

PollFdThread::PollFdThread(Passkey passkey) {
    ftl::ignore(passkey);
}

auto PollFdThread::init() -> base::expected<void, std::string> {
    mEventFd = base::unique_fd(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
    CHECK(mEventFd.get() >= 0);
    mPollFd = base::unique_fd(epoll_create1(EPOLL_CLOEXEC));
    CHECK(mPollFd.get() >= 0);
    addFileDescriptor(mEventFd.get(), {PollFlags::IN, PollFlags::ERR}, [this]() {
        int64_t ignoredCount = 0;
        read(mEventFd.get(), &ignoredCount, sizeof(ignoredCount));
    });

    mThread = std::thread(&PollFdThread::threadMain, this);

    mCleanup = ftl::Finalizer([this]() {
        stopThread();

        const std::lock_guard lock(mMutex);
        mCallbacks.erase(mEventFd.get());
        CHECK(mCallbacks.empty()) << "Not all finalizers were called.";
    });

    return {};
}

// PollFlagMask (ftl::Flags) is not trivially copyable, but should be.
// NOLINTNEXTLINE(performance-unnecessary-value-param)
void PollFdThread::addFileDescriptor(int descriptor, PollFlagMask flags, EventCallback callback) {
    LOG(VERBOSE) << __func__ << " descriptor " << descriptor << " mask " << flags.string();

    ftl::FinalizerStd cleanup;
    {
        const std::lock_guard lock(mMutex);
        cleanup = mCallbacks[descriptor].set(callback);
    }
    cleanup();

    epoll_event event{.events = flags.get(), .data = {.fd = descriptor}};
    const int result = epoll_ctl(mPollFd.get(), EPOLL_CTL_ADD, descriptor, &event);

    if (result != 0) {
        auto err = std::generic_category().default_error_condition(errno);
        LOG(FATAL) << fmt::format("Result {} errno: {} ({})", result, err.message(), err.value());
    }
}

void PollFdThread::removeFileDescriptor(int descriptor) {
    LOG(VERBOSE) << __func__ << " descriptor " << descriptor;

    {
        const std::lock_guard lock(mMutex);
        mCallbacks.erase(descriptor);
    }

    CHECK(epoll_ctl(mPollFd.get(), EPOLL_CTL_DEL, descriptor, nullptr) == 0);
}

void PollFdThread::threadMain() {
    LOG(VERBOSE) << __func__ << " begins";
    while (!mStop) {
        constexpr int kMaxEvents = 10;
        std::array<epoll_event, kMaxEvents> events = {};
        LOG(VERBOSE) << __func__ << " epoll_wait()";
        const int eventCount = epoll_wait(mPollFd.get(), events.data(), kMaxEvents, -1);
        if (eventCount < 0 && errno == EINTR) {
            LOG(VERBOSE) << "epoll_wait() interrupted";
            continue;
        }
        LOG(VERBOSE) << __func__ << " " << eventCount << " events";

        if (eventCount < 0) {
            auto err = std::generic_category().default_error_condition(errno);
            LOG(FATAL) << fmt::format("Result {} errno: {} ({})", eventCount, err.message(),
                                      err.value());
        }

        const std::lock_guard lock(mMutex);
        for (int i = 0; i < eventCount; ++i) {
            // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
            if (const auto found = mCallbacks.find(events[i].data.fd); found != mCallbacks.end()) {
                found->second();
            }
        }
    }
    LOG(VERBOSE) << __func__ << " ends";
}

void PollFdThread::wakeNow() {
    const int64_t one = 1;
    write(mEventFd.get(), &one, sizeof(one));
}

void PollFdThread::stopThread() {
    if (mThread.joinable()) {
        mStop = true;
        wakeNow();
        LOG(VERBOSE) << "Waiting for thread to stop...";
        mThread.join();
        LOG(VERBOSE) << "Stopped.";
    }
}

}  // namespace android::surfaceflinger::tests::end2end::test_framework::surfaceflinger
+82 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 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.
 */

#pragma once

#include <atomic>
#include <cstdint>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>

#include <android-base/expected.h>
#include <android-base/thread_annotations.h>
#include <android-base/unique_fd.h>
#include <ftl/finalizer.h>
#include <ftl/flags.h>
#include <ftl/function.h>

#include "test_framework/core/AsyncFunction.h"

namespace android::surfaceflinger::tests::end2end::test_framework::surfaceflinger {

class PollFdThread final {
    struct Passkey;  // Uses the passkey idiom to restrict construction.

  public:
    enum class PollFlags : uint8_t {
        IN = 0x01,   // EPOLLIN
        OUT = 0x04,  // EPOLLOUT
        ERR = 0x08,  // EPOLLERR
    };
    using PollFlagMask = ftl::Flags<PollFlags>;

    using EventCallback = ftl::Function<void()>;

    [[nodiscard]] static auto make() -> base::expected<std::shared_ptr<PollFdThread>, std::string>;

    explicit PollFdThread(Passkey passkey);

    // Adds the given file descriptor to the polling set, and invokes the event callback for events.
    void addFileDescriptor(int descriptor, PollFlagMask flags, EventCallback callback);

    // Removes the given file descriptor from the polling set.
    void removeFileDescriptor(int descriptor);

  private:
    using AsyncEventCallback = core::AsyncFunctionStd<void()>;
    using CallbackMap = std::unordered_map<int, AsyncEventCallback>;

    [[nodiscard]] auto init() -> base::expected<void, std::string>;

    void threadMain();
    void wakeNow();
    void stopThread();

    base::unique_fd mEventFd;
    base::unique_fd mPollFd;
    std::thread mThread;
    std::atomic_bool mStop{false};
    std::mutex mMutex;
    CallbackMap mCallbacks GUARDED_BY(mMutex);

    // Finalizers should be last so their destructors are invoked first.
    ftl::FinalizerFtl mCleanup;
};

}  // namespace android::surfaceflinger::tests::end2end::test_framework::surfaceflinger
Loading