blob: cae38022f62efc51c455c2832928b2636b1389b3 [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 <stddef.h>
#include "base/macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/devtools/device/android_device_manager.h"
namespace {
#define SEPARATOR "======== output separator ========"
const char kAllCommands[] = "shell:"
"getprop ro.product.model\n"
"echo " SEPARATOR "\n"
"dumpsys window policy\n"
"echo " SEPARATOR "\n"
"ps\n"
"echo " SEPARATOR "\n"
"cat /proc/net/unix\n"
"echo " SEPARATOR "\n"
"dumpsys user\n";
const char kSeparator[] = SEPARATOR;
#undef SEPARATOR
const char kScreenSizePrefix[] = "mStable=";
const char kUserInfoPrefix[] = "UserInfo{";
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.google.android.apps.chrome",
kChromeDefaultSocket,
"Chromium"
},
{
"com.chrome.canary",
kChromeDefaultSocket,
"Chrome Canary"
},
{
"com.chrome.dev",
kChromeDefaultSocket,
"Chrome Dev"
},
{
"com.chrome.beta",
kChromeDefaultSocket,
"Chrome Beta"
},
{
"com.android.chrome",
kChromeDefaultSocket,
kChromeDefaultName
},
{
"org.chromium.android_webview.shell",
"webview_devtools_remote",
"WebView Test Shell"
},
{
"org.chromium.content_shell_apk",
"content_shell_devtools_remote",
"Content Shell"
},
{
"org.chromium.chrome",
kChromeDefaultSocket,
"Chromium"
},
};
const BrowserDescriptor* FindBrowserDescriptor(const std::string& package) {
size_t count = arraysize(kBrowserDescriptors);
for (size_t i = 0; i < count; i++) {
if (kBrowserDescriptors[i].package == package)
return &kBrowserDescriptors[i];
}
return nullptr;
}
bool BrowserCompare(const AndroidDeviceManager::BrowserInfo& a,
const AndroidDeviceManager::BrowserInfo& b) {
size_t count = arraysize(kBrowserDescriptors);
for (size_t i = 0; i < count; i++) {
bool isA = kBrowserDescriptors[i].display_name == a.display_name;
bool isB = kBrowserDescriptors[i].display_name == b.display_name;
if (isA != isB)
return isA;
if (isA && isB)
break;
}
return a.socket_name < b.socket_name;
}
using StringMap = std::map<std::string, std::string>;
void MapProcessesToPackages(const std::string& response,
StringMap* pid_to_package,
StringMap* pid_to_user) {
// Parse 'ps' output which on Android looks like this:
//
// USER PID PPID VSIZE RSS WCHAN PC ? NAME
//
for (const base::StringPiece& line :
base::SplitStringPiece(response, "\n", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
std::vector<std::string> fields =
base::SplitString(line, " \r", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
if (fields.size() < 9)
continue;
std::string pid = fields[1];
(*pid_to_package)[pid] = fields[8];
(*pid_to_user)[pid] = fields[0];
}
}
StringMap MapSocketsToProcesses(const std::string& response) {
// 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;
for (const base::StringPiece& line :
base::SplitStringPiece(response, "\n", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
std::vector<std::string> fields =
base::SplitString(line, " \r", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
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(kDevToolsSocketSuffix);
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 + strlen(kDevToolsSocketSuffix);
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;
}
gfx::Size ParseScreenSize(base::StringPiece str) {
std::vector<base::StringPiece> pairs =
base::SplitStringPiece(str, "-", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
if (pairs.size() != 2)
return gfx::Size();
int width;
int height;
std::vector<base::StringPiece> numbers =
base::SplitStringPiece(pairs[1].substr(1, pairs[1].size() - 2), ",",
base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (numbers.size() != 2 ||
!base::StringToInt(numbers[0], &width) ||
!base::StringToInt(numbers[1], &height))
return gfx::Size();
return gfx::Size(width, height);
}
gfx::Size ParseWindowPolicyResponse(const std::string& response) {
for (const base::StringPiece& line :
base::SplitStringPiece(response, "\r", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
size_t pos = line.find(kScreenSizePrefix);
if (pos != base::StringPiece::npos) {
return ParseScreenSize(
line.substr(pos + strlen(kScreenSizePrefix)));
}
}
return gfx::Size();
}
StringMap MapIdsToUsers(const std::string& response) {
// Parse 'dumpsys user' output which looks like this:
// Users:
// UserInfo{0:Test User:13} serialNo=0
// Created: <unknown>
// Last logged in: +17m18s871ms ago
// UserInfo{10:User with : (colon):10} serialNo=10
// Created: +3d4h35m1s139ms ago
// Last logged in: +17m26s287ms ago
StringMap id_to_username;
for (const base::StringPiece& line :
base::SplitStringPiece(response, "\r", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
size_t pos = line.find(kUserInfoPrefix);
if (pos != std::string::npos) {
base::StringPiece fields = line.substr(pos + strlen(kUserInfoPrefix));
size_t first_pos = fields.find_first_of(":");
size_t last_pos = fields.find_last_of(":");
if (first_pos != std::string::npos && last_pos != std::string::npos) {
std::string id = fields.substr(0, first_pos).as_string();
std::string name = fields.substr(first_pos + 1,
last_pos - first_pos - 1).as_string();
id_to_username[id] = name;
}
}
}
return id_to_username;
}
std::string GetUserName(const std::string& unix_user,
const StringMap id_to_username) {
// Parse username as returned by ps which looks like 'u0_a31'
// where '0' is user id and '31' is app id.
if (!unix_user.empty() && unix_user[0] == 'u') {
size_t pos = unix_user.find('_');
if (pos != std::string::npos) {
auto it = id_to_username.find(unix_user.substr(1, pos - 1));
if (it != id_to_username.end())
return it->second;
}
}
return std::string();
}
AndroidDeviceManager::BrowserInfo::Type
GetBrowserType(const std::string& socket) {
if (base::StartsWith(socket, kChromeDefaultSocket,
base::CompareCase::SENSITIVE)) {
return AndroidDeviceManager::BrowserInfo::kTypeChrome;
}
if (base::StartsWith(socket, kWebViewSocketPrefix,
base::CompareCase::SENSITIVE)) {
return AndroidDeviceManager::BrowserInfo::kTypeWebView;
}
return AndroidDeviceManager::BrowserInfo::kTypeOther;
}
void ReceivedResponse(const AndroidDeviceManager::DeviceInfoCallback& callback,
int result,
const std::string& response) {
AndroidDeviceManager::DeviceInfo device_info;
if (result < 0) {
callback.Run(device_info);
return;
}
std::vector<std::string> outputs = base::SplitStringUsingSubstr(
response, kSeparator, base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (outputs.size() != 5) {
callback.Run(device_info);
return;
}
device_info.connected = true;
device_info.model = outputs[0];
device_info.screen_size = ParseWindowPolicyResponse(outputs[1]);
StringMap pid_to_package;
StringMap pid_to_user;
MapProcessesToPackages(outputs[2], &pid_to_package, &pid_to_user);
StringMap socket_to_pid = MapSocketsToProcesses(outputs[3]);
StringMap id_to_username = MapIdsToUsers(outputs[4]);
std::set<std::string> used_pids;
for (const auto& pair : socket_to_pid)
used_pids.insert(pair.second);
for (const auto& pair : pid_to_package) {
std::string pid = pair.first;
std::string package = pair.second;
if (used_pids.find(pid) == used_pids.end()) {
const BrowserDescriptor* descriptor = FindBrowserDescriptor(package);
if (descriptor)
socket_to_pid[descriptor->socket] = pid;
}
}
for (const auto& pair : socket_to_pid) {
std::string socket = pair.first;
std::string pid = pair.second;
std::string package;
auto pit = pid_to_package.find(pid);
if (pit != pid_to_package.end())
package = pit->second;
AndroidDeviceManager::BrowserInfo browser_info;
browser_info.socket_name = socket;
browser_info.type = GetBrowserType(socket);
browser_info.display_name =
AndroidDeviceManager::GetBrowserName(socket, package);
auto uit = pid_to_user.find(pid);
if (uit != pid_to_user.end())
browser_info.user = GetUserName(uit->second, id_to_username);
device_info.browser_info.push_back(browser_info);
}
std::sort(device_info.browser_info.begin(),
device_info.browser_info.end(),
&BrowserCompare);
callback.Run(device_info);
}
} // namespace
// static
std::string AndroidDeviceManager::GetBrowserName(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 AndroidDeviceManager::QueryDeviceInfo(
const RunCommandCallback& command_callback,
const DeviceInfoCallback& callback) {
command_callback.Run(
kAllCommands,
base::Bind(&ReceivedResponse, callback));
}