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