Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / devtools / device / android_device_info_query.cc
blobcb6be0db696b9f90efb2b8566fb5ac73e999669a
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.
5 #include "base/strings/string_number_conversions.h"
6 #include "base/strings/string_split.h"
7 #include "base/strings/string_util.h"
8 #include "base/strings/stringprintf.h"
9 #include "chrome/browser/devtools/device/android_device_manager.h"
11 namespace {
13 #define SEPARATOR "======== output separator ========"
15 const char kAllCommands[] = "shell:"
16 "getprop ro.product.model\n"
17 "echo " SEPARATOR "\n"
18 "dumpsys window policy\n"
19 "echo " SEPARATOR "\n"
20 "ps\n"
21 "echo " SEPARATOR "\n"
22 "cat /proc/net/unix\n"
23 "echo " SEPARATOR "\n"
24 "dumpsys user\n";
26 const char kSeparator[] = SEPARATOR "\r\n";
28 #undef SEPARATOR
30 const char kScreenSizePrefix[] = "mStable=";
31 const char kUserInfoPrefix[] = "UserInfo{";
33 const char kDevToolsSocketSuffix[] = "_devtools_remote";
35 const char kChromeDefaultName[] = "Chrome";
36 const char kChromeDefaultSocket[] = "chrome_devtools_remote";
38 const char kWebViewSocketPrefix[] = "webview_devtools_remote";
39 const char kWebViewNameTemplate[] = "WebView in %s";
41 struct BrowserDescriptor {
42 const char* package;
43 const char* socket;
44 const char* display_name;
47 const BrowserDescriptor kBrowserDescriptors[] = {
49 "com.google.android.apps.chrome",
50 kChromeDefaultSocket,
51 "Chromium"
54 "com.chrome.canary",
55 kChromeDefaultSocket,
56 "Chrome Canary"
59 "com.chrome.dev",
60 kChromeDefaultSocket,
61 "Chrome Dev"
64 "com.chrome.beta",
65 kChromeDefaultSocket,
66 "Chrome Beta"
69 "com.android.chrome",
70 kChromeDefaultSocket,
71 kChromeDefaultName
74 "com.chrome.work",
75 kChromeDefaultSocket,
76 "Work Chrome"
79 "org.chromium.android_webview.shell",
80 "webview_devtools_remote",
81 "WebView Test Shell"
84 "org.chromium.content_shell_apk",
85 "content_shell_devtools_remote",
86 "Content Shell"
89 "org.chromium.chrome",
90 kChromeDefaultSocket,
91 "Chromium"
95 const BrowserDescriptor* FindBrowserDescriptor(const std::string& package) {
96 size_t count = arraysize(kBrowserDescriptors);
97 for (size_t i = 0; i < count; i++) {
98 if (kBrowserDescriptors[i].package == package)
99 return &kBrowserDescriptors[i];
101 return nullptr;
104 bool BrowserCompare(const AndroidDeviceManager::BrowserInfo& a,
105 const AndroidDeviceManager::BrowserInfo& b) {
106 size_t count = arraysize(kBrowserDescriptors);
107 for (size_t i = 0; i < count; i++) {
108 bool isA = kBrowserDescriptors[i].display_name == a.display_name;
109 bool isB = kBrowserDescriptors[i].display_name == b.display_name;
110 if (isA != isB)
111 return isA;
112 if (isA && isB)
113 break;
115 return a.socket_name < b.socket_name;
118 using StringMap = std::map<std::string, std::string>;
120 void MapProcessesToPackages(const std::string& response,
121 StringMap* pid_to_package,
122 StringMap* pid_to_user) {
123 // Parse 'ps' output which on Android looks like this:
125 // USER PID PPID VSIZE RSS WCHAN PC ? NAME
127 for (const base::StringPiece& line :
128 base::SplitStringPiece(response, "\n", base::KEEP_WHITESPACE,
129 base::SPLIT_WANT_NONEMPTY)) {
130 std::vector<std::string> fields =
131 base::SplitString(line, " \r", base::KEEP_WHITESPACE,
132 base::SPLIT_WANT_NONEMPTY);
133 if (fields.size() < 9)
134 continue;
135 std::string pid = fields[1];
136 (*pid_to_package)[pid] = fields[8];
137 (*pid_to_user)[pid] = fields[0];
141 StringMap MapSocketsToProcesses(const std::string& response) {
142 // Parse 'cat /proc/net/unix' output which on Android looks like this:
144 // Num RefCount Protocol Flags Type St Inode Path
145 // 00000000: 00000002 00000000 00010000 0001 01 331813 /dev/socket/zygote
146 // 00000000: 00000002 00000000 00010000 0001 01 358606 @xxx_devtools_remote
147 // 00000000: 00000002 00000000 00010000 0001 01 347300 @yyy_devtools_remote
149 // We need to find records with paths starting from '@' (abstract socket)
150 // and containing the channel pattern ("_devtools_remote").
151 StringMap socket_to_pid;
152 for (const base::StringPiece& line :
153 base::SplitStringPiece(response, "\n", base::KEEP_WHITESPACE,
154 base::SPLIT_WANT_NONEMPTY)) {
155 std::vector<std::string> fields =
156 base::SplitString(line, " \r", base::KEEP_WHITESPACE,
157 base::SPLIT_WANT_NONEMPTY);
158 if (fields.size() < 8)
159 continue;
160 if (fields[3] != "00010000" || fields[5] != "01")
161 continue;
162 std::string path_field = fields[7];
163 if (path_field.size() < 1 || path_field[0] != '@')
164 continue;
165 size_t socket_name_pos = path_field.find(kDevToolsSocketSuffix);
166 if (socket_name_pos == std::string::npos)
167 continue;
169 std::string socket = path_field.substr(1);
171 std::string pid;
172 size_t socket_name_end = socket_name_pos + strlen(kDevToolsSocketSuffix);
173 if (socket_name_end < path_field.size() &&
174 path_field[socket_name_end] == '_') {
175 pid = path_field.substr(socket_name_end + 1);
177 socket_to_pid[socket] = pid;
179 return socket_to_pid;
182 gfx::Size ParseScreenSize(base::StringPiece str) {
183 std::vector<base::StringPiece> pairs =
184 base::SplitStringPiece(str, "-", base::KEEP_WHITESPACE,
185 base::SPLIT_WANT_NONEMPTY);
186 if (pairs.size() != 2)
187 return gfx::Size();
189 int width;
190 int height;
191 std::vector<base::StringPiece> numbers =
192 base::SplitStringPiece(pairs[1].substr(1, pairs[1].size() - 2), ",",
193 base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
194 if (numbers.size() != 2 ||
195 !base::StringToInt(numbers[0], &width) ||
196 !base::StringToInt(numbers[1], &height))
197 return gfx::Size();
199 return gfx::Size(width, height);
202 gfx::Size ParseWindowPolicyResponse(const std::string& response) {
203 for (const base::StringPiece& line :
204 base::SplitStringPiece(response, "\r", base::KEEP_WHITESPACE,
205 base::SPLIT_WANT_NONEMPTY)) {
206 size_t pos = line.find(kScreenSizePrefix);
207 if (pos != base::StringPiece::npos) {
208 return ParseScreenSize(
209 line.substr(pos + strlen(kScreenSizePrefix)));
212 return gfx::Size();
215 StringMap MapIdsToUsers(const std::string& response) {
216 // Parse 'dumpsys user' output which looks like this:
217 // Users:
218 // UserInfo{0:Test User:13} serialNo=0
219 // Created: <unknown>
220 // Last logged in: +17m18s871ms ago
221 // UserInfo{10:User with : (colon):10} serialNo=10
222 // Created: +3d4h35m1s139ms ago
223 // Last logged in: +17m26s287ms ago
224 StringMap id_to_username;
225 for (const base::StringPiece& line :
226 base::SplitStringPiece(response, "\r", base::KEEP_WHITESPACE,
227 base::SPLIT_WANT_NONEMPTY)) {
228 size_t pos = line.find(kUserInfoPrefix);
229 if (pos != std::string::npos) {
230 base::StringPiece fields = line.substr(pos + strlen(kUserInfoPrefix));
231 size_t first_pos = fields.find_first_of(":");
232 size_t last_pos = fields.find_last_of(":");
233 if (first_pos != std::string::npos && last_pos != std::string::npos) {
234 std::string id = fields.substr(0, first_pos).as_string();
235 std::string name = fields.substr(first_pos + 1,
236 last_pos - first_pos - 1).as_string();
237 id_to_username[id] = name;
241 return id_to_username;
244 std::string GetUserName(const std::string& unix_user,
245 const StringMap id_to_username) {
246 // Parse username as returned by ps which looks like 'u0_a31'
247 // where '0' is user id and '31' is app id.
248 if (!unix_user.empty() && unix_user[0] == 'u') {
249 size_t pos = unix_user.find('_');
250 if (pos != std::string::npos) {
251 StringMap::const_iterator it =
252 id_to_username.find(unix_user.substr(1, pos - 1));
253 if (it != id_to_username.end())
254 return it->second;
257 return std::string();
260 AndroidDeviceManager::BrowserInfo::Type
261 GetBrowserType(const std::string& socket) {
262 if (socket.find(kChromeDefaultSocket) == 0)
263 return AndroidDeviceManager::BrowserInfo::kTypeChrome;
265 if (socket.find(kWebViewSocketPrefix) == 0)
266 return AndroidDeviceManager::BrowserInfo::kTypeWebView;
268 return AndroidDeviceManager::BrowserInfo::kTypeOther;
271 void ReceivedResponse(const AndroidDeviceManager::DeviceInfoCallback& callback,
272 int result,
273 const std::string& response) {
274 AndroidDeviceManager::DeviceInfo device_info;
275 if (result < 0) {
276 callback.Run(device_info);
277 return;
279 std::vector<std::string> outputs;
280 base::SplitStringUsingSubstr(response, kSeparator, &outputs);
281 if (outputs.size() != 5) {
282 callback.Run(device_info);
283 return;
285 device_info.connected = true;
286 device_info.model = outputs[0];
287 device_info.screen_size = ParseWindowPolicyResponse(outputs[1]);
288 StringMap pid_to_package;
289 StringMap pid_to_user;
290 MapProcessesToPackages(outputs[2], &pid_to_package, &pid_to_user);
291 StringMap socket_to_pid = MapSocketsToProcesses(outputs[3]);
292 StringMap id_to_username = MapIdsToUsers(outputs[4]);
293 std::set<std::string> used_pids;
294 for (const auto& pair : socket_to_pid)
295 used_pids.insert(pair.second);
297 for (const auto& pair : pid_to_package) {
298 std::string pid = pair.first;
299 std::string package = pair.second;
300 if (used_pids.find(pid) == used_pids.end()) {
301 const BrowserDescriptor* descriptor = FindBrowserDescriptor(package);
302 if (descriptor)
303 socket_to_pid[descriptor->socket] = pid;
307 for (const auto& pair : socket_to_pid) {
308 std::string socket = pair.first;
309 std::string pid = pair.second;
310 std::string package;
311 StringMap::iterator pit = pid_to_package.find(pid);
312 if (pit != pid_to_package.end())
313 package = pit->second;
315 AndroidDeviceManager::BrowserInfo browser_info;
316 browser_info.socket_name = socket;
317 browser_info.type = GetBrowserType(socket);
318 browser_info.display_name =
319 AndroidDeviceManager::GetBrowserName(socket, package);
321 StringMap::iterator uit = pid_to_user.find(pid);
322 if (uit != pid_to_user.end())
323 browser_info.user = GetUserName(uit->second, id_to_username);
325 device_info.browser_info.push_back(browser_info);
327 std::sort(device_info.browser_info.begin(),
328 device_info.browser_info.end(),
329 &BrowserCompare);
330 callback.Run(device_info);
333 } // namespace
335 // static
336 std::string AndroidDeviceManager::GetBrowserName(const std::string& socket,
337 const std::string& package) {
338 if (package.empty()) {
339 // Derive a fallback display name from the socket name.
340 std::string name = socket.substr(0, socket.find(kDevToolsSocketSuffix));
341 name[0] = base::ToUpperASCII(name[0]);
342 return name;
345 const BrowserDescriptor* descriptor = FindBrowserDescriptor(package);
346 if (descriptor)
347 return descriptor->display_name;
349 if (GetBrowserType(socket) ==
350 AndroidDeviceManager::BrowserInfo::kTypeWebView)
351 return base::StringPrintf(kWebViewNameTemplate, package.c_str());
353 return package;
356 // static
357 void AndroidDeviceManager::QueryDeviceInfo(
358 const RunCommandCallback& command_callback,
359 const DeviceInfoCallback& callback) {
360 command_callback.Run(
361 kAllCommands,
362 base::Bind(&ReceivedResponse, callback));