// 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 #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 }, { "com.chrome.work", kChromeDefaultSocket, "Work Chrome" }, { "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; 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 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 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 pairs = base::SplitStringPiece(str, "-", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); if (pairs.size() != 2) return gfx::Size(); int width; int height; std::vector 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: // 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) { StringMap::const_iterator 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 (socket.find(kChromeDefaultSocket) == 0) return AndroidDeviceManager::BrowserInfo::kTypeChrome; if (socket.find(kWebViewSocketPrefix) == 0) 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 outputs; base::SplitStringUsingSubstr(response, kSeparator, &outputs); 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 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; StringMap::iterator 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); StringMap::iterator 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)); }