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"
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"
21 "echo " SEPARATOR
"\n"
22 "cat /proc/net/unix\n"
23 "echo " SEPARATOR
"\n"
26 const char kSeparator
[] = SEPARATOR
"\r\n";
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
{
44 const char* display_name
;
47 const BrowserDescriptor kBrowserDescriptors
[] = {
49 "com.google.android.apps.chrome",
79 "org.chromium.android_webview.shell",
80 "webview_devtools_remote",
84 "org.chromium.content_shell_apk",
85 "content_shell_devtools_remote",
89 "org.chromium.chrome",
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
];
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
;
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)
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)
160 if (fields
[3] != "00010000" || fields
[5] != "01")
162 std::string path_field
= fields
[7];
163 if (path_field
.size() < 1 || path_field
[0] != '@')
165 size_t socket_name_pos
= path_field
.find(kDevToolsSocketSuffix
);
166 if (socket_name_pos
== std::string::npos
)
169 std::string socket
= path_field
.substr(1);
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)
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
))
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
)));
215 StringMap
MapIdsToUsers(const std::string
& response
) {
216 // Parse 'dumpsys user' output which looks like this:
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())
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
,
273 const std::string
& response
) {
274 AndroidDeviceManager::DeviceInfo device_info
;
276 callback
.Run(device_info
);
279 std::vector
<std::string
> outputs
;
280 base::SplitStringUsingSubstr(response
, kSeparator
, &outputs
);
281 if (outputs
.size() != 5) {
282 callback
.Run(device_info
);
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
);
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
;
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(),
330 callback
.Run(device_info
);
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]);
345 const BrowserDescriptor
* descriptor
= FindBrowserDescriptor(package
);
347 return descriptor
->display_name
;
349 if (GetBrowserType(socket
) ==
350 AndroidDeviceManager::BrowserInfo::kTypeWebView
)
351 return base::StringPrintf(kWebViewNameTemplate
, package
.c_str());
357 void AndroidDeviceManager::QueryDeviceInfo(
358 const RunCommandCallback
& command_callback
,
359 const DeviceInfoCallback
& callback
) {
360 command_callback
.Run(
362 base::Bind(&ReceivedResponse
, callback
));