blob: e1ddf77a236b54754c2581ff823efe6a20c400d1 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/test/chromedriver/chrome/device_manager.h"
#include <algorithm>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/test/chromedriver/chrome/adb.h"
#include "chrome/test/chromedriver/chrome/status.h"
const char kChromeCmdLineFile[] = "/data/local/tmp/chrome-command-line";
Device::Device(
const std::string& device_serial, Adb* adb,
base::Callback<void()> release_callback)
: serial_(device_serial),
adb_(adb),
release_callback_(release_callback) {}
Device::~Device() {
release_callback_.Run();
}
// Only allow completely alpha exec names.
bool IsValidExecName(const std::string& exec_name) {
return std::find_if_not(exec_name.begin(), exec_name.end(), [](char ch) {
return base::IsAsciiAlpha(ch);
}) == exec_name.end();
}
Status Device::SetUp(const std::string& package,
const std::string& activity,
const std::string& process,
const std::string& device_socket,
const std::string& exec_name,
const std::string& args,
bool use_running_app,
int* devtools_port) {
if (!active_package_.empty())
return Status(kUnknownError,
active_package_ + " was launched and has not been quit");
Status status = adb_->CheckAppInstalled(serial_, package);
if (status.IsError())
return status;
std::string known_activity;
std::string command_line_file;
std::string known_device_socket;
std::string known_exec_name;
bool use_debug_flag = false;
if (package.compare("org.chromium.content_shell_apk") == 0) {
// Chromium content shell.
known_activity = ".ContentShellActivity";
known_device_socket = "content_shell_devtools_remote";
command_line_file = "/data/local/tmp/content-shell-command-line";
known_exec_name = "content_shell";
} else if (package.find("chrome") != std::string::npos &&
package.find("webview") == std::string::npos) {
// Chrome.
known_activity = "com.google.android.apps.chrome.Main";
known_device_socket = "chrome_devtools_remote";
command_line_file = kChromeCmdLineFile;
known_exec_name = "chrome";
use_debug_flag = true;
} else if (!exec_name.empty() && IsValidExecName(exec_name)) {
// Allow directly specifying executable file name -- uncommon scenario.
known_exec_name = exec_name;
known_device_socket = device_socket;
command_line_file = base::StringPrintf("/data/local/tmp/%s_devtools_remote",
exec_name.c_str());
use_debug_flag = true;
}
if (!use_running_app) {
if (use_debug_flag) {
// Some apps (such as Google Chrome) read command line from different
// locations depending on if the app debug flag is set. When the debug
// flag is not set, they use a location not writable by ChromeDriver
// (except on rooted devices). Setting the debug flag allows the apps to
// read command line from a location writable by ChromeDriver.
//
// This is needed only when use_running_app is false, for two reasons:
// * It's too late to set the command line if the app is already running.
// * Setting the debug flag has the side effect of shutting down the app,
// preventing use_running_app from working.
status = adb_->SetDebugApp(serial_, package);
if (status.IsError())
return status;
}
status = adb_->ClearAppData(serial_, package);
if (status.IsError())
return status;
if (!known_activity.empty()) {
if (!activity.empty() ||
!process.empty())
return Status(kUnknownError, "known package " + package +
" does not accept activity/process");
} else if (activity.empty()) {
return Status(kUnknownError, "WebView apps require activity name");
}
if (!command_line_file.empty()) {
status = adb_->SetCommandLineFile(serial_, command_line_file,
known_exec_name, args);
if (status.IsError())
return Status(
kUnknownError,
"Failed to set Chrome's command line file on device " + serial_,
status);
}
status = adb_->Launch(serial_, package,
known_activity.empty() ? activity : known_activity);
if (status.IsError())
return status;
active_package_ = package;
}
return this->ForwardDevtoolsPort(package, process, &known_device_socket,
devtools_port);
}
Status Device::ForwardDevtoolsPort(const std::string& package,
const std::string& process,
std::string* device_socket,
int* devtools_port) {
if (device_socket->empty()) {
// Assume this is a WebView app.
int pid;
Status status = adb_->GetPidByName(serial_,
process.empty() ? package : process,
&pid);
if (status.IsError()) {
if (process.empty())
status.AddDetails(
"process name must be specified if not equal to package name");
return status;
}
std::string socket_name;
// The leading '@' means abstract UNIX sockets. Some apps have a custom
// substring between the required "webview_devtools_remote_" prefix and
// their PID, which Chrome DevTools accepts and we also should.
std::string pattern =
base::StringPrintf("@webview_devtools_remote_.*%d", pid);
status = adb_->GetSocketByPattern(serial_, pattern, &socket_name);
if (status.IsError()) {
if (socket_name.empty())
status.AddDetails(
"make sure the app has its WebView configured for debugging");
return status;
}
// When used in adb with "localabstract:", the leading '@' is not needed.
*device_socket = socket_name.substr(1);
}
return adb_->ForwardPort(serial_, *device_socket, devtools_port);
}
Status Device::TearDown() {
if (!active_package_.empty()) {
std::string response;
Status status = adb_->ForceStop(serial_, active_package_);
if (status.IsError())
return status;
active_package_ = "";
}
return Status(kOk);
}
DeviceManager::DeviceManager(Adb* adb) : adb_(adb) {
CHECK(adb_);
}
DeviceManager::~DeviceManager() {}
Status DeviceManager::AcquireDevice(std::unique_ptr<Device>* device) {
std::vector<std::string> devices;
Status status = adb_->GetDevices(&devices);
if (status.IsError())
return status;
if (devices.empty())
return Status(kUnknownError, "There are no devices online");
base::AutoLock lock(devices_lock_);
status = Status(kUnknownError, "All devices are in use (" +
base::NumberToString(devices.size()) +
" online)");
std::vector<std::string>::iterator iter;
for (iter = devices.begin(); iter != devices.end(); iter++) {
if (!IsDeviceLocked(*iter)) {
device->reset(LockDevice(*iter));
status = Status(kOk);
break;
}
}
return status;
}
Status DeviceManager::AcquireSpecificDevice(const std::string& device_serial,
std::unique_ptr<Device>* device) {
std::vector<std::string> devices;
Status status = adb_->GetDevices(&devices);
if (status.IsError())
return status;
if (!base::Contains(devices, device_serial))
return Status(kUnknownError,
"Device " + device_serial + " is not online");
base::AutoLock lock(devices_lock_);
if (IsDeviceLocked(device_serial)) {
status = Status(kUnknownError,
"Device " + device_serial + " is already in use");
} else {
device->reset(LockDevice(device_serial));
status = Status(kOk);
}
return status;
}
void DeviceManager::ReleaseDevice(const std::string& device_serial) {
base::AutoLock lock(devices_lock_);
active_devices_.remove(device_serial);
}
Device* DeviceManager::LockDevice(const std::string& device_serial) {
active_devices_.push_back(device_serial);
return new Device(device_serial, adb_,
base::Bind(&DeviceManager::ReleaseDevice, base::Unretained(this),
device_serial));
}
bool DeviceManager::IsDeviceLocked(const std::string& device_serial) {
return base::Contains(active_devices_, device_serial);
}