chromium/chrome/test/base/chromeos/crosier/adb_helper.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/test/base/chromeos/crosier/adb_helper.h"

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "chrome/test/base/chromeos/crosier/helper/test_sudo_helper_client.h"

namespace {

// Android vendtor key to authorize adb connection.
constexpr char kArcKey[] = R"(-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCnHNzujonYRLoI
F2pyJX1SSrqmiT/3rTRCP1X0pj1V/sPGwgvIr+3QjZehLUGRQL0wneBNXd6EVrST
drO4cOPwSxRJjCf+/PtS1nwkz+o/BGn5yhNppdSro7aPoQxEVM8qLtN5Ke9tx/zE
ggxpF8D3XBC6Los9lAkyesZI6xqXESeofOYu3Hndzfbz8rAjC0X+p6Sx561Bt1dn
T7k2cP0mwWfITjW8tAhzmKgL4tGcgmoLhMHl9JgScFBhW2Nd0QAR4ACyVvryJ/Xa
2L6T2YpUjqWEDbiJNEApFb+m+smIbyGz0H/Kj9znoRs84z3/8rfyNQOyf7oqBpr2
52XG4totAgMBAAECggEARisKYWicXKDO9CLQ4Uj4jBswsEilAVxKux5Y+zbqPjeR
AN3tkMC+PHmXl2enRlRGnClOS24ExtCZVenboLBWJUmBJTiieqDC7o985QAgPYGe
9fFxoUSuPbuqJjjbK73olq++v/tpu1Djw6dPirkcn0CbDXIJqTuFeRqwM2H0ckVl
mVGUDgATckY0HWPyTBIzwBYIQTvAYzqFHmztcUahQrfi9XqxnySI91no8X6fR323
R8WQ44atLWO5TPCu5JEHCwuTzsGEG7dEEtRQUxAsH11QC7S53tqf10u40aT3bXUh
XV62ol9Zk7h3UrrlT1h1Ae+EtgIbhwv23poBEHpRQQKBgQDeUJwLfWQj0xHO+Jgl
gbMCfiPYvjJ9yVcW4ET4UYnO6A9bf0aHOYdDcumScWHrA1bJEFZ/cqRvqUZsbSsB
+thxa7gjdpZzBeSzd7M+Ygrodi6KM/ojSQMsen/EbRFerZBvsXimtRb88NxTBIW1
RXRPLRhHt+VYEF/wOVkNZ5c2eQKBgQDAbwNkkVFTD8yQJFxZZgr1F/g/nR2IC1Yb
ylusFztLG998olxUKcWGGMoF7JjlM6pY3nt8qJFKek9bRJqyWSqS4/pKR7QTU4Nl
a+gECuD3f28qGFgmay+B7Fyi9xmBAsGINyVxvGyKH95y3QICw1V0Q8uuNwJW2feo
3+UD2/rkVQKBgFloh+ljC4QQ3gekGOR0rf6hpl8D1yCZecn8diB8AnVRBOQiYsX9
j/XDYEaCDQRMOnnwdSkafSFfLbBrkzFfpe6viMXSap1l0F2RFWhQW9yzsvHoB4Br
W7hmp73is2qlWQJimIhLKiyd3a4RkoidnzI8i5hEUBtDsqHVHohykfDZAoGABNhG
q5eFBqRVMCPaN138VKNf2qon/i7a4iQ8Hp8PHRr8i3TDAlNy56dkHrYQO2ULmuUv
Erpjvg5KRS/6/RaFneEjgg9AF2R44GrREpj7hP+uWs72GTGFpq2+v1OdTsQ0/yr0
RGLMEMYwoY+y50Lnud+jFyXHZ0xhkdzhNTGqpWkCgYBigHVt/p8uKlTqhlSl6QXw
1AyaV/TmfDjzWaNjmnE+DxQfXzPi9G+cXONdwD0AlRM1NnBRN+smh2B4RBeU515d
x5RpTRFgzayt0I4Rt6QewKmAER3FbbPzaww2pkfH1zr4GJrKQuceWzxUf46K38xl
yee+dcuGhs9IGBOEEF7lFA==
-----END PRIVATE KEY-----)";

// Install the vendor key in the given path and return the full path to the key
// file.
base::FilePath InstallVendorKey(const base::FilePath& path) {
  base::FilePath key_file = path.Append("crosier.adb_key");
  base::WriteFile(key_file, kArcKey);

  constexpr char command_template[] = R"(
    KEY_DIR="%s"
    chown -R root.root "$KEY_DIR"
    chmod 0755 "$KEY_DIR"
    chmod 0600 "%s"
  )";
  auto result = TestSudoHelperClient().RunCommand(base::StringPrintf(
      command_template, path.value().c_str(), key_file.value().c_str()));
  CHECK_EQ(result.return_code, 0);

  return key_file;
}

// Removes the vendor key files.
void RemoveVendorKey(base::ScopedTempDir dir) {
  if (!dir.IsValid()) {
    return;
  }

  constexpr char command_template[] = R"(
     rm -rf "%s"
  )";
  auto result = TestSudoHelperClient().RunCommand(
      base::StringPrintf(command_template, dir.Take().value().c_str()));
  CHECK_EQ(result.return_code, 0);
}

void KillServer() {
  // `adb kill-server` is not reliable (crbug.com/855325).
  // Not using `killall` since it can wait for orphan adb processes indefinitely
  // (b/137797801).
  //
  // Use `kill -9` directly.
  TestSudoHelperClient().RunCommand(R"(
     while pgrep adb; do
       kill -9 $(pgrep adb)
       sleep 0.1
     done
  )");
}

void GiveItSomeTime(base::TimeDelta t) {
  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, run_loop.QuitClosure(), t);
  run_loop.Run();
}

}  // namespace

AdbHelper::AdbHelper() = default;

AdbHelper::~AdbHelper() {
  if (!initialized_) {
    return;
  }

  KillServer();

  base::ScopedAllowBlockingForTesting allow_blocking;
  RemoveVendorKey(std::move(vendor_key_dir_));
}

void AdbHelper::Intialize() {
  initialized_ = true;

  KillServer();

  base::ScopedAllowBlockingForTesting allow_blocking;
  CHECK(vendor_key_dir_.CreateUniqueTempDir())
      << "Failed to create temp dir to hold vendor key.";
  vendor_key_file_ = InstallVendorKey(vendor_key_dir_.GetPath());
  CHECK(!vendor_key_file_.empty());

  constexpr char command_template[] = R"(
      ADB_VENDOR_KEYS=%s adb start-server
  )";
  auto result = TestSudoHelperClient().RunCommand(
      base::StringPrintf(command_template, vendor_key_file_.value().c_str()));
  CHECK_EQ(result.return_code, 0);

  WaitForDevice();
  CHECK(!serial_.empty());
}

bool AdbHelper::InstallApk(const base::FilePath& apk_path) {
  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    if (!base::PathExists(apk_path)) {
      LOG(ERROR) << "Apk file does not exist: " << apk_path;
      return false;
    }
  }

  // Disable apk check.
  auto result = TestSudoHelperClient().RunCommand(R"(
    adb shell settings put global verifier_verify_adb_installs 0
  )");
  CHECK_EQ(result.return_code, 0);

  // Install the apk.
  const bool success =
      Command(base::StringPrintf(R"(install "%s")", apk_path.value().c_str()));
  if (success) {
    return true;
  }

  // Dump logcat to debug the installation failure. The output of `logcat` would
  // be in sudo helper command output so not dumping it again here.
  Command("logcat -d");
  return false;
}

bool AdbHelper::Command(const std::string_view command) {
  auto result = TestSudoHelperClient().RunCommand(
      base::StrCat({"ADB_VENDOR_KEYS=", vendor_key_file_.value(), " adb -s ",
                    serial_, " ", command}));
  return result.return_code == 0;
}

void AdbHelper::WaitForDevice() {
  constexpr char kEmulatorPrefix[] = "emulator-";
  constexpr char kStateDevice[] = "device";

  constexpr char command_template[] = R"(
    ADB_VENDOR_KEYS=%s adb devices
  )";
  const std::string adb_devices =
      base::StringPrintf(command_template, vendor_key_file_.value().c_str());

  do {
    GiveItSomeTime(base::Milliseconds(500));

    // List all devices.
    auto result = TestSudoHelperClient().RunCommand(adb_devices);
    CHECK_EQ(result.return_code, 0);

    // Search for the first emulator device with "device" state.
    const auto lines = base::SplitString(
        result.output, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
    for (const auto& line : lines) {
      const auto fields = base::SplitString(line, " \t", base::TRIM_WHITESPACE,
                                            base::SPLIT_WANT_NONEMPTY);
      if (fields.size() != 2) {
        continue;
      }

      if (base::StartsWith(fields[0], kEmulatorPrefix) &&
          fields[1] == kStateDevice) {
        serial_ = fields[0];
        break;
      }
    }
  } while (serial_.empty());
}