diff --git a/devlib/utils/android.py b/devlib/utils/android.py index 1643b7a..3a978c8 100755 --- a/devlib/utils/android.py +++ b/devlib/utils/android.py @@ -46,6 +46,7 @@ logger = logging.getLogger('android') MAX_ATTEMPTS = 5 AM_START_ERROR = re.compile(r"Error: Activity.*") +AAPT_BADGING_OUTPUT = re.compile(r"no dump ((file)|(apk)) specified", re.IGNORECASE) # See: # http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels @@ -93,6 +94,7 @@ android_home = None platform_tools = None adb = None aapt = None +aapt_version = None fastboot = None @@ -201,7 +203,9 @@ class ApkInfo(object): @property def activities(self): if self._activities is None: - cmd = [aapt, 'dump', 'xmltree', self._apk_path, + file_flag = '--file' if aapt_version == 2 else '' + cmd = [aapt, 'dump', 'xmltree', + self._apk_path, '{}'.format(file_flag), 'AndroidManifest.xml'] matched_activities = self.activity_regex.finditer(self._run(cmd)) self._activities = [m.group('name') for m in matched_activities] @@ -644,6 +648,7 @@ class _AndroidEnvironment(object): self.build_tools = None self.adb = None self.aapt = None + self.aapt_version = None self.fastboot = None @@ -676,31 +681,66 @@ def _init_common(env): def _discover_build_tools(env): logger.debug('ANDROID_HOME: {}'.format(env.android_home)) build_tools_directory = os.path.join(env.android_home, 'build-tools') - if not os.path.isdir(build_tools_directory): - msg = '''ANDROID_HOME ({}) does not appear to have valid Android SDK install - (cannot find build-tools)''' - raise HostError(msg.format(env.android_home)) - env.build_tools = build_tools_directory + if os.path.isdir(build_tools_directory): + env.build_tools = build_tools_directory + +def _check_supported_aapt2(binary): + # At time of writing the version argument of aapt2 is not helpful as + # the output is only a placeholder that does not distinguish between versions + # with and without support for badging. Unfortunately aapt has been + # deprecated and fails to parse some valid apks so we will try to favour + # aapt2 if possible else will fall back to aapt. + # Try to execute the badging command and check if we get an expected error + # message as opposed to an unknown command error to determine if we have a + # suitable version. + cmd = '{} dump badging'.format(binary) + result = subprocess.run(cmd.encode('utf-8'), shell=True, stderr=subprocess.PIPE) + supported = bool(AAPT_BADGING_OUTPUT.search(result.stderr.decode('utf-8'))) + msg = 'Found a {} aapt2 binary at: {}' + logger.debug(msg.format('supported' if supported else 'unsupported', binary)) + return supported def _discover_aapt(env): if env.build_tools: + aapt_path = '' + aapt2_path = '' versions = os.listdir(env.build_tools) for version in reversed(sorted(versions)): - aapt_path = os.path.join(env.build_tools, version, 'aapt') - if os.path.isfile(aapt_path): - logger.debug('Using aapt for version {}'.format(version)) - env.aapt = aapt_path + if not aapt2_path and not os.path.isfile(aapt2_path): + aapt2_path = os.path.join(env.build_tools, version, 'aapt2') + if not aapt_path and not os.path.isfile(aapt_path): + aapt_path = os.path.join(env.build_tools, version, 'aapt') + aapt_version = 1 break + # Use aapt2 only if present and we have a suitable version + if aapt2_path and _check_supported_aapt2(aapt2_path): + aapt_path = aapt2_path + aapt_version = 2 + + # Use the aapt version discoverted from build tools. + if aapt_path: + logger.debug('Using {} for version {}'.format(aapt_path, version)) + env.aapt = aapt_path + env.aapt_version = aapt_version + return + + # Try detecting aapt2 and aapt from PATH if not env.aapt: - env.aapt = which(aapt) + aapt2_path = which('aapt2') + if _check_supported_aapt2(aapt2_path): + env.aapt = aapt2_path + env.aapt_version = 2 + else: + env.aapt = which('aapt') + env.aapt_version = 1 if not env.aapt: - raise HostError('aapt not found. Please make sure it is avaliable in PATH' + raise HostError('aapt/aapt2 not found. Please make sure it is avaliable in PATH' ' or at least one Android platform is installed') def _check_env(): - global android_home, platform_tools, adb, aapt # pylint: disable=W0603 + global android_home, platform_tools, adb, aapt, aapt_version # pylint: disable=W0603 if not android_home: android_home = os.getenv('ANDROID_HOME') if android_home: @@ -711,6 +751,7 @@ def _check_env(): platform_tools = _env.platform_tools adb = _env.adb aapt = _env.aapt + aapt_version = _env.aapt_version class LogcatMonitor(object): """