From 1890db7c04f5b1ca8acadc14d89f31c847d0f119 Mon Sep 17 00:00:00 2001 From: Sebastian Goscik Date: Mon, 15 Feb 2016 14:43:30 +0000 Subject: [PATCH 01/10] AndroidTarget: Updated ANDROID_SCREEN_STATE_REGEX The format of "dumpsys power" changed in Android M --- devlib/target.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devlib/target.py b/devlib/target.py index d808534..5303f3a 100644 --- a/devlib/target.py +++ b/devlib/target.py @@ -20,7 +20,7 @@ from devlib.utils.types import integer, boolean, bitmask, identifier, caseless_s FSTAB_ENTRY_REGEX = re.compile(r'(\S+) on (\S+) type (\S+) \((\S+)\)') -ANDROID_SCREEN_STATE_REGEX = re.compile('(?:mPowerState|mScreenOn)=([0-9]+|true|false)', +ANDROID_SCREEN_STATE_REGEX = re.compile('(?:mPowerState|mScreenOn|Display Power: state)=([0-9]+|true|false|ON|OFF)', re.IGNORECASE) ANDROID_SCREEN_RESOLUTION_REGEX = re.compile(r'mUnrestrictedScreen=\(\d+,\d+\)' r'\s+(?P\d+)x(?P\d+)') From 33603c6648e4965fae34a78efede5b53eff3c13f Mon Sep 17 00:00:00 2001 From: Sebastian Goscik Date: Mon, 15 Feb 2016 15:07:19 +0000 Subject: [PATCH 02/10] Target: Added directory_exists method --- devlib/target.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/devlib/target.py b/devlib/target.py index 5303f3a..1fb2114 100644 --- a/devlib/target.py +++ b/devlib/target.py @@ -348,6 +348,12 @@ class Target(object): command = 'if [ -e \'{}\' ]; then echo 1; else echo 0; fi' return boolean(self.execute(command.format(filepath)).strip()) + def directory_exists(self, filepath): + output = self.execute('if [ -d \'{}\' ]; then echo 1; else echo 0; fi'.format(filepath)) + # output from ssh my contain part of the expression in the buffer, + # split out everything except the last word. + return boolean(output.split()[-1]) # pylint: disable=maybe-no-member + def list_file_systems(self): output = self.execute('mount') fstab = [] From 84151f953ab9f872ef7674146e007dbba18a9b1e Mon Sep 17 00:00:00 2001 From: Sebastian Goscik Date: Mon, 15 Feb 2016 15:09:27 +0000 Subject: [PATCH 03/10] Target: Modified get_installed search order Changed get_installed to search self.executables_directory first. This means that user provided binaries will be used over system ones. Also added the option to not search system folders for a binary. --- devlib/target.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/devlib/target.py b/devlib/target.py index 1fb2114..2b88589 100644 --- a/devlib/target.py +++ b/devlib/target.py @@ -421,16 +421,19 @@ class Target(object): def uninstall(self, name): raise NotImplementedError() - def get_installed(self, name): - for path in self.getenv('PATH').split(self.path.pathsep): - try: - if name in self.list_directory(path): - return self.path.join(path, name) - except TargetError: - pass # directory does not exist or no executable premssions + def get_installed(self, name, search_system_binaries=True): + # Check user installed binaries first if self.file_exists(self.executables_directory): if name in self.list_directory(self.executables_directory): return self.path.join(self.executables_directory, name) + # Fall back to binaries in PATH + if search_system_binaries: + for path in self.getenv('PATH').split(self.path.pathsep): + try: + if name in self.list_directory(path): + return self.path.join(path, name) + except TargetError: + pass # directory does not exist or no executable premssions which = get_installed From be8f972f605f5e387108e409f243c1ee7c0c87f7 Mon Sep 17 00:00:00 2001 From: Sebastian Goscik Date: Mon, 15 Feb 2016 15:11:38 +0000 Subject: [PATCH 04/10] Target: Added install_if_needed method This method will first search the target for a binary, only installing it, if one was not found. --- devlib/target.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/devlib/target.py b/devlib/target.py index 2b88589..46d4486 100644 --- a/devlib/target.py +++ b/devlib/target.py @@ -437,6 +437,14 @@ class Target(object): which = get_installed + def install_if_needed(self, host_path, search_system_binaries=True): + + binary_path = self.get_installed(os.path.split(host_path)[1], + search_system_binaries=search_system_binaries) + if not binary_path: + binary_path = self.install(host_path) + return binary_path + def is_installed(self, name): return bool(self.get_installed(name)) From cafeb81b83b83e3cd2449b7f9851529b6e155cdb Mon Sep 17 00:00:00 2001 From: Sebastian Goscik Date: Mon, 15 Feb 2016 15:17:32 +0000 Subject: [PATCH 05/10] AndroidTarget: Added android_id property Get the device's ANDROID_ID. Which is "A 64-bit number (as a hex string) that is randomly generated when the user first sets up the device and should remain constant for the lifetime of the user's device." --- devlib/target.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/devlib/target.py b/devlib/target.py index 46d4486..98bb268 100644 --- a/devlib/target.py +++ b/devlib/target.py @@ -643,6 +643,21 @@ class AndroidTarget(Target): def adb_name(self): return self.conn.device + @property + def android_id(self): + """ + Get the device's ANDROID_ID. Which is + + "A 64-bit number (as a hex string) that is randomly generated when the user + first sets up the device and should remain constant for the lifetime of the + user's device." + + .. note:: This will get reset on userdata erasure. + + """ + output = self.execute('content query --uri content://settings/secure --projection value --where "name=\'android_id\'"').strip() + return output.split('value=')[-1] + @property @memoized def screen_resolution(self): From 880a0bcb7c986cf53e6778c1646cfe10f92865a3 Mon Sep 17 00:00:00 2001 From: Sebastian Goscik Date: Mon, 15 Feb 2016 15:19:47 +0000 Subject: [PATCH 06/10] AndroidTarget: Added swipe direction to swipe_to_unlock swipe_to_unlock can now do either horizontal or vertical swipes to unlock a target --- devlib/target.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/devlib/target.py b/devlib/target.py index 98bb268..acbd32d 100644 --- a/devlib/target.py +++ b/devlib/target.py @@ -788,13 +788,20 @@ class AndroidTarget(Target): # Android-specific - def swipe_to_unlock(self): + def swipe_to_unlock(self, direction="horizontal"): width, height = self.screen_resolution - swipe_heigh = height * 2 // 3 - start = 100 - stop = width - start command = 'input swipe {} {} {} {}' - self.execute(command.format(start, swipe_heigh, stop, swipe_heigh)) + if direction == "horizontal": + swipe_heigh = height * 2 // 3 + start = 100 + stop = width - start + self.execute(command.format(start, swipe_heigh, stop, swipe_heigh)) + if direction == "vertical": + swipe_middle = height / 2 + swipe_heigh = height * 2 // 3 + self.execute(command.format(swipe_middle, swipe_heigh, swipe_middle, 0)) + else: + raise DeviceError("Invalid swipe direction: {}".format(self.swipe_to_unlock)) def getprop(self, prop=None): props = AndroidProperties(self.execute('getprop')) From aab487c1ac91197ba8f285415481ec80fe9880c1 Mon Sep 17 00:00:00 2001 From: Sebastian Goscik Date: Mon, 15 Feb 2016 15:21:40 +0000 Subject: [PATCH 07/10] pylint --- devlib/platform/__init__.py | 3 +-- devlib/target.py | 4 +--- devlib/utils/android.py | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/devlib/platform/__init__.py b/devlib/platform/__init__.py index beda557..025d9f4 100644 --- a/devlib/platform/__init__.py +++ b/devlib/platform/__init__.py @@ -68,7 +68,7 @@ class Platform(object): def _identify_big_core(self): for core in self.core_names: - if core.upper() in BIG_CPUS: + if core.upper() in BIG_CPUS: return core big_idx = self.core_clusters.index(max(self.core_clusters)) return self.core_names[big_idx] @@ -88,4 +88,3 @@ class Platform(object): if core != self.big_core: self.little_core = core break - diff --git a/devlib/target.py b/devlib/target.py index acbd32d..fdd76fb 100644 --- a/devlib/target.py +++ b/devlib/target.py @@ -669,7 +669,6 @@ class AndroidTarget(Target): else: return (0, 0) - def reset(self, fastboot=False): # pylint: disable=arguments-differ try: self.execute('reboot {}'.format(fastboot and 'fastboot' or ''), @@ -856,7 +855,7 @@ class AndroidTarget(Target): self.remove(on_device_executable, as_root=self.is_rooted) def dump_logcat(self, filepath, filter=None, append=False, timeout=30): # pylint: disable=redefined-builtin - op = '>>' if append == True else '>' + op = '>>' if append else '>' filtstr = ' -s {}'.format(filter) if filter else '' command = 'logcat -d{} {} {}'.format(filtstr, op, filepath) adb_command(self.adb_name, command, timeout=timeout) @@ -1071,4 +1070,3 @@ def _get_part_name(section): if name is None: name = '{}/{}/{}'.format(implementer, part, variant) return name - diff --git a/devlib/utils/android.py b/devlib/utils/android.py index ad637fa..cbbfd09 100644 --- a/devlib/utils/android.py +++ b/devlib/utils/android.py @@ -175,7 +175,7 @@ class AdbConnection(object): timeout = self.timeout # Pull all files matching a wildcard expression if os.path.isdir(dest) and \ - ('*' in source or '?' in source): + ('*' in source or '?' in source): command = 'shell ls {}'.format(source) output = adb_command(self.device, command, timeout=timeout) for line in output.splitlines(): @@ -241,7 +241,7 @@ def adb_get_device(timeout=None): return output[1].split('\t')[0] elif output_length > 3: message = '{} Android devices found; either explicitly specify ' +\ - 'the device you want, or make sure only one is connected.' + 'the device you want, or make sure only one is connected.' raise HostError(message.format(output_length - 2)) else: if timeout < time.time() - start: From 1424cebb909a80af3a688cc7a5a762b2f3099c59 Mon Sep 17 00:00:00 2001 From: Sebastian Goscik Date: Mon, 15 Feb 2016 15:27:19 +0000 Subject: [PATCH 08/10] Added quotes around commands using raw paths This fixes issues with spaces in path names. --- devlib/target.py | 12 ++++++------ devlib/utils/android.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/devlib/target.py b/devlib/target.py index fdd76fb..78697ca 100644 --- a/devlib/target.py +++ b/devlib/target.py @@ -772,17 +772,17 @@ class AndroidTarget(Target): self.conn.push(source, dest, timeout=timeout) else: device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep)) - self.execute('mkdir -p {}'.format(self.path.dirname(device_tempfile))) + self.execute("mkdir -p '{}'".format(self.path.dirname(device_tempfile))) self.conn.push(source, device_tempfile, timeout=timeout) - self.execute('cp {} {}'.format(device_tempfile, dest), as_root=True) + self.execute("cp '{}' '{}'".format(device_tempfile, dest), as_root=True) def pull(self, source, dest, as_root=False, timeout=None): # pylint: disable=arguments-differ if not as_root: self.conn.pull(source, dest, timeout=timeout) else: device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep)) - self.execute('mkdir -p {}'.format(self.path.dirname(device_tempfile))) - self.execute('cp {} {}'.format(source, device_tempfile), as_root=True) + self.execute("mkdir -p '{}'".format(self.path.dirname(device_tempfile))) + self.execute("cp '{}' '{}'".format(source, device_tempfile), as_root=True) self.conn.pull(device_tempfile, dest, timeout=timeout) # Android-specific @@ -829,7 +829,7 @@ class AndroidTarget(Target): def install_apk(self, filepath, timeout=None): # pylint: disable=W0221 ext = os.path.splitext(filepath)[1].lower() if ext == '.apk': - return adb_command(self.adb_name, "install {}".format(filepath), timeout=timeout) + return adb_command(self.adb_name, "install '{}'".format(filepath), timeout=timeout) else: raise TargetError('Can\'t install {}: unsupported format.'.format(filepath)) @@ -842,7 +842,7 @@ class AndroidTarget(Target): if on_device_file != on_device_executable: self.execute('cp {} {}'.format(on_device_file, on_device_executable), as_root=self.is_rooted) self.remove(on_device_file, as_root=self.is_rooted) - self.execute('chmod 0777 {}'.format(on_device_executable), as_root=self.is_rooted) + self.execute("chmod 0777 '{}'".format(on_device_executable), as_root=self.is_rooted) self._installed_binaries[executable_name] = on_device_executable return on_device_executable diff --git a/devlib/utils/android.py b/devlib/utils/android.py index cbbfd09..eeb05ee 100644 --- a/devlib/utils/android.py +++ b/devlib/utils/android.py @@ -167,7 +167,7 @@ class AdbConnection(object): def push(self, source, dest, timeout=None): if timeout is None: timeout = self.timeout - command = 'push {} {}'.format(source, dest) + command = "push '{}' '{}'".format(source, dest) return adb_command(self.device, command, timeout=timeout) def pull(self, source, dest, timeout=None): @@ -179,10 +179,10 @@ class AdbConnection(object): command = 'shell ls {}'.format(source) output = adb_command(self.device, command, timeout=timeout) for line in output.splitlines(): - command = 'pull {} {}'.format(line, dest) + command = "pull '{}' '{}'".format(line, dest) adb_command(self.device, command, timeout=timeout) return - command = 'pull {} {}'.format(source, dest) + command = "pull '{}' '{}'".format(source, dest) return adb_command(self.device, command, timeout=timeout) def execute(self, command, timeout=None, check_exit_code=False, as_root=False): From ff8261e44b4d04d11ced9abdc51a1e4473452114 Mon Sep 17 00:00:00 2001 From: Sebastian Goscik Date: Mon, 15 Feb 2016 15:28:20 +0000 Subject: [PATCH 09/10] AndroidTarget: Updated default executables_directory Now defaults to '/data/local/tmp' which is both executable and writable on all android devices, including production ones. --- devlib/target.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devlib/target.py b/devlib/target.py index 78697ca..da3d2af 100644 --- a/devlib/target.py +++ b/devlib/target.py @@ -880,7 +880,7 @@ class AndroidTarget(Target): self.working_directory = '/data/local/tmp/devlib-target' self._file_transfer_cache = self.path.join(self.working_directory, '.file-cache') if self.executables_directory is None: - self.executables_directory = self.path.join(self.working_directory, 'bin') + self.executables_directory = '/data/local/tmp/bin' def _ensure_executables_directory_is_writable(self): matched = [] From 0c11289e189b541b740d18ecea79514ad76c2f66 Mon Sep 17 00:00:00 2001 From: Sebastian Goscik Date: Mon, 15 Feb 2016 15:35:56 +0000 Subject: [PATCH 10/10] Android: Updated ANDROID_VERSION_MAP --- devlib/utils/android.py | 1 + 1 file changed, 1 insertion(+) diff --git a/devlib/utils/android.py b/devlib/utils/android.py index eeb05ee..8328b22 100644 --- a/devlib/utils/android.py +++ b/devlib/utils/android.py @@ -39,6 +39,7 @@ AM_START_ERROR = re.compile(r"Error: Activity class {[\w|.|/]*} does not exist") # See: # http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels ANDROID_VERSION_MAP = { + 23: 'MARSHMALLOW', 22: 'LOLLYPOP_MR1', 21: 'LOLLYPOP', 20: 'KITKAT_WATCH',