Index: build/android/pylib/android_commands.py |
diff --git a/build/android/pylib/android_commands.py b/build/android/pylib/android_commands.py |
index 8d8949cac74c8db343ff68b5c9a33da11d462bf8..95b193bbc231131f3c8b389cabfadfe429f06f21 100644 |
--- a/build/android/pylib/android_commands.py |
+++ b/build/android/pylib/android_commands.py |
@@ -9,6 +9,7 @@ Assumes adb binary is currently on system path. |
import collections |
import datetime |
+import inspect |
import logging |
import os |
import re |
@@ -22,6 +23,7 @@ import time |
import cmd_helper |
import constants |
import screenshot |
+import system_properties |
from utils import host_path_finder |
@@ -228,14 +230,17 @@ def GetLogTimestamp(log_line, year): |
class AndroidCommands(object): |
- """Helper class for communicating with Android device via adb. |
+ """Helper class for communicating with Android device via adb.""" |
- Args: |
- device: If given, adb commands are only send to the device of this ID. |
- Otherwise commands are sent to all attached devices. |
- """ |
+ def __init__(self, device=None, api_strict_mode=False): |
+ """Constructor. |
- def __init__(self, device=None): |
+ Args: |
+ device: If given, adb commands are only send to the device of this ID. |
+ Otherwise commands are sent to all attached devices. |
+ api_strict_mode: A boolean indicating whether fatal errors should be |
+ raised if this API is used improperly. |
+ """ |
adb_dir = os.path.dirname(constants.ADB_PATH) |
if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep): |
# Required by third_party/android_testrunner to call directly 'adb'. |
@@ -253,6 +258,18 @@ class AndroidCommands(object): |
self._actual_push_size = 0 |
self._external_storage = '' |
self._util_wrapper = '' |
+ self._api_strict_mode = api_strict_mode |
+ self._system_properties = system_properties.SystemProperties(self.Adb()) |
+ |
+ if not self._api_strict_mode: |
+ logging.warning( |
+ 'API STRICT MODE IS DISABLED.\n' |
+ 'It should be enabled as soon as possible as it will eventually ' |
+ 'become the default.') |
+ |
+ @property |
+ def system_properties(self): |
+ return self._system_properties |
def _LogShell(self, cmd): |
"""Logs the adb shell command.""" |
@@ -264,6 +281,7 @@ class AndroidCommands(object): |
def Adb(self): |
"""Returns our AdbInterface to avoid us wrapping all its methods.""" |
+ # TODO(tonyg): Disable this method when in _api_strict_mode. |
return self._adb |
def GetDevice(self): |
@@ -352,6 +370,7 @@ class AndroidCommands(object): |
return |
if full_reboot or not self.IsRootEnabled(): |
self._adb.SendCommand('reboot') |
+ self._system_properties = system_properties.SystemProperties(self.Adb()) |
timeout = 300 |
retries = 1 |
# Wait for the device to disappear. |
@@ -369,6 +388,7 @@ class AndroidCommands(object): |
def Shutdown(self): |
"""Shuts down the device.""" |
self._adb.SendCommand('reboot -p') |
+ self._system_properties = system_properties.SystemProperties(self.Adb()) |
def Uninstall(self, package): |
"""Uninstalls the specified package from the device. |
@@ -536,8 +556,7 @@ class AndroidCommands(object): |
attempts = 0 |
wait_period = 5 |
while not boot_completed and (attempts * wait_period) < wait_time: |
- output = self._adb.SendShellCommand('getprop sys.boot_completed', |
- retry_count=1) |
+ output = self.system_properties['sys.boot_completed'] |
output = output.strip() |
if output == '1': |
boot_completed = True |
@@ -571,6 +590,34 @@ class AndroidCommands(object): |
raise errors.WaitForResponseTimedOutError( |
'SD card not ready after %s seconds' % timeout_time) |
+ def _CheckCommandIsValid(self, command): |
+ """Raises a ValueError if the command is not valid.""" |
+ |
+ # A dict of commands the user should not run directly and a mapping to the |
+ # API they should use instead. |
+ preferred_apis = { |
+ 'getprop': 'system_properties[<PROPERTY>]', |
+ 'setprop': 'system_properties[<PROPERTY>]', |
+ 'su': 'RunShellCommandWithSU()', |
+ } |
+ |
+ # A dict of commands to methods that may call them. |
+ whitelisted_callers = { |
+ 'su': 'RunShellCommandWithSU', |
+ } |
+ |
+ base_command = shlex.split(command)[0] |
+ if (base_command in preferred_apis and |
+ (base_command not in whitelisted_callers or |
+ whitelisted_callers[base_command] not in [ |
+ f[3] for f in inspect.stack()])): |
+ error_msg = ('%s cannot be run directly. Instead use: %s' % |
+ (base_command, preferred_apis[base_command])) |
+ if self._api_strict_mode: |
+ raise ValueError(error_msg) |
+ else: |
+ logging.warning(error_msg) |
+ |
# It is tempting to turn this function into a generator, however this is not |
# possible without using a private (local) adb_shell instance (to ensure no |
# other command interleaves usage of it), which would defeat the main aim of |
@@ -589,6 +636,7 @@ class AndroidCommands(object): |
Returns: |
list containing the lines of output received from running the command |
""" |
+ self._CheckCommandIsValid(command) |
self._LogShell(command) |
if "'" in command: logging.warning(command + " contains ' quotes") |
result = self._adb.SendShellCommand( |
@@ -1000,9 +1048,7 @@ class AndroidCommands(object): |
return self.GetExternalStorage() + '/' + base_name % i |
def RunShellCommandWithSU(self, command, timeout_time=20, log_result=False): |
- return self.RunShellCommand('su -c %s' % command, |
- timeout_time=timeout_time, |
- log_result=log_result) |
+ return self.RunShellCommand('su -c %s' % command, timeout_time, log_result) |
def CanAccessProtectedFileContents(self): |
"""Returns True if Get/SetProtectedFileContents would work via "su". |
@@ -1103,41 +1149,39 @@ class AndroidCommands(object): |
# Next, check the current runtime value is what we need, and |
# if not, set it and report that a reboot is required. |
- was_set = 'all' in self.RunShellCommand('getprop ' + JAVA_ASSERT_PROPERTY) |
+ was_set = 'all' in self.system_properties[JAVA_ASSERT_PROPERTY] |
if was_set == enable: |
return False |
- |
- self.RunShellCommand('setprop %s "%s"' % (JAVA_ASSERT_PROPERTY, |
- enable and 'all' or '')) |
+ self.system_properties[JAVA_ASSERT_PROPERTY] = enable and 'all' or '' |
return True |
def GetBuildId(self): |
"""Returns the build ID of the system (e.g. JRM79C).""" |
- build_id = self.RunShellCommand('getprop ro.build.id')[0] |
+ build_id = self.system_properties['ro.build.id'] |
assert build_id |
return build_id |
def GetBuildType(self): |
"""Returns the build type of the system (e.g. eng).""" |
- build_type = self.RunShellCommand('getprop ro.build.type')[0] |
+ build_type = self.system_properties['ro.build.type'] |
assert build_type |
return build_type |
def GetBuildProduct(self): |
"""Returns the build product of the device (e.g. maguro).""" |
- build_product = self.RunShellCommand('getprop ro.build.product')[0] |
+ build_product = self.system_properties['ro.build.product'] |
assert build_product |
return build_product |
def GetProductName(self): |
"""Returns the product name of the device (e.g. takju).""" |
- name = self.RunShellCommand('getprop ro.product.name')[0] |
+ name = self.system_properties['ro.product.name'] |
assert name |
return name |
def GetBuildFingerprint(self): |
"""Returns the build fingerprint of the device.""" |
- build_fingerprint = self.RunShellCommand('getprop ro.build.fingerprint')[0] |
+ build_fingerprint = self.system_properties['ro.build.fingerprint'] |
assert build_fingerprint |
return build_fingerprint |
@@ -1146,19 +1190,19 @@ class AndroidCommands(object): |
For example, "yakju-userdebug 4.1 JRN54F 364167 dev-keys". |
""" |
- description = self.RunShellCommand('getprop ro.build.description')[0] |
+ description = self.system_properties['ro.build.description'] |
assert description |
return description |
def GetProductModel(self): |
"""Returns the name of the product model (e.g. "Galaxy Nexus") """ |
- model = self.RunShellCommand('getprop ro.product.model')[0] |
+ model = self.system_properties['ro.product.model'] |
assert model |
return model |
def GetWifiIP(self): |
"""Returns the wifi IP on the device.""" |
- wifi_ip = self.RunShellCommand('getprop dhcp.wlan0.ipaddress')[0] |
+ wifi_ip = self.system_properties['dhcp.wlan0.ipaddress'] |
# Do not assert here. Devices (e.g. emulators) may not have a WifiIP. |
return wifi_ip |
@@ -1176,7 +1220,7 @@ class AndroidCommands(object): |
def GetSetupWizardStatus(self): |
"""Returns the status of the device setup wizard (e.g. DISABLED).""" |
- status = self.RunShellCommand('getprop ro.setupwizard.mode')[0] |
+ status = self.system_properties['ro.setupwizard.mode'] |
# On some devices, the status is empty if not otherwise set. In such cases |
# the caller should expect an empty string to be returned. |
return status |