1 /* 2 * Copyright (C) 2019 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 17 package com.android.apkverity; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assert.fail; 24 25 import android.platform.test.annotations.RootPermissionTest; 26 27 import com.android.blockdevicewriter.BlockDeviceWriter; 28 import com.android.tradefed.device.DeviceNotAvailableException; 29 import com.android.tradefed.device.ITestDevice; 30 import com.android.tradefed.log.LogUtil.CLog; 31 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 32 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 33 import com.android.tradefed.util.CommandResult; 34 import com.android.tradefed.util.CommandStatus; 35 36 import org.junit.After; 37 import org.junit.Before; 38 import org.junit.Test; 39 import org.junit.runner.RunWith; 40 41 import java.io.FileNotFoundException; 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.HashSet; 45 import java.util.Set; 46 47 /** 48 * This test makes sure app installs with fs-verity signature, and on-access verification works. 49 * 50 * <p>When an app is installed, all or none of the files should have their corresponding .fsv_sig 51 * signature file. Otherwise, install will fail. 52 * 53 * <p>Once installed, file protected by fs-verity is verified by kernel every time a block is loaded 54 * from disk to memory. The file is immutable by design, enforced by filesystem. 55 * 56 * <p>In order to make sure a block of the file is readable only if the underlying block on disk 57 * stay intact, the test needs to bypass the filesystem and tampers with the corresponding physical 58 * address against the block device. 59 * 60 * <p>Requirements to run this test: 61 * <ul> 62 * <li>Device is rootable</li> 63 * <li>The filesystem supports fs-verity</li> 64 * <li>The feature flag is enabled</li> 65 * </ul> 66 */ 67 @RootPermissionTest 68 @RunWith(DeviceJUnit4ClassRunner.class) 69 public class ApkVerityTest extends BaseHostJUnit4Test { 70 private static final String TARGET_PACKAGE = "com.android.apkverity"; 71 72 private static final String BASE_APK = "ApkVerityTestApp.apk"; 73 private static final String BASE_APK_DM = "ApkVerityTestApp.dm"; 74 private static final String SPLIT_APK = "ApkVerityTestAppSplit.apk"; 75 private static final String SPLIT_APK_DM = "ApkVerityTestAppSplit.dm"; 76 77 private static final String INSTALLED_BASE_APK = "base.apk"; 78 private static final String INSTALLED_BASE_DM = "base.dm"; 79 private static final String INSTALLED_SPLIT_APK = "split_feature_x.apk"; 80 private static final String INSTALLED_SPLIT_DM = "split_feature_x.dm"; 81 private static final String INSTALLED_BASE_APK_FSV_SIG = "base.apk.fsv_sig"; 82 private static final String INSTALLED_BASE_DM_FSV_SIG = "base.dm.fsv_sig"; 83 private static final String INSTALLED_SPLIT_APK_FSV_SIG = "split_feature_x.apk.fsv_sig"; 84 private static final String INSTALLED_SPLIT_DM_FSV_SIG = "split_feature_x.dm.fsv_sig"; 85 86 private static final String DAMAGING_EXECUTABLE = "/data/local/tmp/block_device_writer"; 87 private static final String CERT_PATH = "/data/local/tmp/ApkVerityTestCert.der"; 88 89 /** Only 4K page is supported by fs-verity currently. */ 90 private static final int FSVERITY_PAGE_SIZE = 4096; 91 92 private ITestDevice mDevice; 93 private boolean mDmRequireFsVerity; 94 95 @Before setUp()96 public void setUp() throws DeviceNotAvailableException { 97 mDevice = getDevice(); 98 mDmRequireFsVerity = "true".equals( 99 mDevice.getProperty("pm.dexopt.dm.require_fsverity")); 100 101 expectRemoteCommandToSucceed("cmd file_integrity append-cert " + CERT_PATH); 102 uninstallPackage(TARGET_PACKAGE); 103 } 104 105 @After tearDown()106 public void tearDown() throws DeviceNotAvailableException { 107 expectRemoteCommandToSucceed("cmd file_integrity remove-last-cert"); 108 uninstallPackage(TARGET_PACKAGE); 109 } 110 111 @Test testFsverityKernelSupports()112 public void testFsverityKernelSupports() throws DeviceNotAvailableException { 113 ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data"); 114 expectRemoteCommandToSucceed("test -f /sys/fs/" + mountPoint.type + "/features/verity"); 115 } 116 117 @Test testInstallBase()118 public void testInstallBase() throws DeviceNotAvailableException, FileNotFoundException { 119 new InstallMultiple() 120 .addFileAndSignature(BASE_APK) 121 .run(); 122 assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); 123 124 verifyInstalledFiles( 125 INSTALLED_BASE_APK, 126 INSTALLED_BASE_APK_FSV_SIG); 127 verifyInstalledFilesHaveFsverity(INSTALLED_BASE_APK); 128 } 129 130 @Test testInstallBaseWithWrongSignature()131 public void testInstallBaseWithWrongSignature() 132 throws DeviceNotAvailableException, FileNotFoundException { 133 new InstallMultiple() 134 .addFile(BASE_APK) 135 .addFile(SPLIT_APK_DM + ".fsv_sig", 136 BASE_APK + ".fsv_sig") 137 .runExpectingFailure(); 138 } 139 140 @Test testInstallBaseWithSplit()141 public void testInstallBaseWithSplit() 142 throws DeviceNotAvailableException, FileNotFoundException { 143 new InstallMultiple() 144 .addFileAndSignature(BASE_APK) 145 .addFileAndSignature(SPLIT_APK) 146 .run(); 147 assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); 148 149 verifyInstalledFiles( 150 INSTALLED_BASE_APK, 151 INSTALLED_BASE_APK_FSV_SIG, 152 INSTALLED_SPLIT_APK, 153 INSTALLED_SPLIT_APK_FSV_SIG); 154 verifyInstalledFilesHaveFsverity( 155 INSTALLED_BASE_APK, 156 INSTALLED_SPLIT_APK); 157 } 158 159 @Test testInstallBaseWithDm()160 public void testInstallBaseWithDm() throws DeviceNotAvailableException, FileNotFoundException { 161 new InstallMultiple() 162 .addFileAndSignature(BASE_APK) 163 .addFileAndSignature(BASE_APK_DM) 164 .run(); 165 assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); 166 167 verifyInstalledFiles( 168 INSTALLED_BASE_APK, 169 INSTALLED_BASE_APK_FSV_SIG, 170 INSTALLED_BASE_DM, 171 INSTALLED_BASE_DM_FSV_SIG); 172 verifyInstalledFilesHaveFsverity( 173 INSTALLED_BASE_APK, 174 INSTALLED_BASE_DM); 175 } 176 177 @Test testInstallEverything()178 public void testInstallEverything() throws DeviceNotAvailableException, FileNotFoundException { 179 new InstallMultiple() 180 .addFileAndSignature(BASE_APK) 181 .addFileAndSignature(BASE_APK_DM) 182 .addFileAndSignature(SPLIT_APK) 183 .addFileAndSignature(SPLIT_APK_DM) 184 .run(); 185 assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); 186 187 verifyInstalledFiles( 188 INSTALLED_BASE_APK, 189 INSTALLED_BASE_APK_FSV_SIG, 190 INSTALLED_BASE_DM, 191 INSTALLED_BASE_DM_FSV_SIG, 192 INSTALLED_SPLIT_APK, 193 INSTALLED_SPLIT_APK_FSV_SIG, 194 INSTALLED_SPLIT_DM, 195 INSTALLED_SPLIT_DM_FSV_SIG); 196 verifyInstalledFilesHaveFsverity( 197 INSTALLED_BASE_APK, 198 INSTALLED_BASE_DM, 199 INSTALLED_SPLIT_APK, 200 INSTALLED_SPLIT_DM); 201 } 202 203 @Test testInstallSplitOnly()204 public void testInstallSplitOnly() 205 throws DeviceNotAvailableException, FileNotFoundException { 206 new InstallMultiple() 207 .addFileAndSignature(BASE_APK) 208 .run(); 209 assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); 210 verifyInstalledFiles( 211 INSTALLED_BASE_APK, 212 INSTALLED_BASE_APK_FSV_SIG); 213 214 new InstallMultiple() 215 .inheritFrom(TARGET_PACKAGE) 216 .addFileAndSignature(SPLIT_APK) 217 .run(); 218 219 verifyInstalledFiles( 220 INSTALLED_BASE_APK, 221 INSTALLED_BASE_APK_FSV_SIG, 222 INSTALLED_SPLIT_APK, 223 INSTALLED_SPLIT_APK_FSV_SIG); 224 verifyInstalledFilesHaveFsverity( 225 INSTALLED_BASE_APK, 226 INSTALLED_SPLIT_APK); 227 } 228 229 @Test testInstallSplitOnlyMissingSignature()230 public void testInstallSplitOnlyMissingSignature() 231 throws DeviceNotAvailableException, FileNotFoundException { 232 new InstallMultiple() 233 .addFileAndSignature(BASE_APK) 234 .run(); 235 assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); 236 verifyInstalledFiles( 237 INSTALLED_BASE_APK, 238 INSTALLED_BASE_APK_FSV_SIG); 239 240 new InstallMultiple() 241 .inheritFrom(TARGET_PACKAGE) 242 .addFile(SPLIT_APK) 243 .runExpectingFailure(); 244 } 245 246 @Test testInstallSplitOnlyWithoutBaseSignature()247 public void testInstallSplitOnlyWithoutBaseSignature() 248 throws DeviceNotAvailableException, FileNotFoundException { 249 new InstallMultiple() 250 .addFile(BASE_APK) 251 .run(); 252 assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); 253 verifyInstalledFiles(INSTALLED_BASE_APK); 254 255 new InstallMultiple() 256 .inheritFrom(TARGET_PACKAGE) 257 .addFileAndSignature(SPLIT_APK) 258 .run(); 259 verifyInstalledFiles( 260 INSTALLED_BASE_APK, 261 INSTALLED_SPLIT_APK, 262 INSTALLED_SPLIT_APK_FSV_SIG); 263 } 264 265 @Test testInstallOnlyDmHasFsvSig()266 public void testInstallOnlyDmHasFsvSig() 267 throws DeviceNotAvailableException, FileNotFoundException { 268 new InstallMultiple() 269 .addFile(BASE_APK) 270 .addFileAndSignature(BASE_APK_DM) 271 .addFile(SPLIT_APK) 272 .addFileAndSignature(SPLIT_APK_DM) 273 .run(); 274 verifyInstalledFiles( 275 INSTALLED_BASE_APK, 276 INSTALLED_BASE_DM, 277 INSTALLED_BASE_DM_FSV_SIG, 278 INSTALLED_SPLIT_APK, 279 INSTALLED_SPLIT_DM, 280 INSTALLED_SPLIT_DM_FSV_SIG); 281 verifyInstalledFilesHaveFsverity( 282 INSTALLED_BASE_DM, 283 INSTALLED_SPLIT_DM); 284 } 285 286 @Test testInstallDmWithoutFsvSig_Base()287 public void testInstallDmWithoutFsvSig_Base() 288 throws DeviceNotAvailableException, FileNotFoundException { 289 InstallMultiple installer = new InstallMultiple() 290 .addFile(BASE_APK) 291 .addFile(BASE_APK_DM) 292 .addFile(SPLIT_APK) 293 .addFileAndSignature(SPLIT_APK_DM); 294 if (mDmRequireFsVerity) { 295 installer.runExpectingFailure(); 296 } else { 297 installer.run(); 298 verifyInstalledFiles( 299 INSTALLED_BASE_APK, 300 INSTALLED_BASE_DM, 301 INSTALLED_SPLIT_APK, 302 INSTALLED_SPLIT_DM, 303 INSTALLED_SPLIT_DM_FSV_SIG); 304 verifyInstalledFilesHaveFsverity(INSTALLED_SPLIT_DM); 305 } 306 } 307 308 @Test testInstallDmWithoutFsvSig_Split()309 public void testInstallDmWithoutFsvSig_Split() 310 throws DeviceNotAvailableException, FileNotFoundException { 311 InstallMultiple installer = new InstallMultiple() 312 .addFile(BASE_APK) 313 .addFileAndSignature(BASE_APK_DM) 314 .addFile(SPLIT_APK) 315 .addFile(SPLIT_APK_DM); 316 if (mDmRequireFsVerity) { 317 installer.runExpectingFailure(); 318 } else { 319 installer.run(); 320 verifyInstalledFiles( 321 INSTALLED_BASE_APK, 322 INSTALLED_BASE_DM, 323 INSTALLED_BASE_DM_FSV_SIG, 324 INSTALLED_SPLIT_APK, 325 INSTALLED_SPLIT_DM); 326 verifyInstalledFilesHaveFsverity(INSTALLED_BASE_DM); 327 } 328 } 329 330 @Test testInstallSomeApkIsMissingFsvSig_Base()331 public void testInstallSomeApkIsMissingFsvSig_Base() 332 throws DeviceNotAvailableException, FileNotFoundException { 333 new InstallMultiple() 334 .addFileAndSignature(BASE_APK) 335 .addFileAndSignature(BASE_APK_DM) 336 .addFile(SPLIT_APK) 337 .addFileAndSignature(SPLIT_APK_DM) 338 .runExpectingFailure(); 339 } 340 341 @Test testInstallSomeApkIsMissingFsvSig_Split()342 public void testInstallSomeApkIsMissingFsvSig_Split() 343 throws DeviceNotAvailableException, FileNotFoundException { 344 new InstallMultiple() 345 .addFile(BASE_APK) 346 .addFileAndSignature(BASE_APK_DM) 347 .addFileAndSignature(SPLIT_APK) 348 .addFileAndSignature(SPLIT_APK_DM) 349 .runExpectingFailure(); 350 } 351 352 @Test testInstallBaseWithFsvSigThenSplitWithout()353 public void testInstallBaseWithFsvSigThenSplitWithout() 354 throws DeviceNotAvailableException, FileNotFoundException { 355 new InstallMultiple() 356 .addFileAndSignature(BASE_APK) 357 .run(); 358 assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); 359 verifyInstalledFiles( 360 INSTALLED_BASE_APK, 361 INSTALLED_BASE_APK_FSV_SIG); 362 363 new InstallMultiple() 364 .addFile(SPLIT_APK) 365 .runExpectingFailure(); 366 } 367 368 @Test testInstallBaseWithoutFsvSigThenSplitWith()369 public void testInstallBaseWithoutFsvSigThenSplitWith() 370 throws DeviceNotAvailableException, FileNotFoundException { 371 new InstallMultiple() 372 .addFile(BASE_APK) 373 .run(); 374 assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); 375 verifyInstalledFiles(INSTALLED_BASE_APK); 376 377 new InstallMultiple() 378 .addFileAndSignature(SPLIT_APK) 379 .runExpectingFailure(); 380 } 381 382 @Test testFsverityFileIsImmutableAndReadable()383 public void testFsverityFileIsImmutableAndReadable() throws DeviceNotAvailableException { 384 new InstallMultiple().addFileAndSignature(BASE_APK).run(); 385 String apkPath = getApkPath(TARGET_PACKAGE); 386 387 assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); 388 expectRemoteCommandToFail("echo -n '' >> " + apkPath); 389 expectRemoteCommandToSucceed("cat " + apkPath + " > /dev/null"); 390 } 391 392 @Test testFsverityFailToReadModifiedBlockAtFront()393 public void testFsverityFailToReadModifiedBlockAtFront() throws DeviceNotAvailableException { 394 new InstallMultiple().addFileAndSignature(BASE_APK).run(); 395 String apkPath = getApkPath(TARGET_PACKAGE); 396 397 long apkSize = getFileSizeInBytes(apkPath); 398 long offsetFirstByte = 0; 399 400 // The first two pages should be both readable at first. 401 assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetFirstByte)); 402 if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) { 403 assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, 404 offsetFirstByte + FSVERITY_PAGE_SIZE)); 405 } 406 407 // Damage the file directly against the block device. 408 damageFileAgainstBlockDevice(apkPath, offsetFirstByte); 409 410 // Expect actual read from disk to fail but only at damaged page. 411 BlockDeviceWriter.dropCaches(mDevice); 412 assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetFirstByte)); 413 if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) { 414 long lastByteOfTheSamePage = 415 offsetFirstByte % FSVERITY_PAGE_SIZE + FSVERITY_PAGE_SIZE - 1; 416 assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, lastByteOfTheSamePage)); 417 assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, lastByteOfTheSamePage + 1)); 418 } 419 } 420 421 @Test testFsverityFailToReadModifiedBlockAtBack()422 public void testFsverityFailToReadModifiedBlockAtBack() throws DeviceNotAvailableException { 423 new InstallMultiple().addFileAndSignature(BASE_APK).run(); 424 String apkPath = getApkPath(TARGET_PACKAGE); 425 426 long apkSize = getFileSizeInBytes(apkPath); 427 long offsetOfLastByte = apkSize - 1; 428 429 // The first two pages should be both readable at first. 430 assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetOfLastByte)); 431 if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) { 432 assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, 433 offsetOfLastByte - FSVERITY_PAGE_SIZE)); 434 } 435 436 // Damage the file directly against the block device. 437 damageFileAgainstBlockDevice(apkPath, offsetOfLastByte); 438 439 // Expect actual read from disk to fail but only at damaged page. 440 BlockDeviceWriter.dropCaches(mDevice); 441 assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetOfLastByte)); 442 if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) { 443 long firstByteOfTheSamePage = offsetOfLastByte - offsetOfLastByte % FSVERITY_PAGE_SIZE; 444 assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, firstByteOfTheSamePage)); 445 assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, firstByteOfTheSamePage - 1)); 446 } 447 } 448 verifyInstalledFilesHaveFsverity(String... filenames)449 private void verifyInstalledFilesHaveFsverity(String... filenames) 450 throws DeviceNotAvailableException { 451 // Verify that all files are protected by fs-verity 452 String apkPath = getApkPath(TARGET_PACKAGE); 453 String appDir = apkPath.substring(0, apkPath.lastIndexOf("/")); 454 long kTargetOffset = 0; 455 for (String basename : filenames) { 456 String path = appDir + "/" + basename; 457 damageFileAgainstBlockDevice(path, kTargetOffset); 458 459 // Retry is sometimes needed to pass the test. Package manager may have FD leaks 460 // (see b/122744005 as example) that prevents the file in question to be evicted 461 // from filesystem cache. Forcing GC workarounds the problem. 462 int retry = 5; 463 for (; retry > 0; retry--) { 464 BlockDeviceWriter.dropCaches(mDevice); 465 if (!BlockDeviceWriter.canReadByte(mDevice, path, kTargetOffset)) { 466 break; 467 } 468 try { 469 String openFiles = expectRemoteCommandToSucceed("lsof " + apkPath); 470 CLog.d("lsof: " + openFiles); 471 Thread.sleep(1000); 472 forceGCOnOpenFilesProcess(getOpenFilesPIDs(openFiles)); 473 } catch (InterruptedException e) { 474 Thread.currentThread().interrupt(); 475 return; 476 } 477 } 478 assertTrue("Read from " + path + " should fail", retry > 0); 479 } 480 } 481 482 /** 483 * This is a helper method that parses the lsof output to get PIDs of process holding FD. 484 * Here is an example output of lsof. This method extracts the second columns(PID). 485 * 486 * Example lsof output: 487 * COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME 488 * .example.app 1063 u0_a38 mem REG 253,6 8599 12826 example.apk 489 * .example.app 1063 u0_a38 99r REG 253,6 8599 12826 example.apk 490 */ getOpenFilesPIDs(String lsof)491 private Set<String> getOpenFilesPIDs(String lsof) { 492 Set<String> openFilesPIDs = new HashSet<>(); 493 String[] lines = lsof.split("\n"); 494 for (int i = 1; i < lines.length; i++) { 495 openFilesPIDs.add(lines[i].split("\\s+")[1]); 496 } 497 return openFilesPIDs; 498 } 499 500 /** 501 * This is a helper method that forces GC on processes given their PIDs. 502 * That is to execute shell command "kill -10" on PIDs. 503 */ forceGCOnOpenFilesProcess(Set<String> openFilesPIDs)504 private void forceGCOnOpenFilesProcess(Set<String> openFilesPIDs) 505 throws DeviceNotAvailableException { 506 for (String openFilePID : openFilesPIDs) { 507 mDevice.executeShellV2Command("kill -10 " + openFilePID); 508 } 509 } 510 verifyInstalledFiles(String... filenames)511 private void verifyInstalledFiles(String... filenames) throws DeviceNotAvailableException { 512 String apkPath = getApkPath(TARGET_PACKAGE); 513 String appDir = apkPath.substring(0, apkPath.lastIndexOf("/")); 514 // Exclude directories since we only care about files. 515 HashSet<String> actualFiles = new HashSet<>(Arrays.asList( 516 expectRemoteCommandToSucceed("ls -p " + appDir + " | grep -v '/'").split("\n"))); 517 518 HashSet<String> expectedFiles = new HashSet<>(Arrays.asList(filenames)); 519 assertEquals(expectedFiles, actualFiles); 520 } 521 damageFileAgainstBlockDevice(String path, long offsetOfTargetingByte)522 private void damageFileAgainstBlockDevice(String path, long offsetOfTargetingByte) 523 throws DeviceNotAvailableException { 524 assertTrue(path.startsWith("/data/")); 525 ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data"); 526 ArrayList<String> args = new ArrayList<>(); 527 args.add(DAMAGING_EXECUTABLE); 528 if ("f2fs".equals(mountPoint.type)) { 529 args.add("--use-f2fs-pinning"); 530 } 531 args.add(mountPoint.filesystem); 532 args.add(path); 533 args.add(Long.toString(offsetOfTargetingByte)); 534 expectRemoteCommandToSucceed(String.join(" ", args)); 535 } 536 getApkPath(String packageName)537 private String getApkPath(String packageName) throws DeviceNotAvailableException { 538 String line = expectRemoteCommandToSucceed("pm path " + packageName + " | grep base.apk"); 539 int index = line.trim().indexOf(":"); 540 assertTrue(index >= 0); 541 return line.substring(index + 1); 542 } 543 getFileSizeInBytes(String packageName)544 private long getFileSizeInBytes(String packageName) throws DeviceNotAvailableException { 545 return Long.parseLong(expectRemoteCommandToSucceed("stat -c '%s' " + packageName).trim()); 546 } 547 expectRemoteCommandToSucceed(String cmd)548 private String expectRemoteCommandToSucceed(String cmd) throws DeviceNotAvailableException { 549 CommandResult result = mDevice.executeShellV2Command(cmd); 550 assertEquals("`" + cmd + "` failed: " + result.getStderr(), CommandStatus.SUCCESS, 551 result.getStatus()); 552 return result.getStdout(); 553 } 554 expectRemoteCommandToFail(String cmd)555 private void expectRemoteCommandToFail(String cmd) throws DeviceNotAvailableException { 556 CommandResult result = mDevice.executeShellV2Command(cmd); 557 assertTrue("Unexpected success from `" + cmd + "`: " + result.getStderr(), 558 result.getStatus() != CommandStatus.SUCCESS); 559 } 560 561 private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> { InstallMultiple()562 InstallMultiple() { 563 super(getDevice(), getBuild()); 564 } 565 addFileAndSignature(String filename)566 InstallMultiple addFileAndSignature(String filename) { 567 try { 568 addFile(filename); 569 addFile(filename + ".fsv_sig"); 570 } catch (FileNotFoundException e) { 571 fail("Missing test file: " + e); 572 } 573 return this; 574 } 575 } 576 } 577