Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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() |
| OLD | NEW |