| Index: chrome/browser/devtools/device/adb/adb_device_info_query.cc
|
| diff --git a/chrome/browser/devtools/device/adb/adb_device_info_query.cc b/chrome/browser/devtools/device/adb/adb_device_info_query.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d5cd764d1d8b370a31dcaa53bece2955383a9029
|
| --- /dev/null
|
| +++ b/chrome/browser/devtools/device/adb/adb_device_info_query.cc
|
| @@ -0,0 +1,412 @@
|
| +// 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 kInstalledChromePackagesCommand[] = "shell:pm list packages";
|
| +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, const BrowserDescriptor*> DescriptorMap;
|
| +
|
| +static DescriptorMap FindInstalledBrowserPackages(const std::string& response) {
|
| + // Parse 'pm list packages' output which on Android looks like this:
|
| + //
|
| + // package:com.android.chrome
|
| + // package:com.chrome.beta
|
| + // package:com.example.app
|
| + //
|
| + DescriptorMap package_to_descriptor;
|
| + const std::string package_prefix = "package:";
|
| + std::vector<std::string> entries;
|
| + Tokenize(response, "'\r\n", &entries);
|
| + for (size_t i = 0; i < entries.size(); ++i) {
|
| + if (entries[i].find(package_prefix) != 0)
|
| + continue;
|
| + std::string package = entries[i].substr(package_prefix.size());
|
| + const BrowserDescriptor* descriptor = FindBrowserDescriptor(package);
|
| + if (!descriptor)
|
| + continue;
|
| + package_to_descriptor[descriptor->package] = descriptor;
|
| + }
|
| + return package_to_descriptor;
|
| +}
|
| +
|
| +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;
|
| + 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(
|
| + kInstalledChromePackagesCommand,
|
| + base::Bind(&AdbDeviceInfoQuery::ReceivedPackages,
|
| + 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::ReceivedPackages(
|
| + int result,
|
| + const std::string& packages_response) {
|
| + DCHECK(CalledOnValidThread());
|
| + if (result < 0) {
|
| + Respond();
|
| + return;
|
| + }
|
| + command_callback_.Run(
|
| + kListProcessesCommand,
|
| + base::Bind(&AdbDeviceInfoQuery::ReceivedProcesses,
|
| + base::Unretained(this), packages_response));
|
| +}
|
| +
|
| +void AdbDeviceInfoQuery::ReceivedProcesses(
|
| + const std::string& packages_response,
|
| + 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),
|
| + packages_response,
|
| + processes_response));
|
| +}
|
| +
|
| +void AdbDeviceInfoQuery::ReceivedSockets(
|
| + const std::string& packages_response,
|
| + const std::string& processes_response,
|
| + int result,
|
| + const std::string& sockets_response) {
|
| + DCHECK(CalledOnValidThread());
|
| + if (result >= 0)
|
| + ParseBrowserInfo(packages_response, processes_response, sockets_response);
|
| + Respond();
|
| +}
|
| +
|
| +void AdbDeviceInfoQuery::ParseBrowserInfo(
|
| + const std::string& packages_response,
|
| + const std::string& processes_response,
|
| + const std::string& sockets_response) {
|
| + DCHECK(CalledOnValidThread());
|
| + DescriptorMap package_to_descriptor =
|
| + FindInstalledBrowserPackages(packages_response);
|
| + 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);
|
| + }
|
| +
|
| + // Find installed packages not mapped to browsers.
|
| + typedef std::multimap<std::string, const BrowserDescriptor*>
|
| + DescriptorMultimap;
|
| + DescriptorMultimap socket_to_descriptor;
|
| + for (DescriptorMap::iterator it = package_to_descriptor.begin();
|
| + it != package_to_descriptor.end(); ++it) {
|
| + std::string package = it->first;
|
| + const BrowserDescriptor* descriptor = it->second;
|
| +
|
| + if (packages_for_running_browsers.count(package))
|
| + continue; // This package is already mapped to a browser.
|
| +
|
| + if (package_to_pid.find(package) != package_to_pid.end()) {
|
| + // This package is running but not mapped to a browser.
|
| + socket_to_descriptor.insert(
|
| + DescriptorMultimap::value_type(descriptor->socket, descriptor));
|
| + continue;
|
| + }
|
| + }
|
| +
|
| + // Try naming remaining unnamed browsers.
|
| + for (DescriptorMultimap::iterator it = socket_to_descriptor.begin();
|
| + it != socket_to_descriptor.end(); ++it) {
|
| + std::string socket = it->first;
|
| + const BrowserDescriptor* descriptor = it->second;
|
| +
|
| + if (socket_to_descriptor.count(socket) != 1)
|
| + continue; // No definitive match.
|
| +
|
| + BrowserMap::iterator bit = socket_to_unnamed_browser_index.find(socket);
|
| + if (bit != socket_to_unnamed_browser_index.end()) {
|
| + device_info_.browser_info[bit->second].display_name =
|
| + descriptor->display_name;
|
| + }
|
| + }
|
| +}
|
| +
|
| +void AdbDeviceInfoQuery::Respond() {
|
| + DCHECK(CalledOnValidThread());
|
| + callback_.Run(device_info_);
|
| + delete this;
|
| +}
|
|
|