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..f448796bcc2990e551d36a098dd847237d59f89e |
--- /dev/null |
+++ b/chrome/browser/devtools/device/adb/adb_device_info_query.cc |
@@ -0,0 +1,396 @@ |
+// 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 |
+std::string AdbDeviceInfoQuery::FindDisplayNameByPackage( |
+ const std::string& package) { |
+ const BrowserDescriptor* descriptor = FindBrowserDescriptor(package); |
+ return descriptor ? descriptor->display_name : std::string(); |
+} |
+ |
+AdbDeviceInfoQuery::AdbDeviceInfoQuery( |
+ const RunCommandClosure& command_closure, |
+ const DeviceInfoCallback& callback) |
+ : command_closure_(command_closure), |
+ callback_(callback) { |
+ DCHECK(CalledOnValidThread()); |
+ command_closure_.Run( |
+ kDeviceModelCommand, |
+ base::Bind(&AdbDeviceInfoQuery::ReceivedModel, this)); |
+} |
+ |
+AdbDeviceInfoQuery::~AdbDeviceInfoQuery() { |
+} |
+ |
+void AdbDeviceInfoQuery::ReceivedModel(int result, |
+ const std::string& response) { |
+ DCHECK(CalledOnValidThread()); |
+ if (result < 0) { |
+ Respond(); |
+ return; |
+ } |
+ device_info_.model = response; |
+ command_closure_.Run( |
+ kDumpsysCommand, |
+ base::Bind(&AdbDeviceInfoQuery::ReceivedDumpsys, this)); |
+} |
+ |
+void AdbDeviceInfoQuery::ReceivedDumpsys(int result, |
+ const std::string& response) { |
+ DCHECK(CalledOnValidThread()); |
+ if (result >= 0) |
+ ParseDumpsysResponse(response); |
+ |
+ command_closure_.Run( |
+ kInstalledChromePackagesCommand, |
+ base::Bind(&AdbDeviceInfoQuery::ReceivedPackages, 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_closure_.Run( |
+ kListProcessesCommand, |
+ base::Bind( |
+ &AdbDeviceInfoQuery::ReceivedProcesses, 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_closure_.Run( |
+ kOpenedUnixSocketsCommand, |
+ base::Bind(&AdbDeviceInfoQuery::ReceivedSockets, |
+ 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; |
+ |
+ AndroidDeviceManager::BrowserInfo browser_info; |
+ browser_info.socket = socket; |
+ if (socket.find(kChromeDefaultSocket) == 0) |
+ browser_info.type = AndroidDeviceManager::BrowserInfo::kTypeChrome; |
+ else if (socket.find(kWebViewSocketPrefix) == 0) |
+ browser_info.type = AndroidDeviceManager::BrowserInfo::kTypeWebView; |
+ else |
+ browser_info.type = AndroidDeviceManager::BrowserInfo::kTypeOther; |
+ |
+ StringMap::iterator pit = pid_to_package.find(pid); |
+ if (pit != pid_to_package.end()) { |
+ std::string package = pit->second; |
+ packages_for_running_browsers.insert(package); |
+ const BrowserDescriptor* descriptor = FindBrowserDescriptor(package); |
+ if (descriptor) { |
+ browser_info.display_name = descriptor->display_name; |
+ } else if (browser_info.type == |
+ AndroidDeviceManager::BrowserInfo::kTypeWebView) { |
+ browser_info.display_name = |
+ base::StringPrintf(kWebViewNameTemplate, package.c_str()); |
+ } else { |
+ browser_info.display_name = package; |
+ } |
+ } else { |
+ // Set fallback display name. |
+ std::string name = socket.substr(0, socket.find(kDevToolsSocketSuffix)); |
+ name[0] = base::ToUpperASCII(name[0]); |
+ browser_info.display_name = name; |
+ |
+ socket_to_unnamed_browser_index[socket] = |
+ device_info_.browser_info.size(); |
+ } |
+ 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_); |
+} |