1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/test/chromedriver/chrome/device_manager.h"
6
7#include <algorithm>
8#include <vector>
9
10#include "base/bind.h"
11#include "base/bind_helpers.h"
12#include "base/callback.h"
13#include "base/logging.h"
14#include "base/strings/string_number_conversions.h"
15#include "base/strings/stringprintf.h"
16#include "chrome/test/chromedriver/chrome/adb.h"
17#include "chrome/test/chromedriver/chrome/status.h"
18
19// TODO(craigdh): Remove once Chromedriver no longer supports pre-m33 Chrome.
20const char* kChromeCmdLineFileBeforeM33 = "/data/local/chrome-command-line";
21const char* kChromeCmdLineFile = "/data/local/tmp/chrome-command-line";
22
23Device::Device(
24    const std::string& device_serial, Adb* adb,
25    base::Callback<void()> release_callback)
26    : serial_(device_serial),
27      adb_(adb),
28      release_callback_(release_callback) {}
29
30Device::~Device() {
31  release_callback_.Run();
32}
33
34Status Device::SetUp(const std::string& package,
35                     const std::string& activity,
36                     const std::string& process,
37                     const std::string& args,
38                     bool use_running_app,
39                     int port) {
40  if (!active_package_.empty())
41    return Status(kUnknownError,
42        active_package_ + " was launched and has not been quit");
43
44  Status status = adb_->CheckAppInstalled(serial_, package);
45  if (status.IsError())
46    return status;
47
48  std::string known_activity;
49  std::string command_line_file;
50  std::string device_socket;
51  std::string exec_name;
52  if (package.compare("org.chromium.content_shell_apk") == 0) {
53    // Chromium content shell.
54    known_activity = ".ContentShellActivity";
55    device_socket = "content_shell_devtools_remote";
56    command_line_file = "/data/local/tmp/content-shell-command-line";
57    exec_name = "content_shell";
58  } else if (package.compare("org.chromium.chrome.shell") == 0) {
59    // ChromeShell
60    known_activity = ".ChromeShellActivity";
61    device_socket = "chrome_shell_devtools_remote";
62    command_line_file = "/data/local/tmp/chrome-shell-command-line";
63    exec_name = "chrome_shell";
64  } else if (package.find("chrome") != std::string::npos &&
65             package.find("webview") == std::string::npos) {
66    // Chrome.
67    known_activity = "com.google.android.apps.chrome.Main";
68    device_socket = "chrome_devtools_remote";
69    command_line_file = kChromeCmdLineFileBeforeM33;
70    exec_name = "chrome";
71    status = adb_->SetDebugApp(serial_, package);
72    if (status.IsError())
73      return status;
74  }
75
76  if (!use_running_app) {
77    status = adb_->ClearAppData(serial_, package);
78    if (status.IsError())
79      return status;
80
81    if (!known_activity.empty()) {
82      if (!activity.empty() ||
83          !process.empty())
84        return Status(kUnknownError, "known package " + package +
85                      " does not accept activity/process");
86    } else if (activity.empty()) {
87      return Status(kUnknownError, "WebView apps require activity name");
88    }
89
90    if (!command_line_file.empty()) {
91      // If Chrome is set as the debug app it looks in /data/local/tmp/.
92      // There's no way to know if this is set, so write to both locations.
93      // This can be removed once support for pre-M33 is no longer needed.
94      if (command_line_file == kChromeCmdLineFileBeforeM33) {
95        status = adb_->SetCommandLineFile(
96            serial_, kChromeCmdLineFileBeforeM33, exec_name, args);
97        Status status2 = adb_->SetCommandLineFile(
98            serial_, kChromeCmdLineFile, exec_name, args);
99        if (status.IsError() && status2.IsError())
100          return Status(kUnknownError,
101              "Failed to set Chrome's command line file on device " + serial_);
102      } else {
103        status = adb_->SetCommandLineFile(
104            serial_, command_line_file, exec_name, args);
105        if (status.IsError())
106          return status;
107      }
108    }
109
110    status = adb_->Launch(serial_, package,
111                          known_activity.empty() ? activity : known_activity);
112    if (status.IsError())
113      return status;
114
115    active_package_ = package;
116  }
117  this->ForwardDevtoolsPort(package, process, device_socket, port);
118
119  return status;
120}
121
122Status Device::ForwardDevtoolsPort(const std::string& package,
123                                   const std::string& process,
124                                   std::string& device_socket,
125                                   int port) {
126  if (device_socket.empty()) {
127    // Assume this is a WebView app.
128    int pid;
129    Status status = adb_->GetPidByName(serial_,
130                                       process.empty() ? package : process,
131                                       &pid);
132    if (status.IsError()) {
133      if (process.empty())
134        status.AddDetails(
135            "process name must be specified if not equal to package name");
136      return status;
137    }
138    device_socket = base::StringPrintf("webview_devtools_remote_%d", pid);
139  }
140
141  return adb_->ForwardPort(serial_, port, device_socket);
142}
143
144Status Device::TearDown() {
145  if (!active_package_.empty()) {
146    std::string response;
147    Status status = adb_->ForceStop(serial_, active_package_);
148    if (status.IsError())
149      return status;
150    active_package_ = "";
151  }
152  return Status(kOk);
153}
154
155DeviceManager::DeviceManager(Adb* adb) : adb_(adb) {
156  CHECK(adb_);
157}
158
159DeviceManager::~DeviceManager() {}
160
161Status DeviceManager::AcquireDevice(scoped_ptr<Device>* device) {
162  std::vector<std::string> devices;
163  Status status = adb_->GetDevices(&devices);
164  if (status.IsError())
165    return status;
166
167  if (devices.empty())
168    return Status(kUnknownError, "There are no devices online");
169
170  base::AutoLock lock(devices_lock_);
171  status = Status(kUnknownError, "All devices are in use (" +
172                  base::IntToString(devices.size()) + " online)");
173  std::vector<std::string>::iterator iter;
174  for (iter = devices.begin(); iter != devices.end(); iter++) {
175    if (!IsDeviceLocked(*iter)) {
176      device->reset(LockDevice(*iter));
177      status = Status(kOk);
178      break;
179    }
180  }
181  return status;
182}
183
184Status DeviceManager::AcquireSpecificDevice(
185    const std::string& device_serial, scoped_ptr<Device>* device) {
186  std::vector<std::string> devices;
187  Status status = adb_->GetDevices(&devices);
188  if (status.IsError())
189    return status;
190
191  if (std::find(devices.begin(), devices.end(), device_serial) == devices.end())
192    return Status(kUnknownError,
193        "Device " + device_serial + " is not online");
194
195  base::AutoLock lock(devices_lock_);
196  if (IsDeviceLocked(device_serial)) {
197    status = Status(kUnknownError,
198        "Device " + device_serial + " is already in use");
199  } else {
200    device->reset(LockDevice(device_serial));
201    status = Status(kOk);
202  }
203  return status;
204}
205
206void DeviceManager::ReleaseDevice(const std::string& device_serial) {
207  base::AutoLock lock(devices_lock_);
208  active_devices_.remove(device_serial);
209}
210
211Device* DeviceManager::LockDevice(const std::string& device_serial) {
212  active_devices_.push_back(device_serial);
213  return new Device(device_serial, adb_,
214      base::Bind(&DeviceManager::ReleaseDevice, base::Unretained(this),
215                 device_serial));
216}
217
218bool DeviceManager::IsDeviceLocked(const std::string& device_serial) {
219  return std::find(active_devices_.begin(), active_devices_.end(),
220                   device_serial) != active_devices_.end();
221}
222
223