1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 package android.appsecurity.cts; 17 18 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; 19 import com.android.tradefed.build.IBuildInfo; 20 import com.android.tradefed.device.DeviceNotAvailableException; 21 import com.android.tradefed.device.ITestDevice; 22 import com.android.tradefed.testtype.IAbi; 23 import com.android.tradefed.util.AbiUtils; 24 25 import junit.framework.TestCase; 26 27 import java.io.File; 28 import java.io.FileNotFoundException; 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * Base class for invoking the install-multiple command via ADB. Subclass this for less typing: 34 * 35 * <code> 36 * private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> { 37 * public InstallMultiple() { 38 * super(getDevice(), null, null); 39 * } 40 * } 41 * </code> 42 */ 43 public class BaseInstallMultiple<T extends BaseInstallMultiple<?>> { 44 private final ITestDevice mDevice; 45 private final IBuildInfo mBuild; 46 private final IAbi mAbi; 47 48 static class DeviceFile { 49 public final File localFile; 50 public final File remoteFile; 51 public final boolean addToInstallSession; 52 public final boolean manageRemoteFile; 53 DeviceFile(File localFile, File remoteFile, boolean addToInstallSession, boolean manageRemoteFile)54 private DeviceFile(File localFile, File remoteFile, boolean addToInstallSession, 55 boolean manageRemoteFile) { 56 this.localFile = localFile; 57 this.remoteFile = remoteFile; 58 this.addToInstallSession = addToInstallSession; 59 this.manageRemoteFile = manageRemoteFile; 60 } 61 addToSession(File file)62 static DeviceFile addToSession(File file) { 63 return new DeviceFile(file, file, true, true); 64 } 65 renameAndAddToSession(File localFile, File remoteFile)66 static DeviceFile renameAndAddToSession(File localFile, File remoteFile) { 67 return new DeviceFile(localFile, remoteFile, true, true); 68 } 69 pushOnly(File file)70 static DeviceFile pushOnly(File file) { 71 return new DeviceFile(file, file, false, true); 72 } 73 renameAndPushOnly(File localFile, File remoteFile)74 static DeviceFile renameAndPushOnly(File localFile, File remoteFile) { 75 return new DeviceFile(localFile, remoteFile, false, true); 76 } 77 addRemoteFileToSession(File remoteFile)78 static DeviceFile addRemoteFileToSession(File remoteFile) { 79 return new DeviceFile(null, remoteFile, true, false); 80 } 81 } 82 83 private final List<String> mArgs = new ArrayList<>(); 84 private final List<DeviceFile> mFilesToAdd = new ArrayList<>(); 85 private final List<String> mSplitsToRemove = new ArrayList<>(); 86 private boolean mUseNaturalAbi = false; 87 private boolean mUseIncremental = false; 88 private boolean mAddFileNamePrefix = true; 89 BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi)90 public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi) { 91 this(device, buildInfo, abi, true); 92 } 93 BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi, boolean grantPermissions)94 public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi, 95 boolean grantPermissions) { 96 mDevice = device; 97 mBuild = buildInfo; 98 mAbi = abi; 99 if (grantPermissions) { 100 addArg("-g"); 101 } 102 // Allow the install of test apps 103 addArg("-t"); 104 } 105 addArg(String arg)106 T addArg(String arg) { 107 mArgs.add(arg); 108 return (T) this; 109 } 110 addFile(String file)111 T addFile(String file) throws FileNotFoundException { 112 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); 113 mFilesToAdd.add(DeviceFile.addToSession(buildHelper.getTestFile(file, mAbi))); 114 return (T) this; 115 } 116 disableFileNamePrefix()117 T disableFileNamePrefix() { 118 mAddFileNamePrefix = false; 119 return (T) this; 120 } 121 addRemoteFile(String remoteFile)122 T addRemoteFile(String remoteFile) { 123 mFilesToAdd.add(DeviceFile.addRemoteFileToSession(new File(remoteFile))); 124 return (T) this; 125 } 126 renameAndAddFile(String localFile, String remoteFile)127 T renameAndAddFile(String localFile, String remoteFile) throws FileNotFoundException { 128 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); 129 mFilesToAdd.add(DeviceFile.renameAndAddToSession(buildHelper.getTestFile(localFile, mAbi), 130 buildHelper.getTestFile(remoteFile, mAbi))); 131 return (T) this; 132 } 133 pushFile(String file)134 T pushFile(String file) throws FileNotFoundException { 135 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); 136 mFilesToAdd.add(DeviceFile.pushOnly(buildHelper.getTestFile(file, mAbi))); 137 return (T) this; 138 } 139 renameAndPushFile(String localFile, String remoteFile)140 T renameAndPushFile(String localFile, String remoteFile) throws FileNotFoundException { 141 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); 142 mFilesToAdd.add(DeviceFile.renameAndPushOnly(buildHelper.getTestFile(localFile, mAbi), 143 buildHelper.getTestFile(remoteFile, mAbi))); 144 return (T) this; 145 } 146 removeSplit(String split)147 T removeSplit(String split) { 148 mSplitsToRemove.add(split); 149 return (T) this; 150 } 151 inheritFrom(String packageName)152 T inheritFrom(String packageName) { 153 addArg("-r"); 154 addArg("-p " + packageName); 155 return (T) this; 156 } 157 useNaturalAbi()158 T useNaturalAbi() { 159 mUseNaturalAbi = true; 160 return (T) this; 161 } 162 useIncremental()163 T useIncremental() { 164 mUseIncremental = true; 165 return (T) this; 166 } 167 allowTest()168 T allowTest() { 169 addArg("-t"); 170 return (T) this; 171 } 172 locationAuto()173 T locationAuto() { 174 addArg("--install-location 0"); 175 return (T) this; 176 } 177 locationInternalOnly()178 T locationInternalOnly() { 179 addArg("--install-location 1"); 180 return (T) this; 181 } 182 locationPreferExternal()183 T locationPreferExternal() { 184 addArg("--install-location 2"); 185 return (T) this; 186 } 187 forceUuid(String uuid)188 T forceUuid(String uuid) { 189 addArg("--force-uuid " + uuid); 190 return (T) this; 191 } 192 forUser(int userId)193 T forUser(int userId) { 194 addArg("--user " + userId); 195 return (T) this; 196 } 197 restrictPermissions()198 T restrictPermissions() { 199 addArg("--restrict-permissions"); 200 return (T) this; 201 } 202 forceQueryable()203 T forceQueryable() { 204 addArg("--force-queryable"); 205 return (T) this; 206 } 207 bypassLowTargetSdkBlock()208 T bypassLowTargetSdkBlock() { 209 addArg("--bypass-low-target-sdk-block"); 210 return (T) this; 211 } 212 213 deriveRemoteName(String originalName, int index)214 protected String deriveRemoteName(String originalName, int index) { 215 return (mAddFileNamePrefix ? index + "_" + originalName : originalName); 216 } 217 run()218 void run() throws DeviceNotAvailableException { 219 run(true, null); 220 } 221 run(boolean expectingSuccess)222 void run(boolean expectingSuccess) throws DeviceNotAvailableException { 223 run(expectingSuccess, null); 224 } 225 runExpectingFailure()226 void runExpectingFailure() throws DeviceNotAvailableException { 227 run(false, null); 228 } 229 runExpectingFailure(String failure)230 void runExpectingFailure(String failure) throws DeviceNotAvailableException { 231 run(false, failure); 232 } 233 run(boolean expectingSuccess, String failure)234 void run(boolean expectingSuccess, String failure) throws DeviceNotAvailableException { 235 if (mUseIncremental) { 236 runIncremental(expectingSuccess, failure); 237 } else { 238 runNonIncremental(expectingSuccess, failure); 239 } 240 cleanupDeviceFiles(); 241 } 242 runForResult()243 String runForResult() throws DeviceNotAvailableException { 244 String result; 245 if (mUseIncremental) { 246 result = runIncrementalForResult(); 247 } else { 248 result = runNonIncrementalForResult(); 249 } 250 cleanupDeviceFiles(); 251 return result; 252 } 253 runNonIncrementalForResult()254 private String runNonIncrementalForResult() throws DeviceNotAvailableException { 255 final ITestDevice device = mDevice; 256 257 // Create an install session 258 final StringBuilder cmd = new StringBuilder(); 259 cmd.append("pm install-create"); 260 for (String arg : mArgs) { 261 cmd.append(' ').append(arg); 262 } 263 if (!mUseNaturalAbi && mAbi != null) { 264 cmd.append(' ').append(AbiUtils.createAbiFlag(mAbi.getName())); 265 } 266 267 String result = device.executeShellCommand(cmd.toString()); 268 TestCase.assertTrue(result, result.startsWith("Success")); 269 270 final int start = result.lastIndexOf("["); 271 final int end = result.lastIndexOf("]"); 272 int sessionId = -1; 273 try { 274 if (start != -1 && end != -1 && start < end) { 275 sessionId = Integer.parseInt(result.substring(start + 1, end)); 276 } 277 } catch (NumberFormatException e) { 278 } 279 if (sessionId == -1) { 280 throw new IllegalStateException("Failed to create install session: " + result); 281 } 282 283 // Push our files into session. Ideally we'd use stdin streaming, 284 // but ddmlib doesn't support it yet. 285 for (int i = 0; i < mFilesToAdd.size(); i++) { 286 boolean manageRemoteFile = mFilesToAdd.get(i).manageRemoteFile; 287 final File localFile = mFilesToAdd.get(i).localFile; 288 final File remoteFile = mFilesToAdd.get(i).remoteFile; 289 final String remoteName = deriveRemoteName(remoteFile.getName(), i); 290 final String remotePath = "/data/local/tmp/" + remoteName; 291 if (manageRemoteFile && !device.pushFile(localFile, remotePath)) { 292 throw new IllegalStateException("Failed to push " + localFile); 293 } 294 295 if (!mFilesToAdd.get(i).addToInstallSession) { 296 continue; 297 } 298 299 cmd.setLength(0); 300 cmd.append("pm install-write"); 301 cmd.append(' ').append(sessionId); 302 cmd.append(' ').append(remoteName); 303 cmd.append(' ').append(remotePath); 304 305 result = device.executeShellCommand(cmd.toString()); 306 TestCase.assertTrue(result, result.startsWith("Success")); 307 } 308 309 for (int i = 0; i < mSplitsToRemove.size(); i++) { 310 final String split = mSplitsToRemove.get(i); 311 312 cmd.setLength(0); 313 cmd.append("pm install-remove"); 314 cmd.append(' ').append(sessionId); 315 cmd.append(' ').append(split); 316 317 result = device.executeShellCommand(cmd.toString()); 318 TestCase.assertTrue(result, result.startsWith("Success")); 319 } 320 321 // Everything staged; let's pull trigger 322 cmd.setLength(0); 323 cmd.append("pm install-commit"); 324 cmd.append(' ').append(sessionId); 325 326 return device.executeShellCommand(cmd.toString()).trim(); 327 } 328 runNonIncremental(boolean expectingSuccess, String failure)329 private void runNonIncremental(boolean expectingSuccess, String failure) 330 throws DeviceNotAvailableException { 331 String result = runNonIncrementalForResult(); 332 if (failure == null) { 333 if (expectingSuccess) { 334 TestCase.assertTrue(result, result.startsWith("Success")); 335 } else { 336 TestCase.assertFalse(result, result.startsWith("Success")); 337 } 338 } else { 339 TestCase.assertTrue(result, result.contains(failure)); 340 } 341 } 342 runIncrementalForResult()343 private String runIncrementalForResult() throws DeviceNotAvailableException { 344 final ITestDevice device = mDevice; 345 346 if (!mSplitsToRemove.isEmpty()) { 347 throw new IllegalStateException("Incremental sessions can't remove splits"); 348 } 349 350 // Create an install session 351 final StringBuilder cmd = new StringBuilder(); 352 cmd.append("pm install-incremental"); 353 for (String arg : mArgs) { 354 cmd.append(' ').append(arg); 355 } 356 if (!mUseNaturalAbi && mAbi != null) { 357 cmd.append(' ').append(AbiUtils.createAbiFlag(mAbi.getName())); 358 } 359 360 // Push our files into session. Ideally we'd use stdin streaming, 361 // but ddmlib doesn't support it yet. 362 for (int i = 0; i < mFilesToAdd.size(); i++) { 363 boolean manageRemoteFile = mFilesToAdd.get(i).manageRemoteFile; 364 final File localFile = mFilesToAdd.get(i).localFile; 365 final File remoteFile = mFilesToAdd.get(i).remoteFile; 366 final String remoteName = deriveRemoteName(remoteFile.getName(), i); 367 final String remotePath = "/data/local/tmp/" + remoteName; 368 if (manageRemoteFile && !device.pushFile(localFile, remotePath)) { 369 throw new IllegalStateException("Failed to push " + localFile); 370 } 371 372 if (!mFilesToAdd.get(i).addToInstallSession) { 373 continue; 374 } 375 376 cmd.append(' ').append(remotePath); 377 } 378 379 // Everything staged; let's pull trigger 380 return device.executeShellCommand(cmd.toString()).trim(); 381 } 382 runIncremental(boolean expectingSuccess, String failure)383 private void runIncremental(boolean expectingSuccess, String failure) 384 throws DeviceNotAvailableException { 385 String result = runIncrementalForResult(); 386 if (failure == null) { 387 if (expectingSuccess) { 388 TestCase.assertTrue(result, result.startsWith("Success")); 389 } else { 390 TestCase.assertFalse(result, result.startsWith("Success")); 391 } 392 } else { 393 TestCase.assertTrue(result, result.contains(failure)); 394 } 395 } 396 cleanupDeviceFiles()397 private void cleanupDeviceFiles() throws DeviceNotAvailableException { 398 final ITestDevice device = mDevice; 399 for (int i = 0; i < mFilesToAdd.size(); i++) { 400 if (!mFilesToAdd.get(i).manageRemoteFile) { 401 continue; 402 } 403 final File remoteFile = mFilesToAdd.get(i).remoteFile; 404 final String remoteName = deriveRemoteName(remoteFile.getName(), i); 405 final String remotePath = "/data/local/tmp/" + remoteName; 406 device.deleteFile(remotePath); 407 } 408 } 409 } 410