blob: 2b44acd78ddcf8201e001891bba891ff40e813b0 [file] [log] [blame]
// Copyright 2014 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/browser/devtools/device/adb/adb_device_info_query.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
namespace {
const char kDeviceModelCommand[] = "shell:getprop ro.product.model";
const char kOpenedUnixSocketsCommand[] = "shell:cat /proc/net/unix";
const char kListProcessesCommand[] = "shell:ps";
const char kDumpsysCommand[] = "shell:dumpsys window policy";
const char kDumpsysScreenSizePrefix[] = "mStable=";
const char kDevToolsSocketSuffix[] = "_devtools_remote";
const char kChromeDefaultName[] = "Chrome";
const char kChromeDefaultSocket[] = "chrome_devtools_remote";
const char kWebViewSocketPrefix[] = "webview_devtools_remote";
const char kWebViewNameTemplate[] = "WebView in %s";
struct BrowserDescriptor {
const char* package;
const char* socket;
const char* display_name;
};
const BrowserDescriptor kBrowserDescriptors[] = {
{
"com.android.chrome",
kChromeDefaultSocket,
kChromeDefaultName
},
{
"com.chrome.beta",
kChromeDefaultSocket,
"Chrome Beta"
},
{
"com.google.android.apps.chrome_dev",
kChromeDefaultSocket,
"Chrome Dev"
},
{
"com.chrome.canary",
kChromeDefaultSocket,
"Chrome Canary"
},
{
"com.google.android.apps.chrome",
kChromeDefaultSocket,
"Chromium"
},
{
"org.chromium.content_shell_apk",
"content_shell_devtools_remote",
"Content Shell"
},
{
"org.chromium.chrome.shell",
"chrome_shell_devtools_remote",
"Chrome Shell"
},
{
"org.chromium.android_webview.shell",
"webview_devtools_remote",
"WebView Test Shell"
}
};
const BrowserDescriptor* FindBrowserDescriptor(const std::string& package) {
int count = sizeof(kBrowserDescriptors) / sizeof(kBrowserDescriptors[0]);
for (int i = 0; i < count; i++)
if (kBrowserDescriptors[i].package == package)
return &kBrowserDescriptors[i];
return NULL;
}
typedef std::map<std::string, std::string> StringMap;
static void MapProcessesToPackages(const std::string& response,
StringMap& pid_to_package,
StringMap& package_to_pid) {
// Parse 'ps' output which on Android looks like this:
//
// USER PID PPID VSIZE RSS WCHAN PC ? NAME
//
std::vector<std::string> entries;
Tokenize(response, "\n", &entries);
for (size_t i = 1; i < entries.size(); ++i) {
std::vector<std::string> fields;
Tokenize(entries[i], " \r", &fields);
if (fields.size() < 9)
continue;
std::string pid = fields[1];
std::string package = fields[8];
pid_to_package[pid] = package;
package_to_pid[package] = pid;
}
}
static StringMap MapSocketsToProcesses(const std::string& response,
const std::string& channel_pattern) {
// Parse 'cat /proc/net/unix' output which on Android looks like this:
//
// Num RefCount Protocol Flags Type St Inode Path
// 00000000: 00000002 00000000 00010000 0001 01 331813 /dev/socket/zygote
// 00000000: 00000002 00000000 00010000 0001 01 358606 @xxx_devtools_remote
// 00000000: 00000002 00000000 00010000 0001 01 347300 @yyy_devtools_remote
//
// We need to find records with paths starting from '@' (abstract socket)
// and containing the channel pattern ("_devtools_remote").
StringMap socket_to_pid;
std::vector<std::string> entries;
Tokenize(response, "\n", &entries);
for (size_t i = 1; i < entries.size(); ++i) {
std::vector<std::string> fields;
Tokenize(entries[i], " \r", &fields);
if (fields.size() < 8)
continue;
if (fields[3] != "00010000" || fields[5] != "01")
continue;
std::string path_field = fields[7];
if (path_field.size() < 1 || path_field[0] != '@')
continue;
size_t socket_name_pos = path_field.find(channel_pattern);
if (socket_name_pos == std::string::npos)
continue;
std::string socket = path_field.substr(1);
std::string pid;
size_t socket_name_end = socket_name_pos + channel_pattern.size();
if (socket_name_end < path_field.size() &&
path_field[socket_name_end] == '_') {
pid = path_field.substr(socket_name_end + 1);
}
socket_to_pid[socket] = pid;
}
return socket_to_pid;
}
} // namespace
// static
AndroidDeviceManager::BrowserInfo::Type
AdbDeviceInfoQuery::GetBrowserType(const std::string& socket) {
if (socket.find(kChromeDefaultSocket) == 0)
return AndroidDeviceManager::BrowserInfo::kTypeChrome;
if (socket.find(kWebViewSocketPrefix) == 0)
return AndroidDeviceManager::BrowserInfo::kTypeWebView;
return AndroidDeviceManager::BrowserInfo::kTypeOther;
}
// static
std::string AdbDeviceInfoQuery::GetDisplayName(const std::string& socket,
const std::string& package) {
if (package.empty()) {
// Derive a fallback display name from the socket name.
std::string name = socket.substr(0, socket.find(kDevToolsSocketSuffix));
name[0] = base::ToUpperASCII(name[0]);
return name;
}
const BrowserDescriptor* descriptor = FindBrowserDescriptor(package);
if (descriptor)
return descriptor->display_name;
if (GetBrowserType(socket) ==
AndroidDeviceManager::BrowserInfo::kTypeWebView)
return base::StringPrintf(kWebViewNameTemplate, package.c_str());
return package;
}
// static
void AdbDeviceInfoQuery::Start(const RunCommandCallback& command_callback,
const DeviceInfoCallback& callback) {
new AdbDeviceInfoQuery(command_callback, callback);
}
AdbDeviceInfoQuery::AdbDeviceInfoQuery(
const RunCommandCallback& command_callback,
const DeviceInfoCallback& callback)
: command_callback_(command_callback),
callback_(callback) {
DCHECK(CalledOnValidThread());
command_callback_.Run(
kDeviceModelCommand,
base::Bind(&AdbDeviceInfoQuery::ReceivedModel, base::Unretained(this)));
}
AdbDeviceInfoQuery::~AdbDeviceInfoQuery() {
}
void AdbDeviceInfoQuery::ReceivedModel(int result,
const std::string& response) {
DCHECK(CalledOnValidThread());
if (result < 0) {
Respond();
return;
}
device_info_.model = response;
device_info_.connected = true;
command_callback_.Run(
kDumpsysCommand,
base::Bind(&AdbDeviceInfoQuery::ReceivedDumpsys, base::Unretained(this)));
}
void AdbDeviceInfoQuery::ReceivedDumpsys(int result,
const std::string& response) {
DCHECK(CalledOnValidThread());
if (result >= 0)
ParseDumpsysResponse(response);
command_callback_.Run(
kListProcessesCommand,
base::Bind(&AdbDeviceInfoQuery::ReceivedProcesses,
base::Unretained(this)));
}
void AdbDeviceInfoQuery::ParseDumpsysResponse(const std::string& response) {
std::vector<std::string> lines;
Tokenize(response, "\r", &lines);
for (size_t i = 0; i < lines.size(); ++i) {
std::string line = lines[i];
size_t pos = line.find(kDumpsysScreenSizePrefix);
if (pos != std::string::npos) {
ParseScreenSize(
line.substr(pos + std::string(kDumpsysScreenSizePrefix).size()));
break;
}
}
}
void AdbDeviceInfoQuery::ParseScreenSize(const std::string& str) {
std::vector<std::string> pairs;
Tokenize(str, "-", &pairs);
if (pairs.size() != 2)
return;
int width;
int height;
std::vector<std::string> numbers;
Tokenize(pairs[1].substr(1, pairs[1].size() - 2), ",", &numbers);
if (numbers.size() != 2 ||
!base::StringToInt(numbers[0], &width) ||
!base::StringToInt(numbers[1], &height))
return;
device_info_.screen_size = gfx::Size(width, height);
}
void AdbDeviceInfoQuery::ReceivedProcesses(
int result,
const std::string& processes_response) {
DCHECK(CalledOnValidThread());
if (result < 0) {
Respond();
return;
}
command_callback_.Run(
kOpenedUnixSocketsCommand,
base::Bind(&AdbDeviceInfoQuery::ReceivedSockets,
base::Unretained(this),
processes_response));
}
void AdbDeviceInfoQuery::ReceivedSockets(
const std::string& processes_response,
int result,
const std::string& sockets_response) {
DCHECK(CalledOnValidThread());
if (result >= 0)
ParseBrowserInfo(processes_response, sockets_response);
Respond();
}
void AdbDeviceInfoQuery::ParseBrowserInfo(
const std::string& processes_response,
const std::string& sockets_response) {
DCHECK(CalledOnValidThread());
StringMap pid_to_package;
StringMap package_to_pid;
MapProcessesToPackages(processes_response, pid_to_package, package_to_pid);
StringMap socket_to_pid = MapSocketsToProcesses(sockets_response,
kDevToolsSocketSuffix);
std::set<std::string> packages_for_running_browsers;
typedef std::map<std::string, int> BrowserMap;
BrowserMap socket_to_unnamed_browser_index;
for (StringMap::iterator it = socket_to_pid.begin();
it != socket_to_pid.end(); ++it) {
std::string socket = it->first;
std::string pid = it->second;
std::string package;
StringMap::iterator pit = pid_to_package.find(pid);
if (pit != pid_to_package.end()) {
package = pit->second;
packages_for_running_browsers.insert(package);
} else {
socket_to_unnamed_browser_index[socket] =
device_info_.browser_info.size();
}
AndroidDeviceManager::BrowserInfo browser_info;
browser_info.socket_name = socket;
browser_info.type = GetBrowserType(socket);
browser_info.display_name = GetDisplayName(socket, package);
device_info_.browser_info.push_back(browser_info);
}
}
void AdbDeviceInfoQuery::Respond() {
DCHECK(CalledOnValidThread());
callback_.Run(device_info_);
delete this;
}