Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1278)

Side by Side Diff: appengine/swarming/swarming_bot/api/platforms/android.py

Issue 1396603003: Small improvements to api.platforms.android. (Closed) Base URL: git@github.com:luci/luci-py.git@master
Patch Set: Refuse multiple calls to initialize() and stop dropping broken keys silently Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2015 The Swarming Authors. All rights reserved. 1 # Copyright 2015 The Swarming Authors. All rights reserved.
2 # Use of this source code is governed by the Apache v2.0 license that can be 2 # Use of this source code is governed by the Apache v2.0 license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Android specific utility functions. 5 """Android specific utility functions.
6 6
7 This file serves as an API to bot_config.py. bot_config.py can be replaced on 7 This file serves as an API to bot_config.py. bot_config.py can be replaced on
8 the server to allow additional server-specific functionality. 8 the server to allow additional server-specific functionality.
9 """ 9 """
10 10
11 import logging 11 import logging
12 import os 12 import os
13 import re 13 import re
14 import subprocess 14 import subprocess
15 import sys 15 import sys
16 16
17 # This file must be imported from swarming_bot.zip (or with '..' in sys.path). 17 # This file must be imported from swarming_bot.zip (or with '../..' in
18 # libusb1 must have been put in the path already. 18 # sys.path). libusb1 must have been put in the path already.
19 19
20 import adb 20 import adb
21 import adb.adb_commands 21 import adb.adb_commands
22 22
23 23
24 # Find abs path to third_party/ dir in the bot root dir. 24 # Find abs path to third_party/ dir in the bot root dir.
25 import third_party 25 import third_party
26 THIRD_PARTY_DIR = os.path.dirname(os.path.abspath(third_party.__file__)) 26 THIRD_PARTY_DIR = os.path.dirname(os.path.abspath(third_party.__file__))
27 sys.path.insert(0, os.path.join(THIRD_PARTY_DIR, 'rsa')) 27 sys.path.insert(0, os.path.join(THIRD_PARTY_DIR, 'rsa'))
28 sys.path.insert(0, os.path.join(THIRD_PARTY_DIR, 'pyasn1')) 28 sys.path.insert(0, os.path.join(THIRD_PARTY_DIR, 'pyasn1'))
(...skipping 15 matching lines...) Expand all
44 def __init__(self): 44 def __init__(self):
45 self._buf = '' 45 self._buf = ''
46 def update(self, msg): 46 def update(self, msg):
47 self._buf += msg 47 self._buf += msg
48 def digest(self): 48 def digest(self):
49 return self._buf 49 return self._buf
50 pkcs1.HASH_METHODS['SHA-1-PREHASHED'] = _Accum 50 pkcs1.HASH_METHODS['SHA-1-PREHASHED'] = _Accum
51 pkcs1.HASH_ASN1['SHA-1-PREHASHED'] = pkcs1.HASH_ASN1['SHA-1'] 51 pkcs1.HASH_ASN1['SHA-1-PREHASHED'] = pkcs1.HASH_ASN1['SHA-1']
52 52
53 53
54 # Set when ADB is initialized. It contains one or multiple key used to 54 # Both following are set when ADB is initialized.
55 # authenticate to Android debug protocol (adb). 55 # _ADB_KEYS is set to a list of PythonRSASigner instances. It contains one or
56 # multiple key used to authenticate to Android debug protocol (adb).
56 _ADB_KEYS = None 57 _ADB_KEYS = None
58 # _ADB_KEYS_RAW is set to a dict(pub: priv) when initialized, with the raw
59 # content of each key.
60 _ADB_KEYS_RAW = None
57 61
58 62
59 # Cache of /system/build.prop on Android devices connected to this host. 63 # Cache of /system/build.prop on Android devices connected to this host.
60 _BUILD_PROP_ANDROID = {} 64 _BUILD_PROP_ANDROID = {}
61 65
62 66
63 def _dumpsys(device, arg): 67 def _dumpsys(device, arg):
64 """dumpsys is a native android tool that returns semi structured data. 68 """dumpsys is a native android tool that returns semi structured data.
65 69
66 It acts as a directory service but each service return their data without any 70 It acts as a directory service but each service return their data without any
(...skipping 18 matching lines...) Expand all
85 group = match.group(i) 89 group = match.group(i)
86 char = group[4:8] 90 char = group[4:8]
87 if char != ' ': 91 if char != ' ':
88 out.append(char) 92 out.append(char)
89 char = group[0:4] 93 char = group[0:4]
90 if char != ' ': 94 if char != ' ':
91 out.append(char) 95 out.append(char)
92 return out 96 return out
93 97
94 98
95 class _Device(object): 99 class Device(object):
96 """Wraps an AdbCommands to make it exception safe. 100 """Wraps an AdbCommands to make it exception safe.
97 101
98 The fact that exceptions can be thrown any time makes the client code really 102 The fact that exceptions can be thrown any time makes the client code really
99 hard to write safely. Convert USBError* to None return value. 103 hard to write safely. Convert USBError* to None return value.
100 """ 104 """
101 def __init__(self, adb_cmd, port_path): 105 def __init__(self, adb_cmd, port_path):
102 if adb_cmd: 106 if adb_cmd:
103 assert isinstance(adb_cmd, adb.adb_commands.AdbCommands), adb_cmd 107 assert isinstance(adb_cmd, adb.adb_commands.AdbCommands), adb_cmd
104 self.adb_cmd = adb_cmd 108 self.adb_cmd = adb_cmd
105 # Try to get the real serial number if possible. 109 # Try to get the real serial number if possible.
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
179 assert out[-1] == '\n', out 183 assert out[-1] == '\n', out
180 # Strip the last line to extract the exit code. 184 # Strip the last line to extract the exit code.
181 parts = out[:-1].rsplit('\n', 1) 185 parts = out[:-1].rsplit('\n', 1)
182 if len(parts) == 2: 186 if len(parts) == 2:
183 return parts[0] + '\n', int(parts[1]) 187 return parts[0] + '\n', int(parts[1])
184 # The command itself generated no output. 188 # The command itself generated no output.
185 return '', int(parts[0]) 189 return '', int(parts[0])
186 190
187 191
188 def _load_device(handle): 192 def _load_device(handle):
189 """Given a adb.USBDevice, return a _Device wrapping an 193 """Given a adb.USBDevice, return a Device wrapping an
190 adb_commands.AdbCommands. 194 adb_commands.AdbCommands.
191 """ 195 """
192 port_path = '/'.join(map(str, handle.port_path)) 196 port_path = '/'.join(map(str, handle.port_path))
193 try: 197 try:
194 handle.Open() 198 handle.Open()
195 except adb.common.usb1.USBErrorNoDevice as e: 199 except adb.common.usb1.USBErrorNoDevice as e:
196 logging.warning('Got USBErrorNoDevice for %s: %s', port_path, e) 200 logging.warning('Got USBErrorNoDevice for %s: %s', port_path, e)
197 # Do not kill adb, it just means the USB host is likely resetting and the 201 # Do not kill adb, it just means the USB host is likely resetting and the
198 # device is temporarily unavailable.We can't use handle.serial_number since 202 # device is temporarily unavailable.We can't use handle.serial_number since
199 # this communicates with the device. 203 # this communicates with the device.
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
232 logging.warning('AUTH FAILURE: %s: %s', port_path, e) 236 logging.warning('AUTH FAILURE: %s: %s', port_path, e)
233 except adb.usb_exceptions.ReadFailedError as e: 237 except adb.usb_exceptions.ReadFailedError as e:
234 logging.warning('READ FAILURE: %s: %s', port_path, e) 238 logging.warning('READ FAILURE: %s: %s', port_path, e)
235 except adb.usb_exceptions.WriteFailedError as e: 239 except adb.usb_exceptions.WriteFailedError as e:
236 logging.warning('WRITE FAILURE: %s: %s', port_path, e) 240 logging.warning('WRITE FAILURE: %s: %s', port_path, e)
237 except ValueError as e: 241 except ValueError as e:
238 # This is generated when there's a protocol level failure, e.g. the data 242 # This is generated when there's a protocol level failure, e.g. the data
239 # is garbled. 243 # is garbled.
240 logging.warning( 244 logging.warning(
241 'Trying unpluging and pluging it back: %s: %s', port_path, e) 245 'Trying unpluging and pluging it back: %s: %s', port_path, e)
242 # Always create a _Device, even if it points to nothing. It makes using the 246 # Always create a Device, even if it points to nothing. It makes using the
243 # client code much easier. 247 # client code much easier.
244 return _Device(cmd, port_path) 248 return Device(cmd, port_path)
245 249
246 250
247 ### Public API. 251 ### Public API.
248 252
249 253
250 def initialize(pub_key, priv_key): 254 def initialize(pub_key, priv_key):
251 """Initialize Android support through adb. 255 """Initialize Android support through adb.
252 256
253 You can steal pub_key, priv_key pair from ~/.android/adbkey and 257 You can steal pub_key, priv_key pair from ~/.android/adbkey and
254 ~/.android/adbkey.pub. 258 ~/.android/adbkey.pub.
255 """ 259 """
256 global _ADB_KEYS 260 global _ADB_KEYS
261 global _ADB_KEYS_RAW
262 if _ADB_KEYS is not None:
263 assert False, 'initialize() was called repeatedly: ignoring keys'
257 assert bool(pub_key) == bool(priv_key) 264 assert bool(pub_key) == bool(priv_key)
258 if _ADB_KEYS is None: 265 pub_key = pub_key.strip() if pub_key else pub_key
259 _ADB_KEYS = [] 266 priv_key = priv_key.strip() if priv_key else priv_key
260 if pub_key:
261 try:
262 _ADB_KEYS.append(PythonRSASigner(pub_key, priv_key))
263 except ValueError as exc:
264 logging.warning('Ignoring adb private key: %s', exc)
265 267
266 # Try to add local adb keys if available. 268 _ADB_KEYS = []
267 path = os.path.expanduser('~/.android/adbkey') 269 _ADB_KEYS_RAW = {}
268 if os.path.isfile(path) and os.path.isfile(path+'.pub'): 270 if pub_key:
269 with open(path + '.pub', 'rb') as f: 271 _ADB_KEYS.append(PythonRSASigner(pub_key, priv_key))
270 pub = f.read() 272 _ADB_KEYS_RAW[pub_key] = priv_key
271 with open(path, 'rb') as f:
272 priv = f.read()
273 try:
274 _ADB_KEYS.append(PythonRSASigner(pub, priv))
275 except ValueError as exc:
276 logging.warning('Ignoring adb private key %s: %s', path, exc)
277 273
278 if not _ADB_KEYS: 274 # Try to add local adb keys if available.
279 return False 275 path = os.path.expanduser('~/.android/adbkey')
280 else: 276 if os.path.isfile(path) and os.path.isfile(path + '.pub'):
281 if pub_key: 277 with open(path + '.pub', 'rb') as f:
282 logging.warning('initialize() was called repeatedly: ignoring keys') 278 pub = f.read().strip()
279 with open(path, 'rb') as f:
280 priv = f.read().strip()
281 _ADB_KEYS.append(PythonRSASigner(pub, priv))
282 _ADB_KEYS_RAW[pub] = priv
283
283 return bool(_ADB_KEYS) 284 return bool(_ADB_KEYS)
284 285
285 286
286 class PythonRSASigner(object): 287 class PythonRSASigner(object):
287 """Implements adb_protocol.AuthSigner using http://stuvel.eu/rsa.""" 288 """Implements adb_protocol.AuthSigner using http://stuvel.eu/rsa."""
288 def __init__(self, pub, priv): 289 def __init__(self, pub, priv):
289 self.priv_key = load_rsa_private_key(priv) 290 self.priv_key = load_rsa_private_key(priv)
290 self.pub_key = pub 291 self.pub_key = pub
291 292
292 def Sign(self, data): 293 def Sign(self, data):
(...skipping 14 matching lines...) Expand all
307 keyinfo, _ = decoder.decode(der) 308 keyinfo, _ = decoder.decode(der)
308 if keyinfo[1][0] != univ.ObjectIdentifier('1.2.840.113549.1.1.1'): 309 if keyinfo[1][0] != univ.ObjectIdentifier('1.2.840.113549.1.1.1'):
309 raise ValueError('Not a DER-encoded OpenSSL private RSA key') 310 raise ValueError('Not a DER-encoded OpenSSL private RSA key')
310 private_key_der = keyinfo[2].asOctets() 311 private_key_der = keyinfo[2].asOctets()
311 except IndexError: 312 except IndexError:
312 raise ValueError('Not a DER-encoded OpenSSL private RSA key') 313 raise ValueError('Not a DER-encoded OpenSSL private RSA key')
313 return rsa.PrivateKey.load_pkcs1(private_key_der, format='DER') 314 return rsa.PrivateKey.load_pkcs1(private_key_der, format='DER')
314 315
315 316
316 def kill_adb(): 317 def kill_adb():
317 """adb sucks. Kill it with fire.""" 318 """Stops the adb daemon.
319
320 The Swarming bot doesn't use the Android's SDK adb. It uses python-adb to
321 directly communicate with the devices. This works much better when a lot of
322 devices are connected to the host.
323
324 adb's stability is less than stellar. Kill it with fire.
325 """
318 if not adb: 326 if not adb:
319 return 327 return
320 try: 328 while True:
321 subprocess.call(['adb', 'kill-server']) 329 try:
322 except OSError: 330 subprocess.check_output(['pgrep', 'adb'])
323 pass 331 except subprocess.CalledProcessError:
324 subprocess.call(['killall', '--exact', 'adb']) 332 return
333 try:
334 subprocess.call(
335 ['adb', 'kill-server'],
336 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
337 except OSError:
338 pass
339 subprocess.call(
340 ['killall', '--exact', 'adb'],
341 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
325 342
326 343
327 def get_devices(): 344 def get_devices(bot=None): # pylint: disable=unused-argument
ghost stip (do not use) 2015/10/08 20:13:28 ?
328 """Returns the list of devices available. 345 """Returns the list of devices available.
329 346
330 Caller MUST call close_devices(devices) on the return value. 347 Caller MUST call close_devices(devices) on the return value.
331 348
332 Returns one of: 349 Returns one of:
333 - dict of {serial_number: }. The value may be 350 - dict of {serial_number: }. The value may be
334 None if there was an Auth failure. 351 None if there was an Auth failure.
335 - None if adb is unavailable. 352 - None if adb is unavailable.
336 """ 353 """
337 # TODO(maruel): Return a list instead of a dict. It's a bit tricky as we need 354 # TODO(maruel): Return a list instead of a dict. It's a bit tricky as we need
(...skipping 26 matching lines...) Expand all
364 return devices 381 return devices
365 382
366 383
367 def close_devices(devices): 384 def close_devices(devices):
368 """Closes all devices opened by get_devices().""" 385 """Closes all devices opened by get_devices()."""
369 for _, device in sorted((devices or {}).iteritems()): 386 for _, device in sorted((devices or {}).iteritems()):
370 device.close() 387 device.close()
371 388
372 389
373 class CpuScalingGovernor(object): 390 class CpuScalingGovernor(object):
374 """List of valid CPU scaling governor values.""" 391 """List of known CPU scaling governor values.
375 ON_DEMAND = 'ondemand' 392 """
393 CONSERVATIVE = 'conservative' # Not available on Nexus 10
394 INTERACTIVE = 'interactive' # Default on Nexus 10
395 ON_DEMAND = 'ondemand' # Not available on Nexus 10. Default on Nexus 4
376 PERFORMANCE = 'performance' 396 PERFORMANCE = 'performance'
377 CONSERVATIVE = 'conservative' 397 POWER_SAVE = 'powersave' # Not available on Nexus 10
378 POWER_SAVE = 'powersave'
379 USER_DEFINED = 'userspace' 398 USER_DEFINED = 'userspace'
380 399
381 @classmethod 400 @classmethod
382 def is_valid(cls, name): 401 def is_valid(cls, name):
383 return name in [getattr(cls, m) for m in dir(cls) if m[0].isupper()] 402 return name in cls.all_values()
403
404 @classmethod
405 def all_values(cls):
ghost stip (do not use) 2015/10/08 20:13:28 I don't know why you're doing all this. why not ju
406 return sorted(getattr(cls, m) for m in dir(cls) if m[0].isupper())
384 407
385 408
386 def set_cpu_scaling_governor(device, governor): 409 def set_cpu_scaling_governor(device, governor):
387 """Sets the CPU scaling governor to the one specified. 410 """Sets the CPU scaling governor to the one specified.
388 411
389 Returns: 412 Returns:
390 True on success. 413 True on success.
391 """ 414 """
392 assert isinstance(device, _Device), device 415 assert isinstance(device, Device), device
393 assert CpuScalingGovernor.is_valid(governor), governor 416 assert CpuScalingGovernor.is_valid(governor), governor
394 _, exit_code = device.shell( 417 _, exit_code = device.shell(
395 'echo "%s">/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor' % 418 'echo "%s">/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor' %
396 governor) 419 governor)
397 return exit_code == 0 420 return exit_code == 0
398 421
399 422
400 def set_cpu_scaling(device, speed): 423 def set_cpu_scaling(device, speed):
401 """Enforces strict CPU speed and disable the CPU scaling governor. 424 """Enforces strict CPU speed and disable the CPU scaling governor.
402 425
403 Returns: 426 Returns:
404 True on success. 427 True on success.
405 """ 428 """
406 assert isinstance(device, _Device), device 429 assert isinstance(device, Device), device
407 assert isinstance(speed, int), speed 430 assert isinstance(speed, int), speed
408 assert 10000 <= speed <= 10000000, speed 431 assert 10000 <= speed <= 10000000, speed
409 success = set_cpu_scaling_governor(device, CpuScalingGovernor.USER_DEFINED) 432 success = set_cpu_scaling_governor(device, CpuScalingGovernor.USER_DEFINED)
410 _, exit_code = device.shell( 433 _, exit_code = device.shell(
411 'echo "%d">/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed' % 434 'echo "%d">/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed' %
412 speed) 435 speed)
413 return success and exit_code == 0 436 return success and exit_code == 0
414 437
415 438
416 def get_build_prop(device): 439 def get_build_prop(device):
(...skipping 12 matching lines...) Expand all
429 if line.startswith(u'#') or not line: 452 if line.startswith(u'#') or not line:
430 continue 453 continue
431 key, value = line.split(u'=', 1) 454 key, value = line.split(u'=', 1)
432 properties[key] = value 455 properties[key] = value
433 _BUILD_PROP_ANDROID[device.serial] = properties 456 _BUILD_PROP_ANDROID[device.serial] = properties
434 return _BUILD_PROP_ANDROID[device.serial] 457 return _BUILD_PROP_ANDROID[device.serial]
435 458
436 459
437 def get_temp(device): 460 def get_temp(device):
438 """Returns the device's 2 temperatures if available.""" 461 """Returns the device's 2 temperatures if available."""
462 assert isinstance(device, Device), device
439 # Not all devices export this file. On other devices, the only real way to 463 # Not all devices export this file. On other devices, the only real way to
440 # read it is via Java 464 # read it is via Java
441 # developer.android.com/guide/topics/sensors/sensors_environment.html 465 # developer.android.com/guide/topics/sensors/sensors_environment.html
442 temps = [] 466 temps = []
443 for i in xrange(2): 467 for i in xrange(2):
444 out, _ = device.shell('cat /sys/class/thermal/thermal_zone%d/temp' % i) 468 out, _ = device.shell('cat /sys/class/thermal/thermal_zone%d/temp' % i)
445 if out: 469 if out:
446 temps.append(int(out)) 470 try:
471 temps.append(int(out))
472 except ValueError:
473 pass
447 return temps 474 return temps
448 475
449 476
450 def get_battery(device): 477 def get_battery(device):
451 """Returns details about the battery's state.""" 478 """Returns details about the battery's state."""
479 assert isinstance(device, Device), device
452 props = {} 480 props = {}
453 out = _dumpsys(device, 'battery') 481 out = _dumpsys(device, 'battery')
454 if not out: 482 if not out:
455 return props 483 return props
456 for line in out.splitlines(): 484 for line in out.splitlines():
457 if line.endswith(u':'): 485 if line.endswith(u':'):
458 continue 486 continue
459 # On Android 4.1.2, it uses "voltage:123" instead of "voltage: 123". 487 # On Android 4.1.2, it uses "voltage:123" instead of "voltage: 123".
460 key, value = line.split(u':', 2) 488 parts = line.split(u':', 2)
461 props[key.lstrip()] = value.strip() 489 if len(parts) == 2:
490 key, value = parts
491 props[key.lstrip()] = value.strip()
462 out = {u'power': []} 492 out = {u'power': []}
463 if props[u'AC powered'] == u'true': 493 if props.get(u'AC powered') == u'true':
464 out[u'power'].append(u'AC') 494 out[u'power'].append(u'AC')
465 if props[u'USB powered'] == u'true': 495 if props.get(u'USB powered') == u'true':
466 out[u'power'].append(u'USB') 496 out[u'power'].append(u'USB')
467 if props.get(u'Wireless powered') == u'true': 497 if props.get(u'Wireless powered') == u'true':
468 out[u'power'].append(u'Wireless') 498 out[u'power'].append(u'Wireless')
469 for key in (u'health', u'level', u'status', u'temperature', u'voltage'): 499 for key in (u'health', u'level', u'status', u'temperature', u'voltage'):
470 out[key] = int(props[key]) 500 out[key] = int(props[key])
471 return out 501 return out
472 502
473 503
474 def get_cpu_scale(device): 504 def get_cpu_scale(device):
475 """Returns the CPU scaling factor.""" 505 """Returns the CPU scaling factor."""
506 assert isinstance(device, Device), device
476 mapping = { 507 mapping = {
477 'cpuinfo_max_freq': u'max', 508 'cpuinfo_max_freq': u'max',
478 'cpuinfo_min_freq': u'min', 509 'cpuinfo_min_freq': u'min',
479 'scaling_cur_freq': u'cur', 510 'scaling_cur_freq': u'cur',
480 'scaling_governor': u'governor', 511 'scaling_governor': u'governor',
481 } 512 }
482 return { 513 return {
483 v: device.shell('cat /sys/devices/system/cpu/cpu0/cpufreq/' + k)[0] 514 v: device.shell('cat /sys/devices/system/cpu/cpu0/cpufreq/' + k)[0]
484 for k, v in mapping.iteritems() 515 for k, v in mapping.iteritems()
485 } 516 }
486 517
487 518
488 def get_uptime(device): 519 def get_uptime(device):
489 """Returns the device's uptime in second.""" 520 """Returns the device's uptime in second."""
521 assert isinstance(device, Device), device
490 out, _ = device.shell('cat /proc/uptime') 522 out, _ = device.shell('cat /proc/uptime')
491 if out: 523 if out:
492 return float(out.split()[1]) 524 return float(out.split()[1])
493 return None 525 return None
494 526
495 527
496 def get_disk(device): 528 def get_disk(device):
497 """Returns details about the battery's state.""" 529 """Returns details about the battery's state."""
530 assert isinstance(device, Device), device
498 props = {} 531 props = {}
499 out = _dumpsys(device, 'diskstats') 532 out = _dumpsys(device, 'diskstats')
500 if not out: 533 if not out:
501 return props 534 return props
502 for line in out.splitlines(): 535 for line in out.splitlines():
503 if line.endswith(u':'): 536 if line.endswith(u':'):
504 continue 537 continue
505 key, value = line.split(u': ', 2) 538 parts = line.split(u': ', 2)
ghost stip (do not use) 2015/10/08 20:13:28 I would have preferred this to be a separate CL to
506 match = re.match(u'^(\d+)K / (\d+)K.*', value) 539 if len(parts) == 2:
507 if match: 540 key, value = parts
508 props[key.lstrip()] = { 541 match = re.match(u'^(\d+)K / (\d+)K.*', value)
509 'free_mb': round(float(match.group(1)) / 1024., 1), 542 if match:
510 'size_mb': round(float(match.group(2)) / 1024., 1), 543 props[key.lstrip()] = {
511 } 544 'free_mb': round(float(match.group(1)) / 1024., 1),
545 'size_mb': round(float(match.group(2)) / 1024., 1),
546 }
512 return { 547 return {
513 u'cache': props[u'Cache-Free'], 548 u'cache': props[u'Cache-Free'],
514 u'data': props[u'Data-Free'], 549 u'data': props[u'Data-Free'],
515 u'system': props[u'System-Free'], 550 u'system': props[u'System-Free'],
516 } 551 }
517 552
518 553
519 def get_imei(device): 554 def get_imei(device):
520 """Returns the phone's IMEI.""" 555 """Returns the phone's IMEI."""
556 assert isinstance(device, Device), device
521 # Android <5.0. 557 # Android <5.0.
522 out = _dumpsys(device, 'iphonesubinfo') 558 out = _dumpsys(device, 'iphonesubinfo')
523 if out: 559 if out:
524 match = re.search(' Device ID = (.+)$', out) 560 match = re.search(' Device ID = (.+)$', out)
525 if match: 561 if match:
526 return match.group(1) 562 return match.group(1)
527 563
528 # Android >= 5.0. 564 # Android >= 5.0.
529 out, _ = device.shell('service call iphonesubinfo 1') 565 out, _ = device.shell('service call iphonesubinfo 1')
530 if out: 566 if out:
531 lines = out.splitlines() 567 lines = out.splitlines()
532 if len(lines) >= 4 and lines[0] == 'Result: Parcel(': 568 if len(lines) >= 4 and lines[0] == 'Result: Parcel(':
533 # Process the UTF-16 string. 569 # Process the UTF-16 string.
534 chars = _parcel_to_list(lines[1:])[4:-1] 570 chars = _parcel_to_list(lines[1:])[4:-1]
535 return u''.join(map(unichr, (int(i, 16) for i in chars))) 571 return u''.join(map(unichr, (int(i, 16) for i in chars)))
536 return None 572 return None
537 573
538 574
539 def get_ip(device): 575 def get_ip(device):
540 """Returns the IP address.""" 576 """Returns the IP address."""
577 assert isinstance(device, Device), device
541 # If ever needed, read wifi.interface from /system/build.prop if a device 578 # If ever needed, read wifi.interface from /system/build.prop if a device
542 # doesn't use wlan0. 579 # doesn't use wlan0.
543 ip, _ = device.shell('getprop dhcp.wlan0.ipaddress') 580 ip, _ = device.shell('getprop dhcp.wlan0.ipaddress')
544 if not ip: 581 if not ip:
545 return None 582 return None
546 return ip.strip() 583 return ip.strip()
OLDNEW
« no previous file with comments | « appengine/swarming/swarming_bot/__main__.py ('k') | appengine/swarming/swarming_bot/api/platforms/android_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698