OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/browser/devtools/device/adb/adb_device_info_query.h" |
| 6 |
| 7 #include "base/strings/string_number_conversions.h" |
| 8 #include "base/strings/string_util.h" |
| 9 #include "base/strings/stringprintf.h" |
| 10 |
| 11 namespace { |
| 12 |
| 13 |
| 14 const char kDeviceModelCommand[] = "shell:getprop ro.product.model"; |
| 15 const char kInstalledChromePackagesCommand[] = "shell:pm list packages"; |
| 16 const char kOpenedUnixSocketsCommand[] = "shell:cat /proc/net/unix"; |
| 17 const char kListProcessesCommand[] = "shell:ps"; |
| 18 const char kDumpsysCommand[] = "shell:dumpsys window policy"; |
| 19 const char kDumpsysScreenSizePrefix[] = "mStable="; |
| 20 |
| 21 const char kDevToolsSocketSuffix[] = "_devtools_remote"; |
| 22 |
| 23 const char kChromeDefaultName[] = "Chrome"; |
| 24 const char kChromeDefaultSocket[] = "chrome_devtools_remote"; |
| 25 |
| 26 const char kWebViewSocketPrefix[] = "webview_devtools_remote"; |
| 27 const char kWebViewNameTemplate[] = "WebView in %s"; |
| 28 |
| 29 struct BrowserDescriptor { |
| 30 const char* package; |
| 31 const char* socket; |
| 32 const char* display_name; |
| 33 }; |
| 34 |
| 35 const BrowserDescriptor kBrowserDescriptors[] = { |
| 36 { |
| 37 "com.android.chrome", |
| 38 kChromeDefaultSocket, |
| 39 kChromeDefaultName |
| 40 }, |
| 41 { |
| 42 "com.chrome.beta", |
| 43 kChromeDefaultSocket, |
| 44 "Chrome Beta" |
| 45 }, |
| 46 { |
| 47 "com.google.android.apps.chrome_dev", |
| 48 kChromeDefaultSocket, |
| 49 "Chrome Dev" |
| 50 }, |
| 51 { |
| 52 "com.chrome.canary", |
| 53 kChromeDefaultSocket, |
| 54 "Chrome Canary" |
| 55 }, |
| 56 { |
| 57 "com.google.android.apps.chrome", |
| 58 kChromeDefaultSocket, |
| 59 "Chromium" |
| 60 }, |
| 61 { |
| 62 "org.chromium.content_shell_apk", |
| 63 "content_shell_devtools_remote", |
| 64 "Content Shell" |
| 65 }, |
| 66 { |
| 67 "org.chromium.chrome.shell", |
| 68 "chrome_shell_devtools_remote", |
| 69 "Chrome Shell" |
| 70 }, |
| 71 { |
| 72 "org.chromium.android_webview.shell", |
| 73 "webview_devtools_remote", |
| 74 "WebView Test Shell" |
| 75 } |
| 76 }; |
| 77 |
| 78 const BrowserDescriptor* FindBrowserDescriptor(const std::string& package) { |
| 79 int count = sizeof(kBrowserDescriptors) / sizeof(kBrowserDescriptors[0]); |
| 80 for (int i = 0; i < count; i++) |
| 81 if (kBrowserDescriptors[i].package == package) |
| 82 return &kBrowserDescriptors[i]; |
| 83 return NULL; |
| 84 } |
| 85 |
| 86 typedef std::map<std::string, const BrowserDescriptor*> DescriptorMap; |
| 87 |
| 88 static DescriptorMap FindInstalledBrowserPackages(const std::string& response) { |
| 89 // Parse 'pm list packages' output which on Android looks like this: |
| 90 // |
| 91 // package:com.android.chrome |
| 92 // package:com.chrome.beta |
| 93 // package:com.example.app |
| 94 // |
| 95 DescriptorMap package_to_descriptor; |
| 96 const std::string package_prefix = "package:"; |
| 97 std::vector<std::string> entries; |
| 98 Tokenize(response, "'\r\n", &entries); |
| 99 for (size_t i = 0; i < entries.size(); ++i) { |
| 100 if (entries[i].find(package_prefix) != 0) |
| 101 continue; |
| 102 std::string package = entries[i].substr(package_prefix.size()); |
| 103 const BrowserDescriptor* descriptor = FindBrowserDescriptor(package); |
| 104 if (!descriptor) |
| 105 continue; |
| 106 package_to_descriptor[descriptor->package] = descriptor; |
| 107 } |
| 108 return package_to_descriptor; |
| 109 } |
| 110 |
| 111 typedef std::map<std::string, std::string> StringMap; |
| 112 |
| 113 static void MapProcessesToPackages(const std::string& response, |
| 114 StringMap& pid_to_package, |
| 115 StringMap& package_to_pid) { |
| 116 // Parse 'ps' output which on Android looks like this: |
| 117 // |
| 118 // USER PID PPID VSIZE RSS WCHAN PC ? NAME |
| 119 // |
| 120 std::vector<std::string> entries; |
| 121 Tokenize(response, "\n", &entries); |
| 122 for (size_t i = 1; i < entries.size(); ++i) { |
| 123 std::vector<std::string> fields; |
| 124 Tokenize(entries[i], " \r", &fields); |
| 125 if (fields.size() < 9) |
| 126 continue; |
| 127 std::string pid = fields[1]; |
| 128 std::string package = fields[8]; |
| 129 pid_to_package[pid] = package; |
| 130 package_to_pid[package] = pid; |
| 131 } |
| 132 } |
| 133 |
| 134 static StringMap MapSocketsToProcesses(const std::string& response, |
| 135 const std::string& channel_pattern) { |
| 136 // Parse 'cat /proc/net/unix' output which on Android looks like this: |
| 137 // |
| 138 // Num RefCount Protocol Flags Type St Inode Path |
| 139 // 00000000: 00000002 00000000 00010000 0001 01 331813 /dev/socket/zygote |
| 140 // 00000000: 00000002 00000000 00010000 0001 01 358606 @xxx_devtools_remote |
| 141 // 00000000: 00000002 00000000 00010000 0001 01 347300 @yyy_devtools_remote |
| 142 // |
| 143 // We need to find records with paths starting from '@' (abstract socket) |
| 144 // and containing the channel pattern ("_devtools_remote"). |
| 145 StringMap socket_to_pid; |
| 146 std::vector<std::string> entries; |
| 147 Tokenize(response, "\n", &entries); |
| 148 for (size_t i = 1; i < entries.size(); ++i) { |
| 149 std::vector<std::string> fields; |
| 150 Tokenize(entries[i], " \r", &fields); |
| 151 if (fields.size() < 8) |
| 152 continue; |
| 153 if (fields[3] != "00010000" || fields[5] != "01") |
| 154 continue; |
| 155 std::string path_field = fields[7]; |
| 156 if (path_field.size() < 1 || path_field[0] != '@') |
| 157 continue; |
| 158 size_t socket_name_pos = path_field.find(channel_pattern); |
| 159 if (socket_name_pos == std::string::npos) |
| 160 continue; |
| 161 |
| 162 std::string socket = path_field.substr(1); |
| 163 |
| 164 std::string pid; |
| 165 size_t socket_name_end = socket_name_pos + channel_pattern.size(); |
| 166 if (socket_name_end < path_field.size() && |
| 167 path_field[socket_name_end] == '_') { |
| 168 pid = path_field.substr(socket_name_end + 1); |
| 169 } |
| 170 socket_to_pid[socket] = pid; |
| 171 } |
| 172 return socket_to_pid; |
| 173 } |
| 174 |
| 175 } // namespace |
| 176 |
| 177 // static |
| 178 AndroidDeviceManager::BrowserInfo::Type |
| 179 AdbDeviceInfoQuery::GetBrowserType(const std::string& socket) { |
| 180 if (socket.find(kChromeDefaultSocket) == 0) |
| 181 return AndroidDeviceManager::BrowserInfo::kTypeChrome; |
| 182 |
| 183 if (socket.find(kWebViewSocketPrefix) == 0) |
| 184 return AndroidDeviceManager::BrowserInfo::kTypeWebView; |
| 185 |
| 186 return AndroidDeviceManager::BrowserInfo::kTypeOther; |
| 187 } |
| 188 |
| 189 // static |
| 190 std::string AdbDeviceInfoQuery::GetDisplayName(const std::string& socket, |
| 191 const std::string& package) { |
| 192 if (package.empty()) { |
| 193 // Derive a fallback display name from the socket name. |
| 194 std::string name = socket.substr(0, socket.find(kDevToolsSocketSuffix)); |
| 195 name[0] = base::ToUpperASCII(name[0]); |
| 196 return name; |
| 197 } |
| 198 |
| 199 const BrowserDescriptor* descriptor = FindBrowserDescriptor(package); |
| 200 if (descriptor) |
| 201 return descriptor->display_name; |
| 202 |
| 203 if (GetBrowserType(socket) == |
| 204 AndroidDeviceManager::BrowserInfo::kTypeWebView) |
| 205 return base::StringPrintf(kWebViewNameTemplate, package.c_str()); |
| 206 |
| 207 return package; |
| 208 } |
| 209 |
| 210 // static |
| 211 void AdbDeviceInfoQuery::Start(const RunCommandCallback& command_callback, |
| 212 const DeviceInfoCallback& callback) { |
| 213 new AdbDeviceInfoQuery(command_callback, callback); |
| 214 } |
| 215 |
| 216 AdbDeviceInfoQuery::AdbDeviceInfoQuery( |
| 217 const RunCommandCallback& command_callback, |
| 218 const DeviceInfoCallback& callback) |
| 219 : command_callback_(command_callback), |
| 220 callback_(callback) { |
| 221 DCHECK(CalledOnValidThread()); |
| 222 command_callback_.Run( |
| 223 kDeviceModelCommand, |
| 224 base::Bind(&AdbDeviceInfoQuery::ReceivedModel, base::Unretained(this))); |
| 225 } |
| 226 |
| 227 AdbDeviceInfoQuery::~AdbDeviceInfoQuery() { |
| 228 } |
| 229 |
| 230 void AdbDeviceInfoQuery::ReceivedModel(int result, |
| 231 const std::string& response) { |
| 232 DCHECK(CalledOnValidThread()); |
| 233 if (result < 0) { |
| 234 Respond(); |
| 235 return; |
| 236 } |
| 237 device_info_.model = response; |
| 238 command_callback_.Run( |
| 239 kDumpsysCommand, |
| 240 base::Bind(&AdbDeviceInfoQuery::ReceivedDumpsys, base::Unretained(this))); |
| 241 } |
| 242 |
| 243 void AdbDeviceInfoQuery::ReceivedDumpsys(int result, |
| 244 const std::string& response) { |
| 245 DCHECK(CalledOnValidThread()); |
| 246 if (result >= 0) |
| 247 ParseDumpsysResponse(response); |
| 248 |
| 249 command_callback_.Run( |
| 250 kInstalledChromePackagesCommand, |
| 251 base::Bind(&AdbDeviceInfoQuery::ReceivedPackages, |
| 252 base::Unretained(this))); |
| 253 } |
| 254 |
| 255 void AdbDeviceInfoQuery::ParseDumpsysResponse(const std::string& response) { |
| 256 std::vector<std::string> lines; |
| 257 Tokenize(response, "\r", &lines); |
| 258 for (size_t i = 0; i < lines.size(); ++i) { |
| 259 std::string line = lines[i]; |
| 260 size_t pos = line.find(kDumpsysScreenSizePrefix); |
| 261 if (pos != std::string::npos) { |
| 262 ParseScreenSize( |
| 263 line.substr(pos + std::string(kDumpsysScreenSizePrefix).size())); |
| 264 break; |
| 265 } |
| 266 } |
| 267 } |
| 268 |
| 269 void AdbDeviceInfoQuery::ParseScreenSize(const std::string& str) { |
| 270 std::vector<std::string> pairs; |
| 271 Tokenize(str, "-", &pairs); |
| 272 if (pairs.size() != 2) |
| 273 return; |
| 274 |
| 275 int width; |
| 276 int height; |
| 277 std::vector<std::string> numbers; |
| 278 Tokenize(pairs[1].substr(1, pairs[1].size() - 2), ",", &numbers); |
| 279 if (numbers.size() != 2 || |
| 280 !base::StringToInt(numbers[0], &width) || |
| 281 !base::StringToInt(numbers[1], &height)) |
| 282 return; |
| 283 |
| 284 device_info_.screen_size = gfx::Size(width, height); |
| 285 } |
| 286 |
| 287 |
| 288 void AdbDeviceInfoQuery::ReceivedPackages( |
| 289 int result, |
| 290 const std::string& packages_response) { |
| 291 DCHECK(CalledOnValidThread()); |
| 292 if (result < 0) { |
| 293 Respond(); |
| 294 return; |
| 295 } |
| 296 command_callback_.Run( |
| 297 kListProcessesCommand, |
| 298 base::Bind(&AdbDeviceInfoQuery::ReceivedProcesses, |
| 299 base::Unretained(this), packages_response)); |
| 300 } |
| 301 |
| 302 void AdbDeviceInfoQuery::ReceivedProcesses( |
| 303 const std::string& packages_response, |
| 304 int result, |
| 305 const std::string& processes_response) { |
| 306 DCHECK(CalledOnValidThread()); |
| 307 if (result < 0) { |
| 308 Respond(); |
| 309 return; |
| 310 } |
| 311 command_callback_.Run( |
| 312 kOpenedUnixSocketsCommand, |
| 313 base::Bind(&AdbDeviceInfoQuery::ReceivedSockets, |
| 314 base::Unretained(this), |
| 315 packages_response, |
| 316 processes_response)); |
| 317 } |
| 318 |
| 319 void AdbDeviceInfoQuery::ReceivedSockets( |
| 320 const std::string& packages_response, |
| 321 const std::string& processes_response, |
| 322 int result, |
| 323 const std::string& sockets_response) { |
| 324 DCHECK(CalledOnValidThread()); |
| 325 if (result >= 0) |
| 326 ParseBrowserInfo(packages_response, processes_response, sockets_response); |
| 327 Respond(); |
| 328 } |
| 329 |
| 330 void AdbDeviceInfoQuery::ParseBrowserInfo( |
| 331 const std::string& packages_response, |
| 332 const std::string& processes_response, |
| 333 const std::string& sockets_response) { |
| 334 DCHECK(CalledOnValidThread()); |
| 335 DescriptorMap package_to_descriptor = |
| 336 FindInstalledBrowserPackages(packages_response); |
| 337 StringMap pid_to_package; |
| 338 StringMap package_to_pid; |
| 339 MapProcessesToPackages(processes_response, pid_to_package, package_to_pid); |
| 340 |
| 341 StringMap socket_to_pid = MapSocketsToProcesses(sockets_response, |
| 342 kDevToolsSocketSuffix); |
| 343 |
| 344 std::set<std::string> packages_for_running_browsers; |
| 345 |
| 346 typedef std::map<std::string, int> BrowserMap; |
| 347 BrowserMap socket_to_unnamed_browser_index; |
| 348 |
| 349 for (StringMap::iterator it = socket_to_pid.begin(); |
| 350 it != socket_to_pid.end(); ++it) { |
| 351 std::string socket = it->first; |
| 352 std::string pid = it->second; |
| 353 |
| 354 std::string package; |
| 355 StringMap::iterator pit = pid_to_package.find(pid); |
| 356 if (pit != pid_to_package.end()) { |
| 357 package = pit->second; |
| 358 packages_for_running_browsers.insert(package); |
| 359 } else { |
| 360 socket_to_unnamed_browser_index[socket] = |
| 361 device_info_.browser_info.size(); |
| 362 } |
| 363 |
| 364 AndroidDeviceManager::BrowserInfo browser_info; |
| 365 browser_info.socket_name = socket; |
| 366 browser_info.type = GetBrowserType(socket); |
| 367 browser_info.display_name = GetDisplayName(socket, package); |
| 368 device_info_.browser_info.push_back(browser_info); |
| 369 } |
| 370 |
| 371 // Find installed packages not mapped to browsers. |
| 372 typedef std::multimap<std::string, const BrowserDescriptor*> |
| 373 DescriptorMultimap; |
| 374 DescriptorMultimap socket_to_descriptor; |
| 375 for (DescriptorMap::iterator it = package_to_descriptor.begin(); |
| 376 it != package_to_descriptor.end(); ++it) { |
| 377 std::string package = it->first; |
| 378 const BrowserDescriptor* descriptor = it->second; |
| 379 |
| 380 if (packages_for_running_browsers.count(package)) |
| 381 continue; // This package is already mapped to a browser. |
| 382 |
| 383 if (package_to_pid.find(package) != package_to_pid.end()) { |
| 384 // This package is running but not mapped to a browser. |
| 385 socket_to_descriptor.insert( |
| 386 DescriptorMultimap::value_type(descriptor->socket, descriptor)); |
| 387 continue; |
| 388 } |
| 389 } |
| 390 |
| 391 // Try naming remaining unnamed browsers. |
| 392 for (DescriptorMultimap::iterator it = socket_to_descriptor.begin(); |
| 393 it != socket_to_descriptor.end(); ++it) { |
| 394 std::string socket = it->first; |
| 395 const BrowserDescriptor* descriptor = it->second; |
| 396 |
| 397 if (socket_to_descriptor.count(socket) != 1) |
| 398 continue; // No definitive match. |
| 399 |
| 400 BrowserMap::iterator bit = socket_to_unnamed_browser_index.find(socket); |
| 401 if (bit != socket_to_unnamed_browser_index.end()) { |
| 402 device_info_.browser_info[bit->second].display_name = |
| 403 descriptor->display_name; |
| 404 } |
| 405 } |
| 406 } |
| 407 |
| 408 void AdbDeviceInfoQuery::Respond() { |
| 409 DCHECK(CalledOnValidThread()); |
| 410 callback_.Run(device_info_); |
| 411 delete this; |
| 412 } |
OLD | NEW |