summaryrefslogtreecommitdiff
path: root/systrace/catapult/devil/devil/android/device_utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'systrace/catapult/devil/devil/android/device_utils.py')
-rw-r--r--systrace/catapult/devil/devil/android/device_utils.py202
1 files changed, 169 insertions, 33 deletions
diff --git a/systrace/catapult/devil/devil/android/device_utils.py b/systrace/catapult/devil/devil/android/device_utils.py
index 3d8e203..7ba1b51 100644
--- a/systrace/catapult/devil/devil/android/device_utils.py
+++ b/systrace/catapult/devil/devil/android/device_utils.py
@@ -76,13 +76,22 @@ _PERMISSIONS_BLACKLIST = [
'android.permission.ACCESS_LOCATION_EXTRA_COMMANDS',
'android.permission.ACCESS_MOCK_LOCATION',
'android.permission.ACCESS_NETWORK_STATE',
+ 'android.permission.ACCESS_NOTIFICATION_POLICY',
'android.permission.ACCESS_WIFI_STATE',
'android.permission.AUTHENTICATE_ACCOUNTS',
'android.permission.BLUETOOTH',
'android.permission.BLUETOOTH_ADMIN',
+ 'android.permission.BROADCAST_STICKY',
+ 'android.permission.CHANGE_NETWORK_STATE',
+ 'android.permission.CHANGE_WIFI_MULTICAST_STATE',
+ 'android.permission.CHANGE_WIFI_STATE',
'android.permission.DISABLE_KEYGUARD',
'android.permission.DOWNLOAD_WITHOUT_NOTIFICATION',
+ 'android.permission.EXPAND_STATUS_BAR',
+ 'android.permission.GET_PACKAGE_SIZE',
+ 'android.permission.INSTALL_SHORTCUT',
'android.permission.INTERNET',
+ 'android.permission.KILL_BACKGROUND_PROCESSES',
'android.permission.MANAGE_ACCOUNTS',
'android.permission.MODIFY_AUDIO_SETTINGS',
'android.permission.NFC',
@@ -90,8 +99,16 @@ _PERMISSIONS_BLACKLIST = [
'android.permission.READ_SYNC_STATS',
'android.permission.RECEIVE_BOOT_COMPLETED',
'android.permission.RECORD_VIDEO',
+ 'android.permission.REORDER_TASKS',
+ 'android.permission.REQUEST_INSTALL_PACKAGES',
'android.permission.RUN_INSTRUMENTATION',
+ 'android.permission.SET_ALARM',
+ 'android.permission.SET_TIME_ZONE',
+ 'android.permission.SET_WALLPAPER',
+ 'android.permission.SET_WALLPAPER_HINTS',
+ 'android.permission.TRANSMIT_IR',
'android.permission.USE_CREDENTIALS',
+ 'android.permission.USE_FINGERPRINT',
'android.permission.VIBRATE',
'android.permission.WAKE_LOCK',
'android.permission.WRITE_SYNC_SETTINGS',
@@ -150,6 +167,17 @@ _FILE_MODE_SPECIAL = [
('s', stat.S_ISGID),
('t', stat.S_ISVTX),
]
+_SELINUX_MODE = {
+ 'enforcing': True,
+ 'permissive': False,
+ 'disabled': None
+}
+# Some devices require different logic for checking if root is necessary
+_SPECIAL_ROOT_DEVICE_LIST = [
+ 'marlin',
+ 'sailfish',
+]
+
@decorators.WithExplicitTimeoutAndRetries(
_DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
@@ -367,6 +395,8 @@ class DeviceUtils(object):
DeviceUnreachableError on missing device.
"""
try:
+ if self.product_name in _SPECIAL_ROOT_DEVICE_LIST:
+ return self.GetProp('service.adb.root') == '1'
self.RunShellCommand(['ls', '/root'], check_return=True)
return True
except device_errors.AdbCommandFailedError:
@@ -390,9 +420,14 @@ class DeviceUtils(object):
DeviceUnreachableError on missing device.
"""
if 'needs_su' not in self._cache:
+ cmd = '%s && ! ls /root' % self._Su('ls /root')
+ if self.product_name in _SPECIAL_ROOT_DEVICE_LIST:
+ if self.HasRoot():
+ self._cache['needs_su'] = False
+ return False
+ cmd = 'which which && which su'
try:
- self.RunShellCommand(
- '%s && ! ls /root' % self._Su('ls /root'), check_return=True,
+ self.RunShellCommand(cmd, shell=True, check_return=True,
timeout=self._default_timeout if timeout is DEFAULT else timeout,
retries=self._default_retries if retries is DEFAULT else retries)
self._cache['needs_su'] = True
@@ -400,6 +435,7 @@ class DeviceUtils(object):
self._cache['needs_su'] = False
return self._cache['needs_su']
+
def _Su(self, command):
if self.build_version_sdk >= version_codes.MARSHMALLOW:
return 'su 0 %s' % command
@@ -799,24 +835,27 @@ class DeviceUtils(object):
device_serial=self.serial)
@decorators.WithTimeoutAndRetriesFromInstance()
- def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None,
- run_as=None, as_root=False, single_line=False,
+ def RunShellCommand(self, cmd, shell=False, check_return=False, cwd=None,
+ env=None, run_as=None, as_root=False, single_line=False,
large_output=False, raw_output=False, timeout=None,
retries=None):
"""Run an ADB shell command.
- The command to run |cmd| should be a sequence of program arguments or else
- a single string.
+ The command to run |cmd| should be a sequence of program arguments
+ (preferred) or a single string with a shell script to run.
When |cmd| is a sequence, it is assumed to contain the name of the command
to run followed by its arguments. In this case, arguments are passed to the
- command exactly as given, without any further processing by the shell. This
- allows to easily pass arguments containing spaces or special characters
- without having to worry about getting quoting right. Whenever possible, it
- is recomended to pass |cmd| as a sequence.
+ command exactly as given, preventing any further processing by the shell.
+ This allows callers to easily pass arguments with spaces or special
+ characters without having to worry about quoting rules. Whenever possible,
+ it is recomended to pass |cmd| as a sequence.
- When |cmd| is given as a string, it will be interpreted and run by the
- shell on the device.
+ When |cmd| is passed as a single string, |shell| should be set to True.
+ The command will be interpreted and run by the shell on the device,
+ allowing the use of shell features such as pipes, wildcards, or variables.
+ Failing to set shell=True will issue a warning, but this will be changed
+ to a hard failure in the future (see: catapult:#3242).
This behaviour is consistent with that of command runners in cmd_helper as
well as Python's own subprocess.Popen.
@@ -825,8 +864,9 @@ class DeviceUtils(object):
have switched to the new behaviour.
Args:
- cmd: A string with the full command to run on the device, or a sequence
- containing the command and its arguments.
+ cmd: A sequence containing the command to run and its arguments, or a
+ string with a shell script to run (should also set shell=True).
+ shell: A boolean indicating whether shell features may be used in |cmd|.
check_return: A boolean indicating whether or not the return code should
be checked.
cwd: The device directory in which the command should be run.
@@ -906,7 +946,13 @@ class DeviceUtils(object):
else:
raise
- if not isinstance(cmd, basestring):
+ if isinstance(cmd, basestring):
+ if not shell:
+ logging.warning(
+ 'The command to run should preferably be passed as a sequence of'
+ ' args. If shell features are needed (pipes, wildcards, variables)'
+ ' clients should explicitly set shell=True.')
+ else:
cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd)
if env:
env = ' '.join(env_quote(k, v) for k, v in env.iteritems())
@@ -941,7 +987,7 @@ class DeviceUtils(object):
PIPESTATUS_LEADER = 'PIPESTATUS: '
script += '; echo "%s${PIPESTATUS[@]}"' % PIPESTATUS_LEADER
- kwargs['check_return'] = True
+ kwargs.update(shell=True, check_return=True)
output = self.RunShellCommand(script, **kwargs)
pipestatus_line = output[-1]
@@ -1073,7 +1119,7 @@ class DeviceUtils(object):
package = component.split('/')[0]
shell_snippet = 'p=%s;%s' % (package,
cmd_helper.ShrinkToSnippet(cmd, 'p', package))
- return self.RunShellCommand(shell_snippet, check_return=True,
+ return self.RunShellCommand(shell_snippet, shell=True, check_return=True,
large_output=True)
@decorators.WithTimeoutAndRetriesFromInstance()
@@ -1145,7 +1191,7 @@ class DeviceUtils(object):
DeviceUnreachableError on missing device.
"""
cmd = 'p=%s;if [[ "$(ps)" = *$p* ]]; then am force-stop $p; fi'
- self.RunShellCommand(cmd % package, check_return=True)
+ self.RunShellCommand(cmd % package, shell=True, check_return=True)
@decorators.WithTimeoutAndRetriesFromInstance()
def ClearApplicationState(
@@ -1451,7 +1497,7 @@ class DeviceUtils(object):
quoted_dirs = ' '.join(cmd_helper.SingleQuote(d) for d in dirs)
self.RunShellCommand(
'unzip %s&&chmod -R 777 %s' % (device_temp.name, quoted_dirs),
- as_root=True,
+ shell=True, as_root=True,
env={'PATH': '%s:$PATH' % install_commands.BIN_DIR},
check_return=True)
finally:
@@ -1496,8 +1542,11 @@ class DeviceUtils(object):
paths = device_paths
if isinstance(paths, basestring):
paths = (paths,)
- condition = ' -a '.join('-e %s' % cmd_helper.SingleQuote(p) for p in paths)
- cmd = 'test %s' % condition
+ if not paths:
+ return True
+ cmd = ['test', '-e', paths[0]]
+ for p in paths[1:]:
+ cmd.extend(['-a', '-e', p])
try:
self.RunShellCommand(cmd, as_root=as_root, check_return=True,
timeout=timeout, retries=retries)
@@ -1603,7 +1652,7 @@ class DeviceUtils(object):
cmd = 'SRC=%s DEST=%s;cp "$SRC" "$DEST" && chmod 666 "$DEST"' % (
cmd_helper.SingleQuote(device_path),
cmd_helper.SingleQuote(device_temp.name))
- self.RunShellCommand(cmd, as_root=True, check_return=True)
+ self.RunShellCommand(cmd, shell=True, as_root=True, check_return=True)
return self._ReadFileWithPull(device_temp.name)
else:
return self._ReadFileWithPull(device_path)
@@ -1641,7 +1690,7 @@ class DeviceUtils(object):
# a shell command rather than pushing a file.
cmd = 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents),
cmd_helper.SingleQuote(device_path))
- self.RunShellCommand(cmd, as_root=as_root, check_return=True)
+ self.RunShellCommand(cmd, shell=True, as_root=as_root, check_return=True)
elif as_root and self.NeedsSU():
# Adb does not allow to "push with su", so we first push to a temp file
# on a safe location, and then copy it to the desired location with su.
@@ -1993,7 +2042,8 @@ class DeviceUtils(object):
'echo "%s">$c &&' % token +
'getprop'
)
- output = self.RunShellCommand(cmd, check_return=True, large_output=True)
+ output = self.RunShellCommand(
+ cmd, shell=True, check_return=True, large_output=True)
# Error-checking for this existing is done in GetExternalStoragePath().
self._cache['external_storage'] = output[0]
self._cache['prev_token'] = output[1]
@@ -2091,13 +2141,14 @@ class DeviceUtils(object):
return self.GetProp('ro.product.cpu.abi', cache=True)
@decorators.WithTimeoutAndRetriesFromInstance()
- def GetPids(self, process_name, timeout=None, retries=None):
- """Returns the PIDs of processes with the given name.
+ def GetPids(self, process_name=None, timeout=None, retries=None):
+ """Returns the PIDs of processes containing the given name as substring.
Note that the |process_name| is often the package name.
Args:
process_name: A string containing the process name to get the PIDs for.
+ If missing returns PIDs for all processes.
timeout: timeout in seconds
retries: number of retries
@@ -2111,8 +2162,17 @@ class DeviceUtils(object):
"""
procs_pids = collections.defaultdict(list)
try:
- ps_output = self._RunPipedShellCommand(
- 'ps | grep -F %s' % cmd_helper.SingleQuote(process_name))
+ ps_cmd = 'ps'
+ # ps behavior was changed in Android above N, http://crbug.com/686716
+ if (self.build_version_sdk >= version_codes.NOUGAT_MR1
+ and self.build_id[0] > 'N'):
+ ps_cmd = 'ps -e'
+ if process_name:
+ ps_output = self._RunPipedShellCommand(
+ '%s | grep -F %s' % (ps_cmd, cmd_helper.SingleQuote(process_name)))
+ else:
+ ps_output = self.RunShellCommand(
+ ps_cmd.split(), check_return=True, large_output=True)
except device_errors.AdbShellCommandFailedError as e:
if e.status and isinstance(e.status, list) and not e.status[0]:
# If ps succeeded but grep failed, there were no processes with the
@@ -2121,16 +2181,91 @@ class DeviceUtils(object):
else:
raise
+ process_name = process_name or ''
for line in ps_output:
try:
ps_data = line.split()
- if process_name in ps_data[-1]:
- pid, process = ps_data[1], ps_data[-1]
+ pid, process = ps_data[1], ps_data[-1]
+ if process_name in process and pid != 'PID':
procs_pids[process].append(pid)
except IndexError:
pass
return procs_pids
+ def GetApplicationPids(self, process_name, at_most_one=False, **kwargs):
+ """Returns the PID or PIDs of a given process name.
+
+ Note that the |process_name|, often the package name, must match exactly.
+
+ Args:
+ process_name: A string containing the process name to get the PIDs for.
+ at_most_one: A boolean indicating that at most one PID is expected to
+ be found.
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ A list of the PIDs for the named process. If at_most_one=True returns
+ the single PID found or None otherwise.
+
+ Raises:
+ CommandFailedError if at_most_one=True and more than one PID is found
+ for the named process.
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ pids = self.GetPids(process_name, **kwargs).get(process_name, [])
+ if at_most_one:
+ if len(pids) > 1:
+ raise device_errors.CommandFailedError(
+ 'Expected a single process but found PIDs: %s.' % ', '.join(pids),
+ device_serial=str(self))
+ return pids[0] if pids else None
+ else:
+ return pids
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def GetEnforce(self, timeout=None, retries=None):
+ """Get the current mode of SELinux.
+
+ Args:
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Returns:
+ True (enforcing), False (permissive), or None (disabled).
+
+ Raises:
+ CommandFailedError on failure.
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ output = self.RunShellCommand(
+ ['getenforce'], check_return=True, single_line=True).lower()
+ if output not in _SELINUX_MODE:
+ raise device_errors.CommandFailedError(
+ 'Unexpected getenforce output: %s' % output)
+ return _SELINUX_MODE[output]
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def SetEnforce(self, enabled, timeout=None, retries=None):
+ """Modify the mode SELinux is running in.
+
+ Args:
+ enabled: a boolean indicating whether to put SELinux in encorcing mode
+ (if True), or permissive mode (otherwise).
+ timeout: timeout in seconds
+ retries: number of retries
+
+ Raises:
+ CommandFailedError on failure.
+ CommandTimeoutError on timeout.
+ DeviceUnreachableError on missing device.
+ """
+ self.RunShellCommand(
+ ['setenforce', '1' if int(enabled) else '0'], as_root=True,
+ check_return=True)
+
@decorators.WithTimeoutAndRetriesFromInstance()
def TakeScreenshot(self, host_path=None, timeout=None, retries=None):
"""Takes a screenshot of the device.
@@ -2440,7 +2575,8 @@ class DeviceUtils(object):
logger.info('Restarting adbd on device.')
with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
self.WriteFile(script.name, _RESTART_ADBD_SCRIPT)
- self.RunShellCommand(['source', script.name], as_root=True)
+ self.RunShellCommand(
+ ['source', script.name], check_return=True, as_root=True)
self.adb.WaitForDevice()
@decorators.WithTimeoutAndRetriesFromInstance()
@@ -2456,7 +2592,7 @@ class DeviceUtils(object):
permissions.append('android.permission.READ_EXTERNAL_STORAGE')
cmd = '&&'.join('pm grant %s %s' % (package, p) for p in permissions)
if cmd:
- output = self.RunShellCommand(cmd, check_return=True)
+ output = self.RunShellCommand(cmd, shell=True, check_return=True)
if output:
logger.warning('Possible problem when granting permissions. Blacklist '
'may need to be updated.')
@@ -2509,5 +2645,5 @@ class DeviceUtils(object):
if screen_test():
logger.info('Screen already in expected state.')
return
- self.RunShellCommand('input keyevent 26')
+ self.SendKeyEvent(keyevent.KEYCODE_POWER)
timeout_retry.WaitFor(screen_test, wait_period=1)