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 std::string AdbDeviceInfoQuery::FindDisplayNameByPackage( |
| 179 const std::string& package) { |
| 180 const BrowserDescriptor* descriptor = FindBrowserDescriptor(package); |
| 181 return descriptor ? descriptor->display_name : std::string(); |
| 182 } |
| 183 |
| 184 AdbDeviceInfoQuery::AdbDeviceInfoQuery( |
| 185 const RunCommandClosure& command_closure, |
| 186 const DeviceInfoCallback& callback) |
| 187 : command_closure_(command_closure), |
| 188 callback_(callback) { |
| 189 DCHECK(CalledOnValidThread()); |
| 190 command_closure_.Run( |
| 191 kDeviceModelCommand, |
| 192 base::Bind(&AdbDeviceInfoQuery::ReceivedModel, this)); |
| 193 } |
| 194 |
| 195 AdbDeviceInfoQuery::~AdbDeviceInfoQuery() { |
| 196 } |
| 197 |
| 198 void AdbDeviceInfoQuery::ReceivedModel(int result, |
| 199 const std::string& response) { |
| 200 DCHECK(CalledOnValidThread()); |
| 201 if (result < 0) { |
| 202 Respond(); |
| 203 return; |
| 204 } |
| 205 device_info_.model = response; |
| 206 command_closure_.Run( |
| 207 kDumpsysCommand, |
| 208 base::Bind(&AdbDeviceInfoQuery::ReceivedDumpsys, this)); |
| 209 } |
| 210 |
| 211 void AdbDeviceInfoQuery::ReceivedDumpsys(int result, |
| 212 const std::string& response) { |
| 213 DCHECK(CalledOnValidThread()); |
| 214 if (result >= 0) |
| 215 ParseDumpsysResponse(response); |
| 216 |
| 217 command_closure_.Run( |
| 218 kInstalledChromePackagesCommand, |
| 219 base::Bind(&AdbDeviceInfoQuery::ReceivedPackages, this)); |
| 220 } |
| 221 |
| 222 void AdbDeviceInfoQuery::ParseDumpsysResponse(const std::string& response) { |
| 223 std::vector<std::string> lines; |
| 224 Tokenize(response, "\r", &lines); |
| 225 for (size_t i = 0; i < lines.size(); ++i) { |
| 226 std::string line = lines[i]; |
| 227 size_t pos = line.find(kDumpsysScreenSizePrefix); |
| 228 if (pos != std::string::npos) { |
| 229 ParseScreenSize( |
| 230 line.substr(pos + std::string(kDumpsysScreenSizePrefix).size())); |
| 231 break; |
| 232 } |
| 233 } |
| 234 } |
| 235 |
| 236 void AdbDeviceInfoQuery::ParseScreenSize(const std::string& str) { |
| 237 std::vector<std::string> pairs; |
| 238 Tokenize(str, "-", &pairs); |
| 239 if (pairs.size() != 2) |
| 240 return; |
| 241 |
| 242 int width; |
| 243 int height; |
| 244 std::vector<std::string> numbers; |
| 245 Tokenize(pairs[1].substr(1, pairs[1].size() - 2), ",", &numbers); |
| 246 if (numbers.size() != 2 || |
| 247 !base::StringToInt(numbers[0], &width) || |
| 248 !base::StringToInt(numbers[1], &height)) |
| 249 return; |
| 250 |
| 251 device_info_.screen_size = gfx::Size(width, height); |
| 252 } |
| 253 |
| 254 |
| 255 void AdbDeviceInfoQuery::ReceivedPackages( |
| 256 int result, |
| 257 const std::string& packages_response) { |
| 258 DCHECK(CalledOnValidThread()); |
| 259 if (result < 0) { |
| 260 Respond(); |
| 261 return; |
| 262 } |
| 263 command_closure_.Run( |
| 264 kListProcessesCommand, |
| 265 base::Bind( |
| 266 &AdbDeviceInfoQuery::ReceivedProcesses, this, packages_response)); |
| 267 } |
| 268 |
| 269 void AdbDeviceInfoQuery::ReceivedProcesses( |
| 270 const std::string& packages_response, |
| 271 int result, |
| 272 const std::string& processes_response) { |
| 273 DCHECK(CalledOnValidThread()); |
| 274 if (result < 0) { |
| 275 Respond(); |
| 276 return; |
| 277 } |
| 278 command_closure_.Run( |
| 279 kOpenedUnixSocketsCommand, |
| 280 base::Bind(&AdbDeviceInfoQuery::ReceivedSockets, |
| 281 this, |
| 282 packages_response, |
| 283 processes_response)); |
| 284 } |
| 285 |
| 286 void AdbDeviceInfoQuery::ReceivedSockets( |
| 287 const std::string& packages_response, |
| 288 const std::string& processes_response, |
| 289 int result, |
| 290 const std::string& sockets_response) { |
| 291 DCHECK(CalledOnValidThread()); |
| 292 if (result >= 0) |
| 293 ParseBrowserInfo(packages_response, processes_response, sockets_response); |
| 294 Respond(); |
| 295 } |
| 296 |
| 297 void AdbDeviceInfoQuery::ParseBrowserInfo( |
| 298 const std::string& packages_response, |
| 299 const std::string& processes_response, |
| 300 const std::string& sockets_response) { |
| 301 DCHECK(CalledOnValidThread()); |
| 302 DescriptorMap package_to_descriptor = |
| 303 FindInstalledBrowserPackages(packages_response); |
| 304 StringMap pid_to_package; |
| 305 StringMap package_to_pid; |
| 306 MapProcessesToPackages(processes_response, pid_to_package, package_to_pid); |
| 307 |
| 308 StringMap socket_to_pid = MapSocketsToProcesses(sockets_response, |
| 309 kDevToolsSocketSuffix); |
| 310 |
| 311 std::set<std::string> packages_for_running_browsers; |
| 312 |
| 313 typedef std::map<std::string, int> BrowserMap; |
| 314 BrowserMap socket_to_unnamed_browser_index; |
| 315 |
| 316 for (StringMap::iterator it = socket_to_pid.begin(); |
| 317 it != socket_to_pid.end(); ++it) { |
| 318 std::string socket = it->first; |
| 319 std::string pid = it->second; |
| 320 |
| 321 AndroidDeviceManager::BrowserInfo browser_info; |
| 322 browser_info.socket = socket; |
| 323 if (socket.find(kChromeDefaultSocket) == 0) |
| 324 browser_info.type = AndroidDeviceManager::BrowserInfo::kTypeChrome; |
| 325 else if (socket.find(kWebViewSocketPrefix) == 0) |
| 326 browser_info.type = AndroidDeviceManager::BrowserInfo::kTypeWebView; |
| 327 else |
| 328 browser_info.type = AndroidDeviceManager::BrowserInfo::kTypeOther; |
| 329 |
| 330 StringMap::iterator pit = pid_to_package.find(pid); |
| 331 if (pit != pid_to_package.end()) { |
| 332 std::string package = pit->second; |
| 333 packages_for_running_browsers.insert(package); |
| 334 const BrowserDescriptor* descriptor = FindBrowserDescriptor(package); |
| 335 if (descriptor) { |
| 336 browser_info.display_name = descriptor->display_name; |
| 337 } else if (browser_info.type == |
| 338 AndroidDeviceManager::BrowserInfo::kTypeWebView) { |
| 339 browser_info.display_name = |
| 340 base::StringPrintf(kWebViewNameTemplate, package.c_str()); |
| 341 } else { |
| 342 browser_info.display_name = package; |
| 343 } |
| 344 } else { |
| 345 // Set fallback display name. |
| 346 std::string name = socket.substr(0, socket.find(kDevToolsSocketSuffix)); |
| 347 name[0] = base::ToUpperASCII(name[0]); |
| 348 browser_info.display_name = name; |
| 349 |
| 350 socket_to_unnamed_browser_index[socket] = |
| 351 device_info_.browser_info.size(); |
| 352 } |
| 353 device_info_.browser_info.push_back(browser_info); |
| 354 } |
| 355 |
| 356 // Find installed packages not mapped to browsers. |
| 357 typedef std::multimap<std::string, const BrowserDescriptor*> |
| 358 DescriptorMultimap; |
| 359 DescriptorMultimap socket_to_descriptor; |
| 360 for (DescriptorMap::iterator it = package_to_descriptor.begin(); |
| 361 it != package_to_descriptor.end(); ++it) { |
| 362 std::string package = it->first; |
| 363 const BrowserDescriptor* descriptor = it->second; |
| 364 |
| 365 if (packages_for_running_browsers.count(package)) |
| 366 continue; // This package is already mapped to a browser. |
| 367 |
| 368 if (package_to_pid.find(package) != package_to_pid.end()) { |
| 369 // This package is running but not mapped to a browser. |
| 370 socket_to_descriptor.insert( |
| 371 DescriptorMultimap::value_type(descriptor->socket, descriptor)); |
| 372 continue; |
| 373 } |
| 374 } |
| 375 |
| 376 // Try naming remaining unnamed browsers. |
| 377 for (DescriptorMultimap::iterator it = socket_to_descriptor.begin(); |
| 378 it != socket_to_descriptor.end(); ++it) { |
| 379 std::string socket = it->first; |
| 380 const BrowserDescriptor* descriptor = it->second; |
| 381 |
| 382 if (socket_to_descriptor.count(socket) != 1) |
| 383 continue; // No definitive match. |
| 384 |
| 385 BrowserMap::iterator bit = socket_to_unnamed_browser_index.find(socket); |
| 386 if (bit != socket_to_unnamed_browser_index.end()) { |
| 387 device_info_.browser_info[bit->second].display_name = |
| 388 descriptor->display_name; |
| 389 } |
| 390 } |
| 391 } |
| 392 |
| 393 void AdbDeviceInfoQuery::Respond() { |
| 394 DCHECK(CalledOnValidThread()); |
| 395 callback_.Run(device_info_); |
| 396 } |
OLD | NEW |