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 DeviceFile(File localFile, File remoteFile, boolean addToInstallSession)53 private DeviceFile(File localFile, File remoteFile, boolean addToInstallSession) { 54 this.localFile = localFile; 55 this.remoteFile = remoteFile; 56 this.addToInstallSession = addToInstallSession; 57 } 58 addToSession(File file)59 static DeviceFile addToSession(File file) { 60 return new DeviceFile(file, file, true); 61 } 62 renameAndAddToSession(File localFile, File remoteFile)63 static DeviceFile renameAndAddToSession(File localFile, File remoteFile) { 64 return new DeviceFile(localFile, remoteFile, true); 65 } 66 pushOnly(File file)67 static DeviceFile pushOnly(File file) { 68 return new DeviceFile(file, file, false); 69 } 70 renameAndPushOnly(File localFile, File remoteFile)71 static DeviceFile renameAndPushOnly(File localFile, File remoteFile) { 72 return new DeviceFile(localFile, remoteFile, false); 73 } 74 } 75 76 private final List<String> mArgs = new ArrayList<>(); 77 private final List<DeviceFile> mFilesToAdd = new ArrayList<>(); 78 private final List<String> mSplitsToRemove = new ArrayList<>(); 79 private boolean mUseNaturalAbi = false; 80 private boolean mUseIncremental = false; 81 BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi)82 public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi) { 83 this(device, buildInfo, abi, true); 84 } 85 BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi, boolean grantPermissions)86 public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi, 87 boolean grantPermissions) { 88 mDevice = device; 89 mBuild = buildInfo; 90 mAbi = abi; 91 if (grantPermissions) { 92 addArg("-g"); 93 } 94 } 95 addArg(String arg)96 T addArg(String arg) { 97 mArgs.add(arg); 98 return (T) this; 99 } 100 addFile(String file)101 T addFile(String file) throws FileNotFoundException { 102 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); 103 mFilesToAdd.add(DeviceFile.addToSession(buildHelper.getTestFile(file, mAbi))); 104 return (T) this; 105 } 106 renameAndAddFile(String localFile, String remoteFile)107 T renameAndAddFile(String localFile, String remoteFile) throws FileNotFoundException { 108 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); 109 mFilesToAdd.add(DeviceFile.renameAndAddToSession(buildHelper.getTestFile(localFile, mAbi), 110 buildHelper.getTestFile(remoteFile, mAbi))); 111 return (T) this; 112 } 113 pushFile(String file)114 T pushFile(String file) throws FileNotFoundException { 115 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); 116 mFilesToAdd.add(DeviceFile.pushOnly(buildHelper.getTestFile(file, mAbi))); 117 return (T) this; 118 } 119 renameAndPushFile(String localFile, String remoteFile)120 T renameAndPushFile(String localFile, String remoteFile) throws FileNotFoundException { 121 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); 122 mFilesToAdd.add(DeviceFile.renameAndPushOnly(buildHelper.getTestFile(localFile, mAbi), 123 buildHelper.getTestFile(remoteFile, mAbi))); 124 return (T) this; 125 } 126 removeSplit(String split)127 T removeSplit(String split) { 128 mSplitsToRemove.add(split); 129 return (T) this; 130 } 131 inheritFrom(String packageName)132 T inheritFrom(String packageName) { 133 addArg("-r"); 134 addArg("-p " + packageName); 135 return (T) this; 136 } 137 useNaturalAbi()138 T useNaturalAbi() { 139 mUseNaturalAbi = true; 140 return (T) this; 141 } 142 useIncremental()143 T useIncremental() { 144 mUseIncremental = true; 145 return (T) this; 146 } 147 allowTest()148 T allowTest() { 149 addArg("-t"); 150 return (T) this; 151 } 152 locationAuto()153 T locationAuto() { 154 addArg("--install-location 0"); 155 return (T) this; 156 } 157 locationInternalOnly()158 T locationInternalOnly() { 159 addArg("--install-location 1"); 160 return (T) this; 161 } 162 locationPreferExternal()163 T locationPreferExternal() { 164 addArg("--install-location 2"); 165 return (T) this; 166 } 167 forceUuid(String uuid)168 T forceUuid(String uuid) { 169 addArg("--force-uuid " + uuid); 170 return (T) this; 171 } 172 forUser(int userId)173 T forUser(int userId) { 174 addArg("--user " + userId); 175 return (T) this; 176 } 177 restrictPermissions()178 T restrictPermissions() { 179 addArg("--restrict-permissions"); 180 return (T) this; 181 } 182 deriveRemoteName(String originalName, int index)183 protected String deriveRemoteName(String originalName, int index) { 184 return index + "_" + originalName; 185 } 186 run()187 void run() throws DeviceNotAvailableException { 188 run(true, null); 189 } 190 run(boolean expectingSuccess)191 void run(boolean expectingSuccess) throws DeviceNotAvailableException { 192 run(expectingSuccess, null); 193 } 194 runExpectingFailure()195 void runExpectingFailure() throws DeviceNotAvailableException { 196 run(false, null); 197 } 198 runExpectingFailure(String failure)199 void runExpectingFailure(String failure) throws DeviceNotAvailableException { 200 run(false, failure); 201 } 202 run(boolean expectingSuccess, String failure)203 private void run(boolean expectingSuccess, String failure) throws DeviceNotAvailableException { 204 if (mUseIncremental) { 205 runIncremental(expectingSuccess, failure); 206 } else { 207 runNonIncremental(expectingSuccess, failure); 208 } 209 cleanupDeviceFiles(); 210 } 211 runNonIncremental(boolean expectingSuccess, String failure)212 private void runNonIncremental(boolean expectingSuccess, String failure) 213 throws DeviceNotAvailableException { 214 final ITestDevice device = mDevice; 215 216 // Create an install session 217 final StringBuilder cmd = new StringBuilder(); 218 cmd.append("pm install-create"); 219 for (String arg : mArgs) { 220 cmd.append(' ').append(arg); 221 } 222 if (!mUseNaturalAbi && mAbi != null) { 223 cmd.append(' ').append(AbiUtils.createAbiFlag(mAbi.getName())); 224 } 225 226 String result = device.executeShellCommand(cmd.toString()); 227 TestCase.assertTrue(result, result.startsWith("Success")); 228 229 final int start = result.lastIndexOf("["); 230 final int end = result.lastIndexOf("]"); 231 int sessionId = -1; 232 try { 233 if (start != -1 && end != -1 && start < end) { 234 sessionId = Integer.parseInt(result.substring(start + 1, end)); 235 } 236 } catch (NumberFormatException e) { 237 } 238 if (sessionId == -1) { 239 throw new IllegalStateException("Failed to create install session: " + result); 240 } 241 242 // Push our files into session. Ideally we'd use stdin streaming, 243 // but ddmlib doesn't support it yet. 244 for (int i = 0; i < mFilesToAdd.size(); i++) { 245 final File localFile = mFilesToAdd.get(i).localFile; 246 final File remoteFile = mFilesToAdd.get(i).remoteFile; 247 final String remoteName = deriveRemoteName(remoteFile.getName(), i); 248 final String remotePath = "/data/local/tmp/" + remoteName; 249 if (!device.pushFile(localFile, remotePath)) { 250 throw new IllegalStateException("Failed to push " + localFile); 251 } 252 253 if (!mFilesToAdd.get(i).addToInstallSession) { 254 continue; 255 } 256 257 cmd.setLength(0); 258 cmd.append("pm install-write"); 259 cmd.append(' ').append(sessionId); 260 cmd.append(' ').append(remoteName); 261 cmd.append(' ').append(remotePath); 262 263 result = device.executeShellCommand(cmd.toString()); 264 TestCase.assertTrue(result, result.startsWith("Success")); 265 } 266 267 for (int i = 0; i < mSplitsToRemove.size(); i++) { 268 final String split = mSplitsToRemove.get(i); 269 270 cmd.setLength(0); 271 cmd.append("pm install-remove"); 272 cmd.append(' ').append(sessionId); 273 cmd.append(' ').append(split); 274 275 result = device.executeShellCommand(cmd.toString()); 276 TestCase.assertTrue(result, result.startsWith("Success")); 277 } 278 279 // Everything staged; let's pull trigger 280 cmd.setLength(0); 281 cmd.append("pm install-commit"); 282 cmd.append(' ').append(sessionId); 283 284 result = device.executeShellCommand(cmd.toString()).trim(); 285 if (failure == null) { 286 if (expectingSuccess) { 287 TestCase.assertTrue(result, result.startsWith("Success")); 288 } else { 289 TestCase.assertFalse(result, result.startsWith("Success")); 290 } 291 } else { 292 TestCase.assertTrue(result, result.contains(failure)); 293 } 294 } 295 runIncremental(boolean expectingSuccess, String failure)296 private void runIncremental(boolean expectingSuccess, String failure) throws DeviceNotAvailableException { 297 final ITestDevice device = mDevice; 298 299 if (!mSplitsToRemove.isEmpty()) { 300 throw new IllegalStateException("Incremental sessions can't remove splits"); 301 } 302 303 // Create an install session 304 final StringBuilder cmd = new StringBuilder(); 305 cmd.append("pm install-incremental"); 306 for (String arg : mArgs) { 307 cmd.append(' ').append(arg); 308 } 309 if (!mUseNaturalAbi && mAbi != null) { 310 cmd.append(' ').append(AbiUtils.createAbiFlag(mAbi.getName())); 311 } 312 313 // Push our files into session. Ideally we'd use stdin streaming, 314 // but ddmlib doesn't support it yet. 315 for (int i = 0; i < mFilesToAdd.size(); i++) { 316 final File localFile = mFilesToAdd.get(i).localFile; 317 final File remoteFile = mFilesToAdd.get(i).remoteFile; 318 final String remoteName = deriveRemoteName(remoteFile.getName(), i); 319 final String remotePath = "/data/local/tmp/" + remoteName; 320 if (!device.pushFile(localFile, remotePath)) { 321 throw new IllegalStateException("Failed to push " + localFile); 322 } 323 324 if (!mFilesToAdd.get(i).addToInstallSession) { 325 continue; 326 } 327 328 cmd.append(' ').append(remotePath); 329 } 330 331 // Everything staged; let's pull trigger 332 String result = device.executeShellCommand(cmd.toString()).trim(); 333 if (failure == null) { 334 if (expectingSuccess) { 335 TestCase.assertTrue(result, result.startsWith("Success")); 336 } else { 337 TestCase.assertFalse(result, result.startsWith("Success")); 338 } 339 } else { 340 TestCase.assertTrue(result, result.contains(failure)); 341 } 342 } 343 cleanupDeviceFiles()344 private void cleanupDeviceFiles() throws DeviceNotAvailableException { 345 final ITestDevice device = mDevice; 346 for (int i = 0; i < mFilesToAdd.size(); i++) { 347 final File remoteFile = mFilesToAdd.get(i).remoteFile; 348 final String remoteName = deriveRemoteName(remoteFile.getName(), i); 349 final String remotePath = "/data/local/tmp/" + remoteName; 350 device.deleteFile(remotePath); 351 } 352 } 353 } 354