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

Commit 9bbacebd authored by Joy Yoonhyung Lee's avatar Joy Yoonhyung Lee
Browse files

Support multi-display screenshot in dumpstate

Added a flag -m to dumpstate which takes screenshots of all displays
and provides a zip file including them instead of a single png file.

This feature is available only when
bugreport_multi_display_screenshot_enabled flag is enabled.

Bug: 364767133
Test: Build and Run `dumpstate -p -m`
Flag: android.os.bugreport_multi_display_screenshot_enabled
Change-Id: I2705ab478edbd96042fd7130b0a2b14750dc21a0
parent 1a0d3789
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ cc_defaults {
        "android.hardware.dumpstate@1.0",
        "android.hardware.dumpstate@1.1",
        "android.hardware.dumpstate-V1-ndk",
        "android.os.flags-aconfig-cc-host",
        "libziparchive",
        "libbase",
        "libbinder", // BAD: dumpstate should not link code directly, should only exec binaries
@@ -99,6 +100,7 @@ cc_defaults {
        "libdumpstateaidl",
        "libdumpstateutil",
        "libdumputils",
        "libgui",
        "libhardware_legacy",
        "libhidlbase", // BAD: dumpstate should not link code directly, should only exec binaries
        "liblog",
+3 −0
Original line number Diff line number Diff line
@@ -61,6 +61,9 @@ interface IDumpstate {
    // Keep bugreport stored after retrieval.
    const int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 0x4;

    // Capture multi-display screenshots.
    const int BUGREPORT_FLAG_CAPTURE_MULTI_DISPLAY_SCREENSHOT = 0x8;

    /**
     * Speculatively pre-dumps UI data for a bugreport request that might come later.
     *
+101 −18
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@
#include <android/hidl/manager/1.0/IServiceManager.h>
#include <android/os/IIncidentCompanion.h>
#include <android_app_admin_flags.h>
#include <android_os.h>
#include <android_tracing.h>
#include <binder/IServiceManager.h>
#include <cutils/multiuser.h>
@@ -48,6 +49,7 @@
#include <dumputils/dump_utils.h>
#include <errno.h>
#include <fcntl.h>
#include <gui/SurfaceComposerClient.h>
#include <hardware_legacy/power.h>
#include <hidl/ServiceManagement.h>
#include <inttypes.h>
@@ -113,10 +115,12 @@ using android::Dumpsys;
using android::INVALID_OPERATION;
using android::IServiceManager;
using android::OK;
using android::PhysicalDisplayId;
using android::sp;
using android::status_t;
using android::String16;
using android::String8;
using android::SurfaceComposerClient;
using android::TIMED_OUT;
using android::UNKNOWN_ERROR;
using android::Vector;
@@ -798,6 +802,9 @@ android::binder::Status Dumpstate::ConsentCallback::onReportApproved() {
                                                    ds.options_->screenshot_fd.get());
    if (copy_succeeded) {
        android::os::UnlinkAndLogOnError(ds.screenshot_path_);
        for (auto& [_, path] : ds.screenshot_path_by_display_id_) {
            android::os::UnlinkAndLogOnError(path);
        }
    } else {
        MYLOGE("Failed to copy screenshot to a permanent file.\n");
        copy_succeeded = android::os::CopyFileToFd(DEFAULT_SCREENSHOT_PATH,
@@ -883,6 +890,12 @@ static const std::set<std::string> PROBLEMATIC_FILE_EXTENSIONS = {

status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd,
                                      std::chrono::milliseconds timeout = 0ms) {
    return AddZipEntryFromFd(zip_writer_, entry_name, fd, timeout);
}

status_t Dumpstate::AddZipEntryFromFd(const std::unique_ptr<ZipWriter>& zip_writer,
                                      const std::string& entry_name, int fd,
                                      std::chrono::milliseconds timeout = 0ms) {
    std::string valid_name = entry_name;

    // Rename extension if necessary.
@@ -899,21 +912,20 @@ status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd,
    // Logging statement  below is useful to time how long each entry takes, but it's too verbose.
    // MYLOGD("Adding zip entry %s\n", entry_name.c_str());
    size_t flags = ZipWriter::kCompress | ZipWriter::kDefaultCompression;
    int32_t err = zip_writer_->StartEntryWithTime(valid_name.c_str(), flags,
                                                  get_mtime(fd, ds.now_));
    int32_t err = zip_writer->StartEntryWithTime(valid_name.c_str(), flags, get_mtime(fd, ds.now_));
    if (err != 0) {
        MYLOGE("zip_writer_->StartEntryWithTime(%s): %s\n", valid_name.c_str(),
        MYLOGE("zip_writer->StartEntryWithTime(%s): %s\n", valid_name.c_str(),
               ZipWriter::ErrorCodeString(err));
        return UNKNOWN_ERROR;
    }
    bool finished_entry = false;
    auto finish_entry = [this, &finished_entry] {
    auto finish_entry = [&zip_writer, &finished_entry] {
        if (!finished_entry) {
            // This should only be called when we're going to return an earlier error,
            // which would've been logged. This may imply the file is already corrupt
            // and any further logging from FinishEntry is more likely to mislead than
            // not.
            this->zip_writer_->FinishEntry();
            zip_writer->FinishEntry();
        }
    };
    auto scope_guard = android::base::make_scope_guard(finish_entry);
@@ -950,17 +962,17 @@ status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd,
            MYLOGE("read(%s): %s\n", entry_name.c_str(), strerror(errno));
            return -errno;
        }
        err = zip_writer_->WriteBytes(buffer.data(), bytes_read);
        err = zip_writer->WriteBytes(buffer.data(), bytes_read);
        if (err) {
            MYLOGE("zip_writer_->WriteBytes(): %s\n", ZipWriter::ErrorCodeString(err));
            MYLOGE("zip_writer->WriteBytes(): %s\n", ZipWriter::ErrorCodeString(err));
            return UNKNOWN_ERROR;
        }
    }

    err = zip_writer_->FinishEntry();
    err = zip_writer->FinishEntry();
    finished_entry = true;
    if (err != 0) {
        MYLOGE("zip_writer_->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err));
        MYLOGE("zip_writer->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err));
        return UNKNOWN_ERROR;
    }

@@ -968,6 +980,11 @@ status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd,
}

bool Dumpstate::AddZipEntry(const std::string& entry_name, const std::string& entry_path) {
    return AddZipEntry(zip_writer_, entry_name, entry_path);
}

bool Dumpstate::AddZipEntry(const std::unique_ptr<ZipWriter>& zip_writer,
                            const std::string& entry_name, const std::string& entry_path) {
    android::base::unique_fd fd(
        TEMP_FAILURE_RETRY(open(entry_path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC)));
    if (fd == -1) {
@@ -975,7 +992,7 @@ bool Dumpstate::AddZipEntry(const std::string& entry_name, const std::string& en
        return false;
    }

    return (AddZipEntryFromFd(entry_name, fd.get()) == OK);
    return (AddZipEntryFromFd(zip_writer, entry_name, fd.get()) == OK);
}

/* adds a file to the existing zipped bugreport */
@@ -2765,13 +2782,14 @@ void Dumpstate::DumpstateBoard(int out_fd) {

static void ShowUsage() {
    fprintf(stderr,
            "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-o directory] [-p] "
            "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-o directory] [-p] [-m] "
            "[-s] [-S] [-q] [-P] [-R] [-L] [-V version]\n"
            "  -h: display this help message\n"
            "  -b: play sound file instead of vibrate, at beginning of job\n"
            "  -e: play sound file instead of vibrate, at end of job\n"
            "  -o: write to custom directory (only in limited mode)\n"
            "  -p: capture screenshot to filename.png\n"
            "  -m: capture screenshots of all displays to filename.zip\n"
            "  -s: write zipped file to control socket (for init)\n"
            "  -S: write file location to control socket (for init)\n"
            "  -q: disable vibrate\n"
@@ -2899,8 +2917,21 @@ static bool PrepareToWriteToFile() {
    }

    if (ds.options_->do_screenshot) {
        if (android::os::bugreport_multi_display_screenshot_enabled() &&
            ds.options_->multi_display_screenshot) {
            ds.screenshot_path_ =
                ds.GetPath(ds.CalledByApi() ? "-screenshots-zip.tmp" : "-screenshots.zip");

            std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
            for (PhysicalDisplayId display_id : ids) {
                std::string id = android::to_string(display_id);
                std::string path = ds.GetPath("-" + id + ".png");
                ds.screenshot_path_by_display_id_.insert(std::make_pair(id, path));
            }
        } else {
            ds.screenshot_path_ = ds.GetPath(ds.CalledByApi() ? "-png.tmp" : ".png");
        }
    }
    ds.tmp_path_ = ds.GetPath(".tmp");
    ds.log_path_ = ds.GetPath("-dumpstate_log-" + std::to_string(ds.pid_) + ".txt");

@@ -3034,15 +3065,14 @@ static void SetOptionsFromMode(Dumpstate::BugreportMode mode, Dumpstate::DumpOpt
static void LogDumpOptions(const Dumpstate::DumpOptions& options) {
    MYLOGI(
        "do_vibrate: %d stream_to_socket: %d progress_updates_to_socket: %d do_screenshot: %d "
        "is_remote_mode: %d show_header_only: %d telephony_only: %d "
        "multi_display_screenshot: %d is_remote_mode: %d show_header_only: %d telephony_only: %d "
        "wifi_only: %d do_progress_updates: %d fd: %d bugreport_mode: %s "
        "limited_only: %d args: %s\n",
        options.do_vibrate, options.stream_to_socket, options.progress_updates_to_socket,
        options.do_screenshot, options.is_remote_mode, options.show_header_only,
        options.telephony_only, options.wifi_only,
        options.do_screenshot, options.multi_display_screenshot, options.is_remote_mode,
        options.show_header_only, options.telephony_only, options.wifi_only,
        options.do_progress_updates, options.bugreport_fd.get(),
        options.bugreport_mode_string.c_str(),
        options.limited_only, options.args.c_str());
        options.bugreport_mode_string.c_str(), options.limited_only, options.args.c_str());
}

void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode,
@@ -3053,6 +3083,8 @@ void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode,
                                        bool skip_user_consent) {
    this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA;
    this->is_consent_deferred = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT;
    this->multi_display_screenshot =
        bugreport_flags & BugreportFlag::BUGREPORT_FLAG_CAPTURE_MULTI_DISPLAY_SCREENSHOT;
    this->skip_user_consent = skip_user_consent;
    // Duplicate the fds because the passed in fds don't outlive the binder transaction.
    bugreport_fd.reset(fcntl(bugreport_fd_in.get(), F_DUPFD_CLOEXEC, 0));
@@ -3064,7 +3096,7 @@ void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode,
Dumpstate::RunStatus Dumpstate::DumpOptions::Initialize(int argc, char* argv[]) {
    RunStatus status = RunStatus::OK;
    int c;
    while ((c = getopt(argc, argv, "dho:svqzpLPBRSV:w")) != -1) {
    while ((c = getopt(argc, argv, "dho:svqzpmLPBRSV:w")) != -1) {
        switch (c) {
            // clang-format off
            case 'o': out_dir = optarg;              break;
@@ -3073,6 +3105,7 @@ Dumpstate::RunStatus Dumpstate::DumpOptions::Initialize(int argc, char* argv[])
            case 'v': show_header_only = true;       break;
            case 'q': do_vibrate = false;            break;
            case 'p': do_screenshot = true;          break;
            case 'm': multi_display_screenshot = true;      break;
            case 'P': do_progress_updates = true;    break;
            case 'R': is_remote_mode = true;         break;
            case 'L': limited_only = true;           break;
@@ -3717,6 +3750,9 @@ bool Dumpstate::CalledByApi() const {
void Dumpstate::CleanupTmpFiles() {
    android::os::UnlinkAndLogOnError(tmp_path_);
    android::os::UnlinkAndLogOnError(screenshot_path_);
    for (auto& [_, path] : screenshot_path_by_display_id_) {
        android::os::UnlinkAndLogOnError(path);
    }
    android::os::UnlinkAndLogOnError(path_);
    if (dump_traces_path != nullptr) {
        android::os::UnlinkAndLogOnError(dump_traces_path);
@@ -4712,6 +4748,15 @@ void Dumpstate::UpdateProgress(int32_t delta_sec) {
}

void Dumpstate::TakeScreenshot(const std::string& path) {
    if (android::os::bugreport_multi_display_screenshot_enabled() &&
        ds.options_->multi_display_screenshot) {
        TakeMultiDisplayScreenshots(path);
    } else {
        TakeSingleDisplayScreenshot(path);
    }
}

void Dumpstate::TakeSingleDisplayScreenshot(const std::string& path) {
    const std::string& real_path = path.empty() ? screenshot_path_ : path;
    int status =
        RunCommand("", {"screencap", "-p", real_path},
@@ -4728,6 +4773,44 @@ void Dumpstate::TakeScreenshot(const std::string& path) {
    }
}

void Dumpstate::TakeMultiDisplayScreenshots(const std::string& path) {
    const std::string& real_path = path.empty() ? screenshot_path_ : path;

    FILE* zipFile = fopen(real_path.c_str(), "wb");
    std::unique_ptr<ZipWriter> zip_writer = std::make_unique<ZipWriter>(zipFile);

    bool success = false;

    for (auto& [display_id, screenshot_path] : ds.screenshot_path_by_display_id_) {
        int status = RunCommand(
            "", {"/system/bin/screencap", "-p", screenshot_path, "-d", display_id},
            CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build());

        if (status == 0) {
            MYLOGD("Screenshot for display id %s saved on %s\n", display_id.c_str(),
                   screenshot_path.c_str());

            size_t flags = ZipWriter::kCompress | ZipWriter::kDefaultCompression;
            std::filesystem::path p(screenshot_path);
            std::string filename = p.filename().string();
            AddZipEntry(zip_writer, filename, screenshot_path);
            success = true;
        } else {
            MYLOGE("Failed to take screenshot for display id %s on %s\n", display_id.c_str(),
                   screenshot_path.c_str());
        }
    }

    zip_writer->Finish();
    fclose(zipFile);

    if (listener_ != nullptr) {
        // Show a visual indication to indicate screenshot is taken via
        // IDumpstateListener.onScreenshotTaken()
        listener_->onScreenshotTaken(success);
    }
}

bool is_dir(const char* pathname) {
    struct stat info;
    if (stat(pathname, &info) == -1) {
+19 −5
Original line number Diff line number Diff line
@@ -209,10 +209,11 @@ class Dumpstate {
    enum BugreportFlag {
        BUGREPORT_USE_PREDUMPED_UI_DATA =
            android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA,
        BUGREPORT_FLAG_DEFER_CONSENT =
          android::os::IDumpstate::BUGREPORT_FLAG_DEFER_CONSENT,
        BUGREPORT_FLAG_DEFER_CONSENT = android::os::IDumpstate::BUGREPORT_FLAG_DEFER_CONSENT,
        BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL =
                    android::os::IDumpstate::BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL
            android::os::IDumpstate::BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL,
        BUGREPORT_FLAG_CAPTURE_MULTI_DISPLAY_SCREENSHOT =
            android::os::IDumpstate::BUGREPORT_FLAG_CAPTURE_MULTI_DISPLAY_SCREENSHOT
    };

    static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS;
@@ -410,6 +411,7 @@ class Dumpstate {
        // Writes generation progress updates to a socket.
        bool progress_updates_to_socket = false;
        bool do_screenshot = false;
        bool multi_display_screenshot = false;
        bool is_screenshot_copied = false;
        bool is_consent_deferred = false;
        bool skip_user_consent = false;
@@ -513,6 +515,9 @@ class Dumpstate {
    // Full path of the file containing the screenshot (when requested).
    std::string screenshot_path_;

    // Full paths of the files containing the screenshots for multi displays (when requested).
    std::map<std::string, std::string> screenshot_path_by_display_id_;

    // Pointer to the zipped file.
    std::unique_ptr<FILE, int (*)(FILE*)> zip_file{nullptr, fclose};

@@ -572,6 +577,15 @@ class Dumpstate {
    RunStatus DumpstateDefaultAfterCritical();
    RunStatus dumpstate();

    bool AddZipEntry(const std::unique_ptr<ZipWriter>& zip_writer, const std::string& entry_name,
                     const std::string& entry_path);
    android::status_t AddZipEntryFromFd(const std::unique_ptr<ZipWriter>& zip_writer,
                                        const std::string& entry_name, int fd,
                                        std::chrono::milliseconds timeout);

    void TakeSingleDisplayScreenshot(const std::string& path = "");
    void TakeMultiDisplayScreenshots(const std::string& path = "");

    void MaybeTakeEarlyScreenshot();
    void MaybeSavePlaceholderScreenshot();
    std::future<std::string> MaybeSnapshotSystemTraceAsync();
+40 −0
Original line number Diff line number Diff line
@@ -1096,6 +1096,7 @@ TEST_F(ZippedBugReportStreamTest, ScreenShotFileCreated) {

    std::string screenshot = ds_.GetPath(ds_.CalledByApi() ? "-png.tmp" : ".png");
    EXPECT_TRUE(std::filesystem::exists(screenshot)) << screenshot << " was not created.";
    android::base::RemoveFileIfExists(screenshot);
}

TEST_F(ZippedBugReportStreamTest, ScreenShotFileIsNotCreated) {
@@ -1114,6 +1115,45 @@ TEST_F(ZippedBugReportStreamTest, ScreenShotFileIsNotCreated) {
    EXPECT_FALSE(std::filesystem::exists(screenshot)) << screenshot << " was created.";
}

TEST_F(ZippedBugReportStreamTest, ScreenShotZipFileCreated) {
    std::string out_path = kTestDataPath + "ScreenShotCapturedOut.zip";
    android::base::unique_fd out_fd;
    CreateFd(out_path, &out_fd);
    ds_.options_->limited_only = true;
    ds_.options_->stream_to_socket = true;
    ds_.options_->do_screenshot = false;
    ds_.options_->multi_display_screenshot = true;
    RedirectOutputToFd(out_fd);

    GenerateBugreport();

    std::string screenshot = ds_.GetPath(ds_.CalledByApi() ? "-screenshots-zip.tmp" : ".zip");
    EXPECT_TRUE(std::filesystem::exists(screenshot)) << screenshot << " was not created.";
    android::base::RemoveFileIfExists(screenshot);
}

TEST_F(ZippedBugReportStreamTest, ScreenShotsAreIncludedInScreenShotZipFile) {
    std::string out_path = kTestDataPath + "ScreenShotCapturedOut.zip";
    android::base::unique_fd out_fd;
    CreateFd(out_path, &out_fd);
    ds_.options_->limited_only = true;
    ds_.options_->stream_to_socket = true;
    ds_.options_->do_screenshot = false;
    ds_.options_->multi_display_screenshot = true;
    RedirectOutputToFd(out_fd);

    GenerateBugreport();
    std::string screenshot_path = ds_.GetPath(ds_.CalledByApi() ? "-screenshots-zip.tmp" : ".zip");
    OpenArchive(screenshot_path.c_str(), &handle_);

    ZipEntry entry;
    for (auto& [_, path] : ds_.screenshot_path_by_display_id_) {
        std::filesystem::path p(path);
        VerifyEntry(handle_, p.filename().string(), &entry);
    }
    android::base::RemoveFileIfExists(screenshot_path);
}

class ProgressTest : public DumpstateBaseTest {
  public:
    Progress GetInstance(int32_t max, double growth_factor, const std::string& path = "") {