| 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 import time | 16 import time |
| 17 | 17 |
| 18 # This file must be imported from swarming_bot.zip (or with '..' in sys.path). | 18 # This file must be imported from swarming_bot.zip (or with '../..' in |
| 19 # libusb1 must have been put in the path already. | 19 # sys.path). libusb1 must have been put in the path already. |
| 20 | 20 |
| 21 import adb | 21 import adb |
| 22 import adb.adb_commands | 22 import adb.adb_commands |
| 23 | 23 |
| 24 | 24 |
| 25 # Find abs path to third_party/ dir in the bot root dir. | 25 # Find abs path to third_party/ dir in the bot root dir. |
| 26 import third_party | 26 import third_party |
| 27 THIRD_PARTY_DIR = os.path.dirname(os.path.abspath(third_party.__file__)) | 27 THIRD_PARTY_DIR = os.path.dirname(os.path.abspath(third_party.__file__)) |
| 28 sys.path.insert(0, os.path.join(THIRD_PARTY_DIR, 'rsa')) | 28 sys.path.insert(0, os.path.join(THIRD_PARTY_DIR, 'rsa')) |
| 29 sys.path.insert(0, os.path.join(THIRD_PARTY_DIR, 'pyasn1')) | 29 sys.path.insert(0, os.path.join(THIRD_PARTY_DIR, 'pyasn1')) |
| (...skipping 418 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 448 for line in out.splitlines(): | 448 for line in out.splitlines(): |
| 449 if line.startswith(u'#') or not line: | 449 if line.startswith(u'#') or not line: |
| 450 continue | 450 continue |
| 451 key, value = line.split(u'=', 1) | 451 key, value = line.split(u'=', 1) |
| 452 properties[key] = value | 452 properties[key] = value |
| 453 _BUILD_PROP_ANDROID[device.serial] = properties | 453 _BUILD_PROP_ANDROID[device.serial] = properties |
| 454 return _BUILD_PROP_ANDROID[device.serial] | 454 return _BUILD_PROP_ANDROID[device.serial] |
| 455 | 455 |
| 456 | 456 |
| 457 def get_temp(device): | 457 def get_temp(device): |
| 458 """Returns the device's 2 temperatures if available.""" | 458 """Returns the device's 2 temperatures if available. |
| 459 |
| 460 Returns None otherwise. |
| 461 """ |
| 462 assert isinstance(device, Device), device |
| 459 # 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 |
| 460 # read it is via Java | 464 # read it is via Java |
| 461 # developer.android.com/guide/topics/sensors/sensors_environment.html | 465 # developer.android.com/guide/topics/sensors/sensors_environment.html |
| 462 temps = [] | 466 temps = [] |
| 463 for i in xrange(2): | 467 try: |
| 464 out, _ = device.shell('cat /sys/class/thermal/thermal_zone%d/temp' % i) | 468 for i in xrange(2): |
| 465 if out: | 469 out, _ = device.shell('cat /sys/class/thermal/thermal_zone%d/temp' % i) |
| 466 temps.append(int(out)) | 470 temps.append(int(out)) |
| 471 except ValueError: |
| 472 return None |
| 467 return temps | 473 return temps |
| 468 | 474 |
| 469 | 475 |
| 470 def get_battery(device): | 476 def get_battery(device): |
| 471 """Returns details about the battery's state.""" | 477 """Returns details about the battery's state.""" |
| 478 assert isinstance(device, Device), device |
| 472 props = {} | 479 props = {} |
| 473 out = _dumpsys(device, 'battery') | 480 out = _dumpsys(device, 'battery') |
| 474 if not out: | 481 if not out: |
| 475 return props | 482 return props |
| 476 for line in out.splitlines(): | 483 for line in out.splitlines(): |
| 477 if line.endswith(u':'): | 484 if line.endswith(u':'): |
| 478 continue | 485 continue |
| 479 # On Android 4.1.2, it uses "voltage:123" instead of "voltage: 123". | 486 # On Android 4.1.2, it uses "voltage:123" instead of "voltage: 123". |
| 480 key, value = line.split(u':', 2) | 487 parts = line.split(u':', 2) |
| 481 props[key.lstrip()] = value.strip() | 488 if len(parts) == 2: |
| 489 key, value = parts |
| 490 props[key.lstrip()] = value.strip() |
| 482 out = {u'power': []} | 491 out = {u'power': []} |
| 483 if props[u'AC powered'] == u'true': | 492 if props.get(u'AC powered') == u'true': |
| 484 out[u'power'].append(u'AC') | 493 out[u'power'].append(u'AC') |
| 485 if props[u'USB powered'] == u'true': | 494 if props.get(u'USB powered') == u'true': |
| 486 out[u'power'].append(u'USB') | 495 out[u'power'].append(u'USB') |
| 487 if props.get(u'Wireless powered') == u'true': | 496 if props.get(u'Wireless powered') == u'true': |
| 488 out[u'power'].append(u'Wireless') | 497 out[u'power'].append(u'Wireless') |
| 489 for key in (u'health', u'level', u'status', u'temperature', u'voltage'): | 498 for key in (u'health', u'level', u'status', u'temperature', u'voltage'): |
| 490 out[key] = int(props[key]) | 499 out[key] = int(props[key]) |
| 491 return out | 500 return out |
| 492 | 501 |
| 493 | 502 |
| 494 def get_cpu_scale(device): | 503 def get_cpu_scale(device): |
| 495 """Returns the CPU scaling factor.""" | 504 """Returns the CPU scaling factor.""" |
| 505 assert isinstance(device, Device), device |
| 496 mapping = { | 506 mapping = { |
| 497 'cpuinfo_max_freq': u'max', | 507 'cpuinfo_max_freq': u'max', |
| 498 'cpuinfo_min_freq': u'min', | 508 'cpuinfo_min_freq': u'min', |
| 499 'scaling_cur_freq': u'cur', | 509 'scaling_cur_freq': u'cur', |
| 500 'scaling_governor': u'governor', | 510 'scaling_governor': u'governor', |
| 501 } | 511 } |
| 502 return { | 512 return { |
| 503 v: device.shell('cat /sys/devices/system/cpu/cpu0/cpufreq/' + k)[0] | 513 v: device.shell('cat /sys/devices/system/cpu/cpu0/cpufreq/' + k)[0] |
| 504 for k, v in mapping.iteritems() | 514 for k, v in mapping.iteritems() |
| 505 } | 515 } |
| 506 | 516 |
| 507 | 517 |
| 508 def get_uptime(device): | 518 def get_uptime(device): |
| 509 """Returns the device's uptime in second.""" | 519 """Returns the device's uptime in second.""" |
| 520 assert isinstance(device, Device), device |
| 510 out, _ = device.shell('cat /proc/uptime') | 521 out, _ = device.shell('cat /proc/uptime') |
| 511 if out: | 522 if out: |
| 512 return float(out.split()[1]) | 523 return float(out.split()[1]) |
| 513 return None | 524 return None |
| 514 | 525 |
| 515 | 526 |
| 516 def get_disk(device): | 527 def get_disk(device): |
| 517 """Returns details about the battery's state.""" | 528 """Returns details about the battery's state.""" |
| 529 assert isinstance(device, Device), device |
| 518 props = {} | 530 props = {} |
| 519 out = _dumpsys(device, 'diskstats') | 531 out = _dumpsys(device, 'diskstats') |
| 520 if not out: | 532 if not out: |
| 521 return props | 533 return props |
| 522 for line in out.splitlines(): | 534 for line in out.splitlines(): |
| 523 if line.endswith(u':'): | 535 if line.endswith(u':'): |
| 524 continue | 536 continue |
| 525 key, value = line.split(u': ', 2) | 537 parts = line.split(u': ', 2) |
| 526 match = re.match(u'^(\d+)K / (\d+)K.*', value) | 538 if len(parts) == 2: |
| 527 if match: | 539 key, value = parts |
| 528 props[key.lstrip()] = { | 540 match = re.match(u'^(\d+)K / (\d+)K.*', value) |
| 529 'free_mb': round(float(match.group(1)) / 1024., 1), | 541 if match: |
| 530 'size_mb': round(float(match.group(2)) / 1024., 1), | 542 props[key.lstrip()] = { |
| 531 } | 543 'free_mb': round(float(match.group(1)) / 1024., 1), |
| 544 'size_mb': round(float(match.group(2)) / 1024., 1), |
| 545 } |
| 532 return { | 546 return { |
| 533 u'cache': props[u'Cache-Free'], | 547 u'cache': props[u'Cache-Free'], |
| 534 u'data': props[u'Data-Free'], | 548 u'data': props[u'Data-Free'], |
| 535 u'system': props[u'System-Free'], | 549 u'system': props[u'System-Free'], |
| 536 } | 550 } |
| 537 | 551 |
| 538 | 552 |
| 539 def get_imei(device): | 553 def get_imei(device): |
| 540 """Returns the phone's IMEI.""" | 554 """Returns the phone's IMEI.""" |
| 555 assert isinstance(device, Device), device |
| 541 # Android <5.0. | 556 # Android <5.0. |
| 542 out = _dumpsys(device, 'iphonesubinfo') | 557 out = _dumpsys(device, 'iphonesubinfo') |
| 543 if out: | 558 if out: |
| 544 match = re.search(' Device ID = (.+)$', out) | 559 match = re.search(' Device ID = (.+)$', out) |
| 545 if match: | 560 if match: |
| 546 return match.group(1) | 561 return match.group(1) |
| 547 | 562 |
| 548 # Android >= 5.0. | 563 # Android >= 5.0. |
| 549 out, _ = device.shell('service call iphonesubinfo 1') | 564 out, _ = device.shell('service call iphonesubinfo 1') |
| 550 if out: | 565 if out: |
| 551 lines = out.splitlines() | 566 lines = out.splitlines() |
| 552 if len(lines) >= 4 and lines[0] == 'Result: Parcel(': | 567 if len(lines) >= 4 and lines[0] == 'Result: Parcel(': |
| 553 # Process the UTF-16 string. | 568 # Process the UTF-16 string. |
| 554 chars = _parcel_to_list(lines[1:])[4:-1] | 569 chars = _parcel_to_list(lines[1:])[4:-1] |
| 555 return u''.join(map(unichr, (int(i, 16) for i in chars))) | 570 return u''.join(map(unichr, (int(i, 16) for i in chars))) |
| 556 return None | 571 return None |
| 557 | 572 |
| 558 | 573 |
| 559 def get_ip(device): | 574 def get_ip(device): |
| 560 """Returns the IP address.""" | 575 """Returns the IP address.""" |
| 576 assert isinstance(device, Device), device |
| 561 # If ever needed, read wifi.interface from /system/build.prop if a device | 577 # If ever needed, read wifi.interface from /system/build.prop if a device |
| 562 # doesn't use wlan0. | 578 # doesn't use wlan0. |
| 563 ip, _ = device.shell('getprop dhcp.wlan0.ipaddress') | 579 ip, _ = device.shell('getprop dhcp.wlan0.ipaddress') |
| 564 if not ip: | 580 if not ip: |
| 565 return None | 581 return None |
| 566 return ip.strip() | 582 return ip.strip() |
| OLD | NEW |