1#!/usr/bin/env python3 2# 3# Copyright 2019 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the 'License'); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an 'AS IS' BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import os 18import re 19 20from acts.libs.proc import job 21 22PKG_NAME_PATTERN = r"^package:\s+name='(?P<pkg_name>.*?)'" 23PM_PATH_PATTERN = r"^package:(?P<apk_path>.*)" 24 25 26class AppInstaller(object): 27 """Class that represents an app on an Android device. Includes methods 28 for install, uninstall, and getting info. 29 """ 30 def __init__(self, ad, apk_path): 31 """Initializes an AppInstaller. 32 33 Args: 34 ad: device to install the apk 35 apk_path: path to the apk 36 """ 37 self._ad = ad 38 self._apk_path = apk_path 39 self._pkg_name = None 40 41 @staticmethod 42 def pull_from_device(ad, pkg_name, dest): 43 """Initializes an AppInstaller by pulling the apk file from the device, 44 given the package name 45 46 Args: 47 ad: device on which the apk is installed 48 pkg_name: package name 49 dest: destination directory 50 (Note: If path represents a directory, it must already exist as 51 a directory) 52 53 Returns: AppInstaller object representing the pulled apk, or None if 54 package not installed 55 """ 56 if not ad.is_apk_installed(pkg_name): 57 ad.log.warning('Unable to find package %s on device. Pull aborted.' 58 % pkg_name) 59 return None 60 path_on_device = re.compile(PM_PATH_PATTERN).search( 61 ad.adb.shell('pm path %s' % pkg_name)).group('apk_path') 62 ad.pull_files(path_on_device, dest) 63 if os.path.isdir(dest): 64 dest = os.path.join(dest, os.path.basename(path_on_device)) 65 return AppInstaller(ad, dest) 66 67 @property 68 def apk_path(self): 69 return self._apk_path 70 71 @property 72 def pkg_name(self): 73 """Get the package name corresponding to the apk from aapt 74 75 Returns: The package name, or empty string if not found. 76 """ 77 if self._pkg_name is None: 78 dump = job.run( 79 'aapt dump badging %s' % self.apk_path, 80 ignore_status=True).stdout 81 match = re.compile(PKG_NAME_PATTERN).search(dump) 82 self._pkg_name = match.group('pkg_name') if match else '' 83 return self._pkg_name 84 85 def install(self, *extra_args): 86 """Installs the apk on the device. 87 88 Args: 89 extra_args: Additional flags to the ADB install command. 90 Note that '-r' is included by default. 91 """ 92 self._ad.log.info('Installing app %s from %s' % 93 (self.pkg_name, self.apk_path)) 94 args = '-r %s' % ' '.join(extra_args) 95 self._ad.adb.install('%s %s' % (args, self.apk_path)) 96 97 def uninstall(self, *extra_args): 98 """Uninstalls the apk from the device. 99 100 Args: 101 extra_args: Additional flags to the uninstall command. 102 """ 103 self._ad.log.info('Uninstalling app %s' % self.pkg_name) 104 if not self.is_installed(): 105 self._ad.log.warning('Unable to uninstall app %s. App is not ' 106 'installed.' % self.pkg_name) 107 return 108 self._ad.adb.shell( 109 'pm uninstall %s %s' % (' '.join(extra_args), self.pkg_name)) 110 111 def is_installed(self): 112 """Verifies that the apk is installed on the device. 113 114 Returns: True if the apk is installed on the device. 115 """ 116 if not self.pkg_name: 117 self._ad.log.warning('No package name found for %s' % self.apk_path) 118 return False 119 return self._ad.is_apk_installed(self.pkg_name) 120