1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tradefed.targetprep; 17 18 import com.android.tradefed.build.IBuildInfo; 19 import com.android.tradefed.build.IDeviceBuildInfo; 20 import com.android.tradefed.command.remote.DeviceDescriptor; 21 import com.android.tradefed.config.Option; 22 import com.android.tradefed.config.Option.Importance; 23 import com.android.tradefed.config.OptionClass; 24 import com.android.tradefed.device.DeviceNotAvailableException; 25 import com.android.tradefed.device.ITestDevice; 26 import com.android.tradefed.log.LogUtil.CLog; 27 import com.android.tradefed.testtype.IAbi; 28 import com.android.tradefed.testtype.IAbiReceiver; 29 import com.android.tradefed.util.AaptParser; 30 import com.android.tradefed.util.AbiFormatter; 31 import com.android.tradefed.util.BuildTestsZipUtils; 32 33 import java.io.File; 34 import java.io.IOException; 35 import java.util.ArrayList; 36 import java.util.Collection; 37 import java.util.List; 38 39 /** 40 * A {@link ITargetPreparer} that installs one or more apps from a {@link 41 * IDeviceBuildInfo#getTestsDir()} folder onto device. 42 * 43 * <p>This preparer will look in alternate directories if the tests zip does not exist or does not 44 * contain the required apk. The search will go in order from the last alternative dir specified to 45 * the first. 46 */ 47 @OptionClass(alias = "tests-zip-app") 48 public class TestAppInstallSetup extends BaseTargetPreparer 49 implements ITargetCleaner, IAbiReceiver { 50 51 /** The mode the apk should be install in. */ 52 private enum InstallMode { 53 FULL, 54 INSTANT, 55 } 56 57 // An error message that occurs when a test APK is already present on the DUT, 58 // but cannot be updated. When this occurs, the package is removed from the 59 // device so that installation can continue like normal. 60 private static final String INSTALL_FAILED_UPDATE_INCOMPATIBLE = 61 "INSTALL_FAILED_UPDATE_INCOMPATIBLE"; 62 63 @Option(name = "test-file-name", 64 description = "the name of a test zip file to install on device. Can be repeated.", 65 importance = Importance.IF_UNSET) 66 private Collection<String> mTestFileNames = new ArrayList<String>(); 67 68 @Option( 69 name = "throw-if-not-found", 70 description = "Throw exception if the specified file is not found." 71 ) 72 private boolean mThrowIfNoFile = true; 73 74 @Option(name = AbiFormatter.FORCE_ABI_STRING, 75 description = AbiFormatter.FORCE_ABI_DESCRIPTION, 76 importance = Importance.IF_UNSET) 77 private String mForceAbi = null; 78 79 @Option(name = "install-arg", 80 description = "Additional arguments to be passed to install command, " 81 + "including leading dash, e.g. \"-d\"") 82 private Collection<String> mInstallArgs = new ArrayList<>(); 83 84 @Option(name = "cleanup-apks", 85 description = "Whether apks installed should be uninstalled after test. Note that the " 86 + "preparer does not verify if the apks are successfully removed.") 87 private boolean mCleanup = false; 88 89 @Option(name = "alt-dir", 90 description = "Alternate directory to look for the apk if the apk is not in the tests " 91 + "zip file. For each alternate dir, will look in //, //data/app, //DATA/app, " 92 + "//DATA/app/apk_name/ and //DATA/priv-app/apk_name/. Can be repeated. " 93 + "Look for apks in last alt-dir first.") 94 private List<File> mAltDirs = new ArrayList<>(); 95 96 @Option(name = "alt-dir-behavior", description = "The order of alternate directory to be used " 97 + "when searching for apks to install") 98 private AltDirBehavior mAltDirBehavior = AltDirBehavior.FALLBACK; 99 100 @Option(name = "instant-mode", description = "Whether or not to install apk in instant mode.") 101 private boolean mInstantMode = false; 102 103 @Option( 104 name = "force-install-mode", 105 description = 106 "Force the preparer to ignore instant-mode option, and install in the requested mode." 107 ) 108 private InstallMode mInstallMode = null; 109 110 private IAbi mAbi = null; 111 private Integer mUserId = null; 112 private Boolean mGrantPermission = null; 113 114 private List<String> mPackagesInstalled = null; 115 116 /** 117 * Adds a file to the list of apks to install 118 * 119 * @param fileName 120 */ addTestFileName(String fileName)121 public void addTestFileName(String fileName) { 122 mTestFileNames.add(fileName); 123 } 124 125 /** Returns a copy of the list of specified test apk names. */ getTestsFileName()126 public List<String> getTestsFileName() { 127 return new ArrayList<String>(mTestFileNames); 128 } 129 130 /** Sets whether or not the installed apk should be cleaned on tearDown */ setCleanApk(boolean shouldClean)131 public void setCleanApk(boolean shouldClean) { 132 mCleanup = shouldClean; 133 } 134 135 /** 136 * If the apk should be installed for a particular user, sets the id of the user to install for. 137 */ setUserId(int userId)138 public void setUserId(int userId) { 139 mUserId = userId; 140 } 141 142 /** If a userId is provided, grantPermission can be set for the apk installation. */ setShouldGrantPermission(boolean shouldGrant)143 public void setShouldGrantPermission(boolean shouldGrant) { 144 mGrantPermission = shouldGrant; 145 } 146 147 /** Adds one apk installation arg to be used. */ addInstallArg(String arg)148 public void addInstallArg(String arg) { 149 mInstallArgs.add(arg); 150 } 151 152 /** 153 * Resolve the actual apk path based on testing artifact information inside build info. 154 * 155 * @param buildInfo build artifact information 156 * @param apkFileName filename of the apk to install 157 * @param device the {@link ITestDevice} being prepared 158 * @return a {@link File} representing the physical apk file on host or {@code null} if the file 159 * does not exist. 160 */ getLocalPathForFilename( IBuildInfo buildInfo, String apkFileName, ITestDevice device)161 protected File getLocalPathForFilename( 162 IBuildInfo buildInfo, String apkFileName, ITestDevice device) throws TargetSetupError { 163 try { 164 return BuildTestsZipUtils.getApkFile(buildInfo, apkFileName, mAltDirs, mAltDirBehavior, 165 false /* use resource as fallback */, 166 null /* device signing key */); 167 } catch (IOException ioe) { 168 throw new TargetSetupError("failed to resolve apk path", ioe, 169 device.getDeviceDescriptor()); 170 } 171 } 172 173 /** {@inheritDoc} */ 174 @Override setUp(ITestDevice device, IBuildInfo buildInfo)175 public void setUp(ITestDevice device, IBuildInfo buildInfo) 176 throws TargetSetupError, DeviceNotAvailableException { 177 if (mTestFileNames == null || mTestFileNames.size() == 0) { 178 CLog.i("No test apps to install, skipping"); 179 return; 180 } 181 if (mCleanup) { 182 mPackagesInstalled = new ArrayList<>(); 183 } 184 185 for (String testAppName : mTestFileNames) { 186 if (testAppName == null || testAppName.trim().isEmpty()) { 187 continue; 188 } 189 File testAppFile = getLocalPathForFilename(buildInfo, testAppName, device); 190 if (testAppFile == null) { 191 if (mThrowIfNoFile) { 192 throw new TargetSetupError( 193 String.format("Test app %s was not found.", testAppName), 194 device.getDeviceDescriptor()); 195 } else { 196 CLog.d("Test app %s was not found.", testAppName); 197 continue; 198 } 199 } 200 if (!testAppFile.canRead()) { 201 if (mThrowIfNoFile) { 202 throw new TargetSetupError( 203 String.format("Could not read file %s.", testAppName), 204 device.getDeviceDescriptor()); 205 } else { 206 CLog.d("Could not read file %s.", testAppName); 207 continue; 208 } 209 } 210 // resolve abi flags 211 if (mAbi != null && mForceAbi != null) { 212 throw new IllegalStateException("cannot specify both abi flags"); 213 } 214 String abiName = null; 215 if (mAbi != null) { 216 abiName = mAbi.getName(); 217 } else if (mForceAbi != null) { 218 abiName = AbiFormatter.getDefaultAbi(device, mForceAbi); 219 } 220 if (abiName != null) { 221 mInstallArgs.add(String.format("--abi %s", abiName)); 222 } 223 // Handle instant mode: if we are forced in one installation mode or not. 224 if (mInstallMode != null) { 225 if (InstallMode.INSTANT.equals(mInstallMode)) { 226 mInstallArgs.add("--instant"); 227 } 228 } else { 229 if (mInstantMode) { 230 mInstallArgs.add("--instant"); 231 } 232 } 233 234 String packageName = parsePackageName(testAppFile, device.getDeviceDescriptor()); 235 CLog.d("Installing apk from %s ...", testAppFile.getAbsolutePath()); 236 String result = installPackage(device, testAppFile); 237 if (result != null) { 238 if (result.startsWith(INSTALL_FAILED_UPDATE_INCOMPATIBLE)) { 239 // Try to uninstall package and reinstall. 240 uninstallPackage(device, packageName); 241 result = installPackage(device, testAppFile); 242 } 243 } 244 if (result != null) { 245 throw new TargetSetupError( 246 String.format("Failed to install %s on %s. Reason: '%s'", testAppName, 247 device.getSerialNumber(), result), device.getDeviceDescriptor()); 248 } 249 if (mCleanup) { 250 mPackagesInstalled.add(packageName); 251 } 252 } 253 } 254 255 @Override setAbi(IAbi abi)256 public void setAbi(IAbi abi) { 257 mAbi = abi; 258 } 259 260 @Override getAbi()261 public IAbi getAbi() { 262 return mAbi; 263 } 264 265 /** 266 * {@inheritDoc} 267 */ 268 @Override tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)269 public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) 270 throws DeviceNotAvailableException { 271 if (mCleanup && mPackagesInstalled != null && !(e instanceof DeviceNotAvailableException)) { 272 for (String packageName : mPackagesInstalled) { 273 uninstallPackage(device, packageName); 274 } 275 } 276 } 277 278 /** 279 * Set an alternate directory. 280 */ setAltDir(File altDir)281 public void setAltDir(File altDir) { 282 mAltDirs.add(altDir); 283 } 284 285 /** 286 * Set an alternate directory behaviors. 287 */ setAltDirBehavior(AltDirBehavior altDirBehavior)288 public void setAltDirBehavior(AltDirBehavior altDirBehavior) { 289 mAltDirBehavior = altDirBehavior; 290 } 291 292 /** Attempt to install a package on the device. */ installPackage(ITestDevice device, File testAppFile)293 private String installPackage(ITestDevice device, File testAppFile) 294 throws DeviceNotAvailableException { 295 // Handle the different install use cases (with or without a user) 296 if (mUserId == null) { 297 return device.installPackage(testAppFile, true, mInstallArgs.toArray(new String[] {})); 298 } else if (mGrantPermission != null) { 299 return device.installPackageForUser( 300 testAppFile, 301 true, 302 mGrantPermission, 303 mUserId, 304 mInstallArgs.toArray(new String[] {})); 305 } else { 306 return device.installPackageForUser( 307 testAppFile, true, mUserId, mInstallArgs.toArray(new String[] {})); 308 } 309 } 310 311 /** Attempt to remove the package from the device. */ uninstallPackage(ITestDevice device, String packageName)312 private void uninstallPackage(ITestDevice device, String packageName) 313 throws DeviceNotAvailableException { 314 String msg = device.uninstallPackage(packageName); 315 if (msg != null) { 316 CLog.w(String.format("error uninstalling package '%s': %s", packageName, msg)); 317 } 318 } 319 320 /** Get the package name from the test app. */ parsePackageName(File testAppFile, DeviceDescriptor deviceDescriptor)321 protected String parsePackageName(File testAppFile, DeviceDescriptor deviceDescriptor) 322 throws TargetSetupError { 323 AaptParser parser = AaptParser.parse(testAppFile); 324 if (parser == null) { 325 throw new TargetSetupError("apk installed but AaptParser failed", deviceDescriptor); 326 } 327 return parser.getPackageName(); 328 } 329 } 330