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 android.content.pm.cts; 18 19 import static android.Manifest.permission.WRITE_DEVICE_CONFIG; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertNotEquals; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assert.fail; 26 27 import android.annotation.NonNull; 28 import android.app.UiAutomation; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.pm.PackageManager; 32 import android.os.IBinder; 33 import android.os.ParcelFileDescriptor; 34 import android.os.Process; 35 import android.os.SystemClock; 36 import android.os.UserHandle; 37 import android.platform.test.annotations.AppModeFull; 38 import android.platform.test.annotations.Presubmit; 39 import android.provider.DeviceConfig; 40 import android.service.dataloader.DataLoaderService; 41 import android.system.Os; 42 import android.text.TextUtils; 43 import android.util.ArrayMap; 44 import android.util.Log; 45 46 import androidx.test.InstrumentationRegistry; 47 import androidx.test.filters.LargeTest; 48 import androidx.test.runner.AndroidJUnit4; 49 50 import com.android.compatibility.common.util.PropertyUtil; 51 import com.android.incfs.install.IBlockFilter; 52 import com.android.incfs.install.IBlockTransformer; 53 import com.android.incfs.install.IncrementalInstallSession; 54 import com.android.incfs.install.PendingBlock; 55 56 import com.google.common.truth.Truth; 57 58 import libcore.io.IoUtils; 59 60 import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorOutputStream; 61 import org.junit.After; 62 import org.junit.Assert; 63 import org.junit.Assume; 64 import org.junit.Before; 65 import org.junit.Test; 66 import org.junit.runner.RunWith; 67 68 import java.io.ByteArrayOutputStream; 69 import java.io.File; 70 import java.io.FileInputStream; 71 import java.io.FileOutputStream; 72 import java.io.IOException; 73 import java.io.InputStream; 74 import java.io.OutputStream; 75 import java.nio.ByteBuffer; 76 import java.nio.channels.Channels; 77 import java.nio.file.Paths; 78 import java.util.ArrayList; 79 import java.util.Arrays; 80 import java.util.Optional; 81 import java.util.Random; 82 import java.util.Scanner; 83 import java.util.concurrent.Callable; 84 import java.util.concurrent.CompletableFuture; 85 import java.util.concurrent.Executors; 86 import java.util.concurrent.TimeUnit; 87 import java.util.concurrent.atomic.AtomicBoolean; 88 import java.util.concurrent.atomic.AtomicLong; 89 import java.util.function.Function; 90 import java.util.stream.Collectors; 91 import java.util.stream.Stream; 92 93 @RunWith(AndroidJUnit4.class) 94 @AppModeFull 95 @LargeTest 96 @Presubmit 97 public class PackageManagerShellCommandIncrementalTest { 98 private static final String TAG = "PackageManagerShellCommandIncrementalTest"; 99 100 private static final String CTS_PACKAGE_NAME = "android.content.cts"; 101 private static final String TEST_APP_PACKAGE = "com.example.helloworld"; 102 103 private static final String TEST_APK_PATH = "/data/local/tmp/cts/content/"; 104 private static final String TEST_APK = "HelloWorld5.apk"; 105 private static final String TEST_APK_IDSIG = "HelloWorld5.apk.idsig"; 106 private static final String TEST_APK_PROFILEABLE = "HelloWorld5Profileable.apk"; 107 private static final String TEST_APK_SHELL = "HelloWorldShell.apk"; 108 private static final String TEST_APK_SPLIT0 = "HelloWorld5_mdpi-v4.apk"; 109 private static final String TEST_APK_SPLIT0_IDSIG = "HelloWorld5_mdpi-v4.apk.idsig"; 110 private static final String TEST_APK_SPLIT1 = "HelloWorld5_hdpi-v4.apk"; 111 private static final String TEST_APK_SPLIT1_IDSIG = "HelloWorld5_hdpi-v4.apk.idsig"; 112 private static final String TEST_APK_SPLIT2 = "HelloWorld5_xhdpi-v4.apk"; 113 private static final String TEST_APK_SPLIT2_IDSIG = "HelloWorld5_xhdpi-v4.apk.idsig"; 114 private static final String TEST_APK_MALFORMED = "malformed.apk"; 115 116 private static final String TEST_HW7 = "HelloWorld7.apk"; 117 private static final String TEST_HW7_IDSIG = "HelloWorld7.apk.idsig"; 118 private static final String TEST_HW7_SPLIT0 = "HelloWorld7_hdpi-v4.apk"; 119 private static final String TEST_HW7_SPLIT0_IDSIG = "HelloWorld7_hdpi-v4.apk.idsig"; 120 private static final String TEST_HW7_SPLIT1 = "HelloWorld7_mdpi-v4.apk"; 121 private static final String TEST_HW7_SPLIT1_IDSIG = "HelloWorld7_mdpi-v4.apk.idsig"; 122 private static final String TEST_HW7_SPLIT2 = "HelloWorld7_xhdpi-v4.apk"; 123 private static final String TEST_HW7_SPLIT2_IDSIG = "HelloWorld7_xhdpi-v4.apk.idsig"; 124 private static final String TEST_HW7_SPLIT3 = "HelloWorld7_xxhdpi-v4.apk"; 125 private static final String TEST_HW7_SPLIT3_IDSIG = "HelloWorld7_xxhdpi-v4.apk.idsig"; 126 private static final String TEST_HW7_SPLIT4 = "HelloWorld7_xxxhdpi-v4.apk"; 127 private static final String TEST_HW7_SPLIT4_IDSIG = "HelloWorld7_xxxhdpi-v4.apk.idsig"; 128 129 private static final boolean CHECK_BASE_APK_DIGESTION = false; 130 131 private static final long EXPECTED_READ_TIME = 1000L; 132 133 private IncrementalInstallSession mSession = null; 134 private String mPackageVerifier = null; 135 getUiAutomation()136 private static UiAutomation getUiAutomation() { 137 return InstrumentationRegistry.getInstrumentation().getUiAutomation(); 138 } 139 getContext()140 private static Context getContext() { 141 return InstrumentationRegistry.getInstrumentation().getContext(); 142 } 143 getPackageManager()144 private static PackageManager getPackageManager() { 145 return getContext().getPackageManager(); 146 } 147 148 @Before onBefore()149 public void onBefore() throws Exception { 150 checkIncrementalDeliveryFeature(); 151 cleanup(); 152 153 // Disable the package verifier to avoid the dialog when installing an app. 154 mPackageVerifier = executeShellCommand("settings get global verifier_verify_adb_installs"); 155 executeShellCommand("settings put global verifier_verify_adb_installs 0"); 156 } 157 158 @After onAfter()159 public void onAfter() throws Exception { 160 cleanup(); 161 162 // Reset the package verifier setting to its original value. 163 executeShellCommand("settings put global verifier_verify_adb_installs " + mPackageVerifier); 164 } 165 checkIncrementalDeliveryFeature()166 static void checkIncrementalDeliveryFeature() { 167 Assume.assumeTrue(getPackageManager().hasSystemFeature( 168 PackageManager.FEATURE_INCREMENTAL_DELIVERY)); 169 } 170 checkIncrementalDeliveryV2Feature()171 private static void checkIncrementalDeliveryV2Feature() throws Exception { 172 checkIncrementalDeliveryFeature(); 173 Assume.assumeTrue(getPackageManager().hasSystemFeature( 174 PackageManager.FEATURE_INCREMENTAL_DELIVERY, 2)); 175 } 176 177 @Test testAndroid12RequiresIncFsV2()178 public void testAndroid12RequiresIncFsV2() throws Exception { 179 // IncFS is a kernel feature, which is a subject to vendor freeze. That's why 180 // the test verifies the vendor API level here, not the system's one. 181 // Note: vendor API level getter returns either the frozen API level, or the current one for 182 // non-vendor-freeze devices; need to verify both the system first API level and vendor 183 // level to make the final decision. 184 final boolean v2ReqdForSystem = PropertyUtil.getFirstApiLevel() > 30; 185 final boolean v2ReqdForVendor = PropertyUtil.isVendorApiLevelNewerThan(30); 186 final boolean v2Required = v2ReqdForSystem && v2ReqdForVendor; 187 if (v2Required) { 188 Assert.assertTrue("Devices launched at API 31+ with a vendor partition of API 31+ need " 189 + "to support Incremental Delivery version 2 or higher", 190 getPackageManager().hasSystemFeature( 191 PackageManager.FEATURE_INCREMENTAL_DELIVERY, 2)); 192 } 193 } 194 195 @Test testInstallWithIdSig()196 public void testInstallWithIdSig() throws Exception { 197 installPackage(TEST_APK); 198 assertTrue(isAppInstalled(TEST_APP_PACKAGE)); 199 } 200 201 @Test testBug183952694Fixed()202 public void testBug183952694Fixed() throws Exception { 203 // first ensure the IncFS is up and running, e.g. if it's a module 204 installPackage(TEST_APK); 205 assertTrue(isAppInstalled(TEST_APP_PACKAGE)); 206 207 // the bug is fixed in the v2 version, or when the specific marker feature is present 208 final String[] validValues = {"v2", "mounter_context_for_backing_rw"}; 209 final String features = executeShellCommand("ls /sys/fs/incremental-fs/features/"); 210 assertTrue( 211 "Missing all of required IncFS features [" + TextUtils.join(",", validValues) + "]", 212 Arrays.stream(features.split("\\s+")).anyMatch( 213 f -> Arrays.stream(validValues).anyMatch(f::equals))); 214 } 215 216 @LargeTest 217 @Test testSpaceAllocatedForPackage()218 public void testSpaceAllocatedForPackage() throws Exception { 219 final String apk = createApkPath(TEST_APK); 220 final String idsig = createApkPath(TEST_APK_IDSIG); 221 final long appFileSize = new File(apk).length(); 222 final AtomicBoolean firstTime = new AtomicBoolean(true); 223 224 getUiAutomation().adoptShellPermissionIdentity(); 225 226 final long blockSize = Os.statvfs("/data/incremental").f_bsize; 227 final long preAllocatedBlocks = Os.statvfs("/data/incremental").f_bfree; 228 229 final AtomicLong freeSpaceDifference = new AtomicLong(-1L); 230 231 mSession = 232 new IncrementalInstallSession.Builder() 233 .addApk(Paths.get(apk), Paths.get(idsig)) 234 .addExtraArgs("-t", "-i", CTS_PACKAGE_NAME) 235 .setLogger(new IncrementalDeviceConnection.Logger()) 236 .setBlockFilter((block -> { 237 // Skip allocation check after first iteration. 238 if (!firstTime.getAndSet(false)) { 239 return true; 240 } 241 242 try { 243 final long postAllocatedBlocks = 244 Os.statvfs("/data/incremental").f_bfree; 245 freeSpaceDifference.set( 246 (preAllocatedBlocks - postAllocatedBlocks) * blockSize); 247 } catch (Exception e) { 248 Log.i(TAG, "ErrnoException: ", e); 249 throw new AssertionError(e); 250 } 251 return true; 252 })) 253 .setBlockTransformer(new CompressingBlockTransformer()) 254 .build(); 255 256 try { 257 mSession.start(Executors.newSingleThreadExecutor(), 258 IncrementalDeviceConnection.Factory.reliable()); 259 mSession.waitForInstallCompleted(30, TimeUnit.SECONDS); 260 } finally { 261 getUiAutomation().dropShellPermissionIdentity(); 262 } 263 264 assertTrue(isAppInstalled(TEST_APP_PACKAGE)); 265 266 final double freeSpaceExpectedDifference = ((appFileSize * 1.015) + blockSize * 8); 267 assertTrue(freeSpaceDifference.get() + " >= " + freeSpaceExpectedDifference, 268 freeSpaceDifference.get() >= freeSpaceExpectedDifference); 269 270 String installPath = executeShellCommand(String.format("pm path %s", TEST_APP_PACKAGE)) 271 .replaceFirst("package:", "") 272 .trim(); 273 274 // Retrieve size of APK. 275 Long apkTrimResult = Os.stat(installPath).st_size; 276 277 // Verify trim was applied. v2+ incfs version required for valid allocation results. 278 if (getPackageManager().hasSystemFeature( 279 PackageManager.FEATURE_INCREMENTAL_DELIVERY, 2)) { 280 assertTrue(apkTrimResult <= appFileSize); 281 } 282 } 283 284 @Test testSplitInstallWithIdSig()285 public void testSplitInstallWithIdSig() throws Exception { 286 // First fully install the apk. 287 { 288 installPackage(TEST_APK); 289 assertTrue(isAppInstalled(TEST_APP_PACKAGE)); 290 } 291 292 installSplit(TEST_APK_SPLIT0); 293 assertEquals("base, config.mdpi", getSplits(TEST_APP_PACKAGE)); 294 295 installSplit(TEST_APK_SPLIT1); 296 assertEquals("base, config.hdpi, config.mdpi", getSplits(TEST_APP_PACKAGE)); 297 } 298 299 @Test testSystemInstallWithIdSig()300 public void testSystemInstallWithIdSig() throws Exception { 301 final String baseName = TEST_APK_SHELL; 302 final File file = new File(createApkPath(baseName)); 303 assertEquals( 304 "Failure [INSTALL_FAILED_SESSION_INVALID: Incremental installation of this " 305 + "package is not allowed.]\n", 306 executeShellCommand("pm install-incremental -t -g " + file.getPath())); 307 } 308 309 @LargeTest 310 @Test testInstallWithIdSigAndSplit()311 public void testInstallWithIdSigAndSplit() throws Exception { 312 File apkfile = new File(createApkPath(TEST_APK)); 313 File splitfile = new File(createApkPath(TEST_APK_SPLIT0)); 314 File[] files = new File[]{apkfile, splitfile}; 315 String param = Arrays.stream(files).map( 316 file -> file.getName() + ":" + file.length()).collect(Collectors.joining(" ")); 317 assertEquals("Success\n", executeShellCommand( 318 String.format("pm install-incremental -t -g -S %s %s", 319 (apkfile.length() + splitfile.length()), param), 320 files)); 321 assertTrue(isAppInstalled(TEST_APP_PACKAGE)); 322 assertEquals("base, config.mdpi", getSplits(TEST_APP_PACKAGE)); 323 } 324 325 @LargeTest 326 @Test testInstallWithStreaming()327 public void testInstallWithStreaming() throws Exception { 328 final String apk = createApkPath(TEST_APK); 329 final String idsig = createApkPath(TEST_APK_IDSIG); 330 mSession = 331 new IncrementalInstallSession.Builder() 332 .addApk(Paths.get(apk), Paths.get(idsig)) 333 .addExtraArgs("-t", "-i", CTS_PACKAGE_NAME) 334 .setLogger(new IncrementalDeviceConnection.Logger()) 335 .build(); 336 getUiAutomation().adoptShellPermissionIdentity(); 337 try { 338 mSession.start(Executors.newSingleThreadExecutor(), 339 IncrementalDeviceConnection.Factory.reliable()); 340 mSession.waitForInstallCompleted(30, TimeUnit.SECONDS); 341 } finally { 342 getUiAutomation().dropShellPermissionIdentity(); 343 } 344 assertTrue(isAppInstalled(TEST_APP_PACKAGE)); 345 } 346 347 @LargeTest 348 @Test testInstallWithMissingBlocks()349 public void testInstallWithMissingBlocks() throws Exception { 350 setDeviceProperty("incfs_default_timeouts", "0:0:0"); 351 setDeviceProperty("known_digesters_list", CTS_PACKAGE_NAME); 352 setSystemProperty("debug.incremental.always_enable_read_timeouts_for_system_dataloaders", 353 "0"); 354 355 final long randomSeed = System.currentTimeMillis(); 356 Log.i(TAG, "Randomizing missing blocks with seed: " + randomSeed); 357 final Random random = new Random(randomSeed); 358 359 // TODO: add detection of orphaned IncFS instances after failed installations 360 361 final int blockSize = 4096; 362 final int retries = 7; // 7 * 3s + leeway ~= 30secs of test timeout 363 364 final File apk = new File(createApkPath(TEST_APK)); 365 final int blocks = (int) (apk.length() / blockSize); 366 367 for (int i = 0; i < retries; ++i) { 368 final int skipBlock = random.nextInt(blocks); 369 Log.i(TAG, "skipBlock: " + skipBlock + " out of " + blocks); 370 try { 371 installWithBlockFilter((block -> block.getType() == PendingBlock.Type.SIGNATURE_TREE 372 || block.getBlockIndex() != skipBlock)); 373 if (isAppInstalled(TEST_APP_PACKAGE)) { 374 uninstallPackageSilently(TEST_APP_PACKAGE); 375 } 376 } catch (RuntimeException re) { 377 Log.i(TAG, "RuntimeException: ", re); 378 assertTrue(re.toString(), re.getCause() instanceof IOException); 379 } catch (IOException e) { 380 Log.i(TAG, "IOException: ", e); 381 throw new IOException("Skipped block: " + skipBlock + ", randomSeed: " + randomSeed, 382 e); 383 } 384 } 385 } 386 installWithBlockFilter(IBlockFilter blockFilter)387 public void installWithBlockFilter(IBlockFilter blockFilter) throws Exception { 388 final String apk = createApkPath(TEST_APK); 389 final String idsig = createApkPath(TEST_APK_IDSIG); 390 mSession = 391 new IncrementalInstallSession.Builder() 392 .addApk(Paths.get(apk), Paths.get(idsig)) 393 .addExtraArgs("-t", "-i", CTS_PACKAGE_NAME) 394 .setLogger(new IncrementalDeviceConnection.Logger()) 395 .setBlockFilter(blockFilter) 396 .build(); 397 getUiAutomation().adoptShellPermissionIdentity(); 398 try { 399 mSession.start(Executors.newSingleThreadExecutor(), 400 IncrementalDeviceConnection.Factory.reliableExpectInstallationFailure()); 401 mSession.waitForAnyCompletion(3, TimeUnit.SECONDS); 402 } finally { 403 getUiAutomation().dropShellPermissionIdentity(); 404 } 405 } 406 407 /** 408 * Compress the data if the compressed size is < original size, otherwise return the original 409 * data. 410 */ maybeCompressPage(ByteBuffer pageData)411 private static ByteBuffer maybeCompressPage(ByteBuffer pageData) { 412 pageData.mark(); 413 ByteArrayOutputStream compressedByteStream = new ByteArrayOutputStream(); 414 try (BlockLZ4CompressorOutputStream compressor = 415 new BlockLZ4CompressorOutputStream(compressedByteStream)) { 416 Channels.newChannel(compressor).write(pageData); 417 // This is required to make sure the bytes are written to the output 418 compressor.finish(); 419 } catch (IOException impossible) { 420 throw new AssertionError(impossible); 421 } finally { 422 pageData.reset(); 423 } 424 425 byte[] compressedBytes = compressedByteStream.toByteArray(); 426 if (compressedBytes.length < pageData.remaining()) { 427 return ByteBuffer.wrap(compressedBytes); 428 } 429 return pageData; 430 } 431 432 static final class CompressedPendingBlock extends PendingBlock { 433 final ByteBuffer mPageData; 434 CompressedPendingBlock(PendingBlock block)435 CompressedPendingBlock(PendingBlock block) throws IOException { 436 super(block); 437 438 final ByteBuffer buffer = ByteBuffer.allocate(super.getBlockSize()); 439 super.readBlockData(buffer); 440 buffer.flip(); // switch to read mode 441 442 if (super.getType() == Type.APK_DATA) { 443 mPageData = maybeCompressPage(buffer); 444 } else { 445 mPageData = buffer; 446 } 447 } 448 getCompression()449 public Compression getCompression() { 450 return this.getBlockSize() < super.getBlockSize() ? Compression.LZ4 : Compression.NONE; 451 } 452 getBlockSize()453 public short getBlockSize() { 454 return (short) mPageData.remaining(); 455 } 456 readBlockData(ByteBuffer buffer)457 public void readBlockData(ByteBuffer buffer) throws IOException { 458 mPageData.mark(); 459 buffer.put(mPageData); 460 mPageData.reset(); 461 } 462 } 463 464 static final class CompressingBlockTransformer implements IBlockTransformer { 465 @Override 466 @NonNull transform(@onNull PendingBlock block)467 public PendingBlock transform(@NonNull PendingBlock block) throws IOException { 468 return new CompressedPendingBlock(block); 469 } 470 } 471 472 @LargeTest 473 @Test testInstallWithStreamingAndCompression()474 public void testInstallWithStreamingAndCompression() throws Exception { 475 final String apk = createApkPath(TEST_APK); 476 final String idsig = createApkPath(TEST_APK_IDSIG); 477 mSession = 478 new IncrementalInstallSession.Builder() 479 .addApk(Paths.get(apk), Paths.get(idsig)) 480 .addExtraArgs("-t", "-i", CTS_PACKAGE_NAME) 481 .setLogger(new IncrementalDeviceConnection.Logger()) 482 .setBlockTransformer(new CompressingBlockTransformer()) 483 .build(); 484 getUiAutomation().adoptShellPermissionIdentity(); 485 try { 486 mSession.start(Executors.newSingleThreadExecutor(), 487 IncrementalDeviceConnection.Factory.reliable()); 488 mSession.waitForInstallCompleted(30, TimeUnit.SECONDS); 489 } finally { 490 getUiAutomation().dropShellPermissionIdentity(); 491 } 492 assertTrue(isAppInstalled(TEST_APP_PACKAGE)); 493 } 494 495 @LargeTest 496 @Test testInstallWithStreamingUnreliableConnection()497 public void testInstallWithStreamingUnreliableConnection() throws Exception { 498 final String apk = createApkPath(TEST_APK); 499 final String idsig = createApkPath(TEST_APK_IDSIG); 500 mSession = 501 new IncrementalInstallSession.Builder() 502 .addApk(Paths.get(apk), Paths.get(idsig)) 503 .addExtraArgs("-t", "-i", CTS_PACKAGE_NAME) 504 .setLogger(new IncrementalDeviceConnection.Logger()) 505 .build(); 506 getUiAutomation().adoptShellPermissionIdentity(); 507 try { 508 mSession.start(Executors.newSingleThreadExecutor(), 509 IncrementalDeviceConnection.Factory.ureliable()); 510 mSession.waitForInstallCompleted(30, TimeUnit.SECONDS); 511 } catch (Exception ignored) { 512 // Ignore, we are looking for crashes anyway. 513 } finally { 514 getUiAutomation().dropShellPermissionIdentity(); 515 } 516 } 517 518 @Test testInstallWithIdSigInvalidLength()519 public void testInstallWithIdSigInvalidLength() throws Exception { 520 File file = new File(createApkPath(TEST_APK)); 521 Truth.assertThat( 522 executeShellCommand("pm install-incremental -t -g -S " + (file.length() - 1), 523 new File[]{file})).contains( 524 "Failure"); 525 assertFalse(isAppInstalled(TEST_APP_PACKAGE)); 526 } 527 528 @Test testInstallWithInvalidIdSig()529 public void testInstallWithInvalidIdSig() throws Exception { 530 File file = new File(createApkPath(TEST_APK_MALFORMED)); 531 Truth.assertThat( 532 executeShellCommand("pm install-incremental -t -g " + file.getPath())).contains( 533 "Failure"); 534 assertFalse(isAppInstalled(TEST_APP_PACKAGE)); 535 } 536 537 @LargeTest 538 @Test testInstallWithIdSigStreamIncompleteData()539 public void testInstallWithIdSigStreamIncompleteData() throws Exception { 540 File file = new File(createApkPath(TEST_APK)); 541 long length = file.length(); 542 // Streaming happens in blocks of 1024 bytes, new length will not stream the last block. 543 long newLength = length - (length % 1024 == 0 ? 1024 : length % 1024); 544 Truth.assertThat( 545 executeShellCommand( 546 "pm install-incremental -t -g -S " + length, 547 new File[]{file}, 548 new long[]{newLength})).contains("Failure"); 549 assertFalse(isAppInstalled(TEST_APP_PACKAGE)); 550 } 551 552 @LargeTest 553 @Test testInstallWithIdSigNoMissingPages()554 public void testInstallWithIdSigNoMissingPages() throws Exception { 555 final int installIterations = 1; 556 final int atraceDumpIterations = 3; 557 final int atraceDumpDelayMs = 1000; 558 final String missingPageReads = "|missing_page_reads: count="; 559 560 final ArrayList<String> missingPages = new ArrayList<>(); 561 562 checkSysTrace( 563 installIterations, 564 atraceDumpIterations, 565 atraceDumpDelayMs, 566 () -> { 567 // Install multiple splits so that digesters won't kick in. 568 installPackage(TEST_APK); 569 installSplit(TEST_APK_SPLIT0); 570 installSplit(TEST_APK_SPLIT1); 571 installSplit(TEST_APK_SPLIT2); 572 // Now read it as fast as we can. 573 readSplitInChunks("base.apk"); 574 readSplitInChunks("split_config.mdpi.apk"); 575 readSplitInChunks("split_config.hdpi.apk"); 576 readSplitInChunks("split_config.xhdpi.apk"); 577 return null; 578 }, 579 (stdout) -> { 580 try (Scanner scanner = new Scanner(stdout)) { 581 ReadLogEntry prevLogEntry = null; 582 while (scanner.hasNextLine()) { 583 final String line = scanner.nextLine(); 584 585 final ReadLogEntry readLogEntry = ReadLogEntry.parse(line); 586 if (readLogEntry != null) { 587 prevLogEntry = readLogEntry; 588 continue; 589 } 590 591 int missingPageIdx = line.indexOf(missingPageReads); 592 if (missingPageIdx == -1) { 593 continue; 594 } 595 String missingBlocks = line.substring( 596 missingPageIdx + missingPageReads.length()); 597 598 int prvTimestamp = prevLogEntry != null ? extractTimestamp( 599 prevLogEntry.line) : -1; 600 int curTimestamp = extractTimestamp(line); 601 if (prvTimestamp == -1 || curTimestamp == -1) { 602 missingPages.add("count=" + missingBlocks); 603 continue; 604 } 605 606 int delta = curTimestamp - prvTimestamp; 607 missingPages.add( 608 "count=" + missingBlocks + ", timestamp delta=" + delta + "ms"); 609 } 610 return false; 611 } 612 }); 613 614 assertTrue("Missing page reads found in atrace dump: " + String.join("\n", missingPages), 615 missingPages.isEmpty()); 616 } 617 618 static class ReadLogEntry { 619 public final String line; 620 public final int blockIdx; 621 public final int count; 622 public final int fileIdx; 623 public final int appId; 624 public final int userId; 625 ReadLogEntry(String line, int blockIdx, int count, int fileIdx, int appId, int userId)626 private ReadLogEntry(String line, int blockIdx, int count, int fileIdx, int appId, 627 int userId) { 628 this.line = line; 629 this.blockIdx = blockIdx; 630 this.count = count; 631 this.fileIdx = fileIdx; 632 this.appId = appId; 633 this.userId = userId; 634 } 635 toString()636 public String toString() { 637 return blockIdx + "/" + count + "/" + fileIdx + "/" + appId + "/" + userId; 638 } 639 640 static final String BLOCK_PREFIX = "|page_read: index="; 641 static final String COUNT_PREFIX = " count="; 642 static final String FILE_PREFIX = " file="; 643 static final String APP_ID_PREFIX = " appid="; 644 static final String USER_ID_PREFIX = " userid="; 645 parseInt(String line, int prefixIdx, int prefixLen, int endIdx)646 private static int parseInt(String line, int prefixIdx, int prefixLen, int endIdx) { 647 if (prefixIdx == -1) { 648 return -1; 649 } 650 final String intStr; 651 if (endIdx != -1) { 652 intStr = line.substring(prefixIdx + prefixLen, endIdx); 653 } else { 654 intStr = line.substring(prefixIdx + prefixLen); 655 } 656 657 return Integer.parseInt(intStr); 658 } 659 parse(String line)660 static ReadLogEntry parse(String line) { 661 int blockIdx = line.indexOf(BLOCK_PREFIX); 662 if (blockIdx == -1) { 663 return null; 664 } 665 int countIdx = line.indexOf(COUNT_PREFIX, blockIdx + BLOCK_PREFIX.length()); 666 if (countIdx == -1) { 667 return null; 668 } 669 int fileIdx = line.indexOf(FILE_PREFIX, countIdx + COUNT_PREFIX.length()); 670 if (fileIdx == -1) { 671 return null; 672 } 673 int appIdIdx = line.indexOf(APP_ID_PREFIX, fileIdx + FILE_PREFIX.length()); 674 final int userIdIdx; 675 if (appIdIdx != -1) { 676 userIdIdx = line.indexOf(USER_ID_PREFIX, appIdIdx + APP_ID_PREFIX.length()); 677 } else { 678 userIdIdx = -1; 679 } 680 681 return new ReadLogEntry( 682 line, 683 parseInt(line, blockIdx, BLOCK_PREFIX.length(), countIdx), 684 parseInt(line, countIdx, COUNT_PREFIX.length(), fileIdx), 685 parseInt(line, fileIdx, FILE_PREFIX.length(), appIdIdx), 686 parseInt(line, appIdIdx, APP_ID_PREFIX.length(), userIdIdx), 687 parseInt(line, userIdIdx, USER_ID_PREFIX.length(), -1)); 688 } 689 } 690 691 @Test testReadLogParser()692 public void testReadLogParser() throws Exception { 693 assertEquals(null, ReadLogEntry.parse("# tracer: nop\n")); 694 assertEquals( 695 "178/290/0/10184/0", 696 ReadLogEntry.parse( 697 "<...>-2777 ( 1639) [006] .... 2764.227110: tracing_mark_write: " 698 + "B|1639|page_read: index=178 count=290 file=0 appid=10184 " 699 + "userid=0") 700 .toString()); 701 assertEquals( 702 null, 703 ReadLogEntry.parse( 704 "<...>-2777 ( 1639) [006] .... 2764.227111: tracing_mark_write: E|1639")); 705 assertEquals( 706 "468/337/0/10184/2", 707 ReadLogEntry.parse( 708 "<...>-2777 ( 1639) [006] .... 2764.243227: tracing_mark_write: " 709 + "B|1639|page_read: index=468 count=337 file=0 appid=10184 " 710 + "userid=2") 711 .toString()); 712 assertEquals( 713 null, 714 ReadLogEntry.parse( 715 "<...>-2777 ( 1639) [006] .... 2764.243229: tracing_mark_write: E|1639")); 716 assertEquals( 717 "18/9/3/-1/-1", 718 ReadLogEntry.parse( 719 " <...>-2777 ( 1639) [006] .... 2764.227095: " 720 + "tracing_mark_write: B|1639|page_read: index=18 count=9 file=3") 721 .toString()); 722 } 723 extractTimestamp(String line)724 static int extractTimestamp(String line) { 725 final String timestampEnd = ": tracing_mark_write:"; 726 int timestampEndIdx = line.indexOf(timestampEnd); 727 if (timestampEndIdx == -1) { 728 return -1; 729 } 730 731 int timestampBegIdx = timestampEndIdx - 1; 732 for (; timestampBegIdx >= 0; --timestampBegIdx) { 733 char ch = line.charAt(timestampBegIdx); 734 if ('0' <= ch && ch <= '9' || ch == '.') { 735 continue; 736 } 737 break; 738 } 739 double timestamp = Double.parseDouble(line.substring(timestampBegIdx, timestampEndIdx)); 740 return (int) (timestamp * 1000); 741 } 742 743 @Test testExtractTimestamp()744 public void testExtractTimestamp() throws Exception { 745 assertEquals(-1, extractTimestamp("# tracer: nop\n")); 746 assertEquals(14255168, extractTimestamp( 747 "<...>-10355 ( 1636) [006] .... 14255.168694: tracing_mark_write: " 748 + "B|1636|page_read: index=1 count=16 file=0 appid=10184 userid=0")); 749 assertEquals(2764243, extractTimestamp( 750 "<...>-2777 ( 1639) [006] .... 2764.243225: tracing_mark_write: " 751 + "B|1639|missing_page_reads: count=132")); 752 assertEquals(114176, extractTimestamp( 753 "DataLoaderManag-8339 ( 1780) [004] .... 114.176342: tracing_mark_write: " 754 + "B|1780|page_read: index=1846 count=21 file=0 appid=10151 userid=0")); 755 } 756 static class AppReads { 757 public final String packageName; 758 public final int reads; 759 AppReads(String packageName, int reads)760 AppReads(String packageName, int reads) { 761 this.packageName = packageName; 762 this.reads = reads; 763 } 764 } 765 766 @LargeTest 767 @Test testInstallWithIdSigNoDigesting()768 public void testInstallWithIdSigNoDigesting() throws Exception { 769 // Overall timeout of 3secs in 100ms intervals. 770 final int installIterations = 1; 771 final int atraceDumpIterations = 30; 772 final int atraceDumpDelayMs = 100; 773 final int blockSize = 4096; 774 775 final String[] apks = 776 new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2, 777 TEST_HW7_SPLIT3, TEST_HW7_SPLIT4}; 778 final boolean[][] touched = new boolean[apks.length][]; 779 final int[] blocks = new int[apks.length]; 780 final AtomicLong[] totalTouchedBlocks = new AtomicLong[apks.length]; 781 for (int i = 0, size = apks.length; i < size; ++i) { 782 final String apkName = apks[i]; 783 final File apkfile = new File(createApkPath(apkName)); 784 blocks[i] = (int) ((apkfile.length() + blockSize - 1) / blockSize); 785 touched[i] = new boolean[blocks[i]]; 786 totalTouchedBlocks[i] = new AtomicLong(0); 787 } 788 789 final ArrayMap<Integer, Integer> uids = new ArrayMap<>(); 790 791 checkSysTrace( 792 installIterations, 793 atraceDumpIterations, 794 atraceDumpDelayMs, 795 () -> { 796 mSession = 797 new IncrementalInstallSession.Builder() 798 .addApk(Paths.get(createApkPath(TEST_HW7)), 799 Paths.get(createApkPath(TEST_HW7_IDSIG))) 800 .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT0)), 801 Paths.get(createApkPath(TEST_HW7_SPLIT0_IDSIG))) 802 .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT1)), 803 Paths.get(createApkPath(TEST_HW7_SPLIT1_IDSIG))) 804 .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT2)), 805 Paths.get(createApkPath(TEST_HW7_SPLIT2_IDSIG))) 806 .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT3)), 807 Paths.get(createApkPath(TEST_HW7_SPLIT3_IDSIG))) 808 .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT4)), 809 Paths.get(createApkPath(TEST_HW7_SPLIT4_IDSIG))) 810 .addExtraArgs("-t", "-i", CTS_PACKAGE_NAME, 811 "--skip-verification") 812 .setLogger(new IncrementalDeviceConnection.Logger()) 813 .build(); 814 getUiAutomation().adoptShellPermissionIdentity(); 815 try { 816 mSession.start(Executors.newSingleThreadExecutor(), 817 IncrementalDeviceConnection.Factory.reliable()); 818 mSession.waitForInstallCompleted(30, TimeUnit.SECONDS); 819 assertEquals( 820 "base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, " 821 + "config.xxxhdpi", 822 getSplits(TEST_APP_PACKAGE)); 823 } finally { 824 getUiAutomation().dropShellPermissionIdentity(); 825 } 826 return null; 827 }, 828 (stdout) -> { 829 try (Scanner scanner = new Scanner(stdout)) { 830 while (scanner.hasNextLine()) { 831 String line = scanner.nextLine(); 832 final ReadLogEntry readLogEntry = ReadLogEntry.parse(line); 833 if (readLogEntry == null) { 834 continue; 835 } 836 int fileIdx = readLogEntry.fileIdx; 837 for (int i = 0, count = readLogEntry.count; i < count; ++i) { 838 int blockIdx = readLogEntry.blockIdx + i; 839 if (touched[fileIdx][blockIdx]) { 840 continue; 841 } 842 843 touched[fileIdx][blockIdx] = true; 844 845 int uid = UserHandle.getUid(readLogEntry.userId, 846 readLogEntry.appId); 847 Integer touchedByUid = uids.get(uid); 848 uids.put(uid, touchedByUid == null ? 1 : touchedByUid + 1); 849 850 long totalTouched = totalTouchedBlocks[fileIdx].incrementAndGet(); 851 if (totalTouched >= blocks[fileIdx]) { 852 return true; 853 } 854 } 855 } 856 return false; 857 } 858 }); 859 860 int firstFileIdx = CHECK_BASE_APK_DIGESTION ? 0 : 1; 861 862 boolean found = false; 863 for (int i = firstFileIdx, size = blocks.length; i < size; ++i) { 864 if (totalTouchedBlocks[i].get() >= blocks[i]) { 865 found = true; 866 break; 867 } 868 } 869 if (!found) { 870 return; 871 } 872 873 PackageManager pm = getPackageManager(); 874 875 AppReads[] appIdReads = new AppReads[uids.size()]; 876 for (int i = 0, size = uids.size(); i < size; ++i) { 877 final int uid = uids.keyAt(i); 878 final int appId = UserHandle.getAppId(uid); 879 final int userId = UserHandle.getUserId(uid); 880 881 final String packageName; 882 if (appId < Process.FIRST_APPLICATION_UID) { 883 packageName = "<system>"; 884 } else { 885 String[] packages = pm.getPackagesForUid(uid); 886 if (packages == null || packages.length == 0) { 887 packageName = "<unknown package, appId=" + appId + ", userId=" + userId + ">"; 888 } else { 889 packageName = "[" + String.join(",", packages) + "]"; 890 } 891 } 892 appIdReads[i] = new AppReads(packageName, uids.valueAt(i)); 893 } 894 Arrays.sort(appIdReads, (lhs, rhs) -> Integer.compare(rhs.reads, lhs.reads)); 895 896 final String packages = String.join("\n", Arrays.stream(appIdReads).map( 897 item -> item.packageName + " : " + item.reads + " blocks").toArray(String[]::new)); 898 fail("Digesting detected, list of packages: " + packages); 899 } 900 901 @LargeTest 902 @Test testInstallWithIdSigPerUidTimeouts()903 public void testInstallWithIdSigPerUidTimeouts() throws Exception { 904 executeShellCommand("atrace --async_start -b 1024 -c adb"); 905 try { 906 setDeviceProperty("incfs_default_timeouts", "5000000:5000000:5000000"); 907 setDeviceProperty("known_digesters_list", CTS_PACKAGE_NAME); 908 909 installPackage(TEST_APK); 910 assertTrue(isAppInstalled(TEST_APP_PACKAGE)); 911 } finally { 912 executeShellCommand("atrace --async_stop"); 913 } 914 } 915 916 @LargeTest 917 @Test testInstallWithIdSigStreamPerUidTimeoutsIncompleteData()918 public void testInstallWithIdSigStreamPerUidTimeoutsIncompleteData() throws Exception { 919 // To disable verification. 920 installNonIncremental(TEST_APK); 921 922 checkIncrementalDeliveryV2Feature(); 923 924 mSession = 925 new IncrementalInstallSession.Builder() 926 .addApk(Paths.get(createApkPath(TEST_HW7)), 927 Paths.get(createApkPath(TEST_HW7_IDSIG))) 928 .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT0)), 929 Paths.get(createApkPath(TEST_HW7_SPLIT0_IDSIG))) 930 .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT1)), 931 Paths.get(createApkPath(TEST_HW7_SPLIT1_IDSIG))) 932 .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT2)), 933 Paths.get(createApkPath(TEST_HW7_SPLIT2_IDSIG))) 934 .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT3)), 935 Paths.get(createApkPath(TEST_HW7_SPLIT3_IDSIG))) 936 .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT4)), 937 Paths.get(createApkPath(TEST_HW7_SPLIT4_IDSIG))) 938 .addExtraArgs("-t", "-i", CTS_PACKAGE_NAME, "--skip-verification") 939 .setLogger(new IncrementalDeviceConnection.Logger()) 940 .build(); 941 942 executeShellCommand("atrace --async_start -b 10240 -c adb"); 943 try { 944 setDeviceProperty("incfs_default_timeouts", "20000000:20000000:20000000"); 945 setDeviceProperty("known_digesters_list", CTS_PACKAGE_NAME); 946 947 final int beforeReadDelayMs = 1000; 948 Thread.currentThread().sleep(beforeReadDelayMs); 949 950 // Partially install the apk+split0/1/2/3/4. 951 getUiAutomation().adoptShellPermissionIdentity(); 952 try { 953 mSession.start(Executors.newSingleThreadExecutor(), 954 IncrementalDeviceConnection.Factory.reliable()); 955 mSession.waitForInstallCompleted(30, TimeUnit.SECONDS); 956 assertEquals( 957 "base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config" 958 + ".xxxhdpi", 959 getSplits(TEST_APP_PACKAGE)); 960 } finally { 961 getUiAutomation().dropShellPermissionIdentity(); 962 } 963 964 final String packagePath = getCodePath(TEST_APP_PACKAGE); 965 966 // Try to read splits and see if we are throttled at least once. 967 long maxReadTime = 0; 968 for (String splitName : new String[]{"split_config.hdpi.apk", "split_config.mdpi.apk", 969 "split_config.xhdpi.apk", "split_config.xxxhdpi.apk", 970 "split_config.xxxhdpi.apk"}) { 971 final File apkToRead = new File(packagePath, splitName); 972 final long readTime0 = readAndReportTime(apkToRead, 1000); 973 974 if (readTime0 < EXPECTED_READ_TIME) { 975 executeShellCommand("atrace --async_dump"); 976 } 977 maxReadTime = Math.max(maxReadTime, readTime0); 978 if (maxReadTime >= EXPECTED_READ_TIME) { 979 break; 980 } 981 } 982 assertTrue("Must take longer than " + EXPECTED_READ_TIME + "ms: time0=" + maxReadTime 983 + "ms", maxReadTime >= EXPECTED_READ_TIME); 984 } finally { 985 executeShellCommand("atrace --async_stop"); 986 } 987 } 988 989 @LargeTest 990 @Test testInstallWithIdSigPerUidTimeoutsIgnored()991 public void testInstallWithIdSigPerUidTimeoutsIgnored() throws Exception { 992 // Timeouts would be ignored as there are no readlogs collected. 993 final int beforeReadDelayMs = 5000; 994 setDeviceProperty("incfs_default_timeouts", "5000000:5000000:5000000"); 995 setDeviceProperty("known_digesters_list", CTS_PACKAGE_NAME); 996 997 // First fully install the apk and a split0. 998 { 999 installPackage(TEST_APK); 1000 assertTrue(isAppInstalled(TEST_APP_PACKAGE)); 1001 installSplit(TEST_APK_SPLIT0); 1002 assertEquals("base, config.mdpi", getSplits(TEST_APP_PACKAGE)); 1003 installSplit(TEST_APK_SPLIT1); 1004 assertEquals("base, config.hdpi, config.mdpi", getSplits(TEST_APP_PACKAGE)); 1005 } 1006 1007 // Allow IncrementalService to update the timeouts after full download. 1008 Thread.currentThread().sleep(beforeReadDelayMs); 1009 1010 // Try to read a split and see if we are throttled. 1011 final long readTime = readAndReportTime(getSplit("split_config.mdpi.apk"), 1000); 1012 assertTrue("Must take less than " + EXPECTED_READ_TIME + "ms vs " + readTime + "ms", 1013 readTime < EXPECTED_READ_TIME); 1014 } 1015 1016 @Test testInstallWithIdSigStreamIncompleteDataForSplit()1017 public void testInstallWithIdSigStreamIncompleteDataForSplit() throws Exception { 1018 File apkfile = new File(createApkPath(TEST_APK)); 1019 File splitfile = new File(createApkPath(TEST_APK_SPLIT0)); 1020 long splitLength = splitfile.length(); 1021 // Don't fully stream the split. 1022 long newSplitLength = splitLength - (splitLength % 1024 == 0 ? 1024 : splitLength % 1024); 1023 File[] files = new File[]{apkfile, splitfile}; 1024 String param = Arrays.stream(files).map( 1025 file -> file.getName() + ":" + file.length()).collect(Collectors.joining(" ")); 1026 Truth.assertThat(executeShellCommand( 1027 String.format("pm install-incremental -t -g -S %s %s", 1028 (apkfile.length() + splitfile.length()), param), 1029 files, new long[]{apkfile.length(), newSplitLength})).contains("Failure"); 1030 assertFalse(isAppInstalled(TEST_APP_PACKAGE)); 1031 } 1032 1033 static class TestDataLoaderService extends DataLoaderService { 1034 } 1035 1036 @Test testDataLoaderServiceDefaultImplementation()1037 public void testDataLoaderServiceDefaultImplementation() { 1038 DataLoaderService service = new TestDataLoaderService(); 1039 assertEquals(null, service.onCreateDataLoader(null)); 1040 IBinder binder = service.onBind(null); 1041 assertNotEquals(null, binder); 1042 assertEquals(binder, service.onBind(new Intent())); 1043 } 1044 1045 @LargeTest 1046 @Test testInstallSysTraceDebuggable()1047 public void testInstallSysTraceDebuggable() throws Exception { 1048 doTestInstallSysTrace(TEST_APK); 1049 } 1050 1051 @LargeTest 1052 @Test testInstallSysTraceProfileable()1053 public void testInstallSysTraceProfileable() throws Exception { 1054 doTestInstallSysTrace(TEST_APK_PROFILEABLE); 1055 } 1056 1057 @LargeTest 1058 @Test testInstallSysTraceNoReadlogs()1059 public void testInstallSysTraceNoReadlogs() throws Exception { 1060 setSystemProperty("debug.incremental.enforce_readlogs_max_interval_for_system_dataloaders", 1061 "1"); 1062 setSystemProperty("debug.incremental.readlogs_max_interval_sec", "0"); 1063 1064 final int atraceDumpIterations = 30; 1065 final int atraceDumpDelayMs = 100; 1066 final String expected = "|page_read:"; 1067 1068 // We don't expect any readlogs with 0sec interval. 1069 assertFalse( 1070 "Page reads (" + expected + ") were found in atrace dump", 1071 checkSysTraceForSubstring(TEST_APK, expected, atraceDumpIterations, 1072 atraceDumpDelayMs)); 1073 } 1074 checkSysTraceForSubstring(String testApk, final String expected, int atraceDumpIterations, int atraceDumpDelayMs)1075 private boolean checkSysTraceForSubstring(String testApk, final String expected, 1076 int atraceDumpIterations, int atraceDumpDelayMs) throws Exception { 1077 final int installIterations = 3; 1078 return checkSysTrace( 1079 installIterations, 1080 atraceDumpIterations, 1081 atraceDumpDelayMs, 1082 () -> installPackage(testApk), 1083 (stdout) -> stdout.contains(expected)); 1084 } 1085 checkSysTrace( int installIterations, int atraceDumpIterations, int atraceDumpDelayMs, final Callable<Void> installer, final Function<String, Boolean> checker)1086 private boolean checkSysTrace( 1087 int installIterations, 1088 int atraceDumpIterations, 1089 int atraceDumpDelayMs, 1090 final Callable<Void> installer, 1091 final Function<String, Boolean> checker) 1092 throws Exception { 1093 final int beforeReadDelayMs = 1000; 1094 1095 final CompletableFuture<Boolean> result = new CompletableFuture<>(); 1096 final Thread readFromProcess = new Thread(() -> { 1097 try { 1098 executeShellCommand("atrace --async_start -b 10240 -c adb"); 1099 try { 1100 for (int i = 0; i < atraceDumpIterations; ++i) { 1101 final String stdout = executeShellCommand("atrace --async_dump"); 1102 try { 1103 if (checker.apply(stdout)) { 1104 result.complete(true); 1105 break; 1106 } 1107 Thread.currentThread().sleep(atraceDumpDelayMs); 1108 } catch (InterruptedException ignored) { 1109 } 1110 } 1111 } finally { 1112 executeShellCommand("atrace --async_stop"); 1113 } 1114 } catch (IOException ignored) { 1115 } 1116 }); 1117 readFromProcess.start(); 1118 1119 for (int i = 0; i < installIterations; ++i) { 1120 installer.call(); 1121 assertTrue(isAppInstalled(TEST_APP_PACKAGE)); 1122 Thread.currentThread().sleep(beforeReadDelayMs); 1123 uninstallPackageSilently(TEST_APP_PACKAGE); 1124 } 1125 1126 readFromProcess.join(); 1127 return result.getNow(false); 1128 } 1129 doTestInstallSysTrace(String testApk)1130 private void doTestInstallSysTrace(String testApk) throws Exception { 1131 // Async atrace dump uses less resources but requires periodic pulls. 1132 // Overall timeout of 10secs in 100ms intervals should be enough. 1133 final int atraceDumpIterations = 100; 1134 final int atraceDumpDelayMs = 100; 1135 final String expected = "|page_read:"; 1136 1137 assertTrue( 1138 "No page reads (" + expected + ") found in atrace dump", 1139 checkSysTraceForSubstring(testApk, expected, atraceDumpIterations, 1140 atraceDumpDelayMs)); 1141 } 1142 isAppInstalled(String packageName)1143 static boolean isAppInstalled(String packageName) throws IOException { 1144 return isAppInstalledForUser(packageName, -1); 1145 } 1146 isAppInstalledForUser(String packageName, int userId)1147 static boolean isAppInstalledForUser(String packageName, int userId) throws IOException { 1148 final String command = userId < 0 ? "pm list packages " + packageName : 1149 "pm list packages --user " + userId + " " + packageName; 1150 final String commandResult = executeShellCommand(command); 1151 return Arrays.stream(commandResult.split("\\r?\\n")) 1152 .anyMatch(line -> line.equals("package:" + packageName)); 1153 } 1154 getSplits(String packageName)1155 private String getSplits(String packageName) throws IOException { 1156 final String result = parsePackageDump(packageName, " splits=["); 1157 if (TextUtils.isEmpty(result)) { 1158 return null; 1159 } 1160 return result.substring(0, result.length() - 1); 1161 } 1162 getCodePath(String packageName)1163 private String getCodePath(String packageName) throws IOException { 1164 return parsePackageDump(packageName, " codePath="); 1165 } 1166 getSplit(String splitName)1167 private File getSplit(String splitName) throws Exception { 1168 return new File(getCodePath(TEST_APP_PACKAGE), splitName); 1169 } 1170 parsePackageDump(String packageName, String prefix)1171 private String parsePackageDump(String packageName, String prefix) throws IOException { 1172 final String commandResult = executeShellCommand("pm dump " + packageName); 1173 final int prefixLength = prefix.length(); 1174 Optional<String> maybeSplits = Arrays.stream(commandResult.split("\\r?\\n")) 1175 .filter(line -> line.startsWith(prefix)).findFirst(); 1176 if (!maybeSplits.isPresent()) { 1177 return null; 1178 } 1179 String splits = maybeSplits.get(); 1180 return splits.substring(prefixLength); 1181 } 1182 createApkPath(String baseName)1183 private static String createApkPath(String baseName) { 1184 return TEST_APK_PATH + baseName; 1185 } 1186 installNonIncremental(String baseName)1187 static void installNonIncremental(String baseName) throws IOException { 1188 File file = new File(createApkPath(baseName)); 1189 assertEquals("Success\n", 1190 executeShellCommand("pm install -t -g " + file.getPath())); 1191 } 1192 installPackage(String baseName)1193 static Void installPackage(String baseName) throws IOException { 1194 File file = new File(createApkPath(baseName)); 1195 assertEquals("Success\n", 1196 executeShellCommand("pm install-incremental -t -g " + file.getPath())); 1197 return null; 1198 } 1199 installSplit(String splitName)1200 private void installSplit(String splitName) throws Exception { 1201 final File splitfile = new File(createApkPath(splitName)); 1202 1203 try (InputStream inputStream = executeShellCommandStream( 1204 "pm install-incremental -t -g -p " + TEST_APP_PACKAGE + " " 1205 + splitfile.getPath())) { 1206 assertEquals("Success\n", readFullStream(inputStream)); 1207 } 1208 } 1209 readSplitInChunks(String splitName)1210 private void readSplitInChunks(String splitName) throws Exception { 1211 final int chunks = 2; 1212 final int waitBetweenChunksMs = 100; 1213 final File file = getSplit(splitName); 1214 1215 assertTrue(file.toString(), file.exists()); 1216 final long totalSize = file.length(); 1217 final long chunkSize = totalSize / chunks; 1218 try (InputStream baseApkStream = new FileInputStream(file)) { 1219 final byte[] buffer = new byte[4 * 1024]; 1220 long readSoFar = 0; 1221 long maxToRead = 0; 1222 for (int i = 0; i < chunks; ++i) { 1223 maxToRead += chunkSize; 1224 int length; 1225 while ((length = baseApkStream.read(buffer)) != -1) { 1226 readSoFar += length; 1227 if (readSoFar >= maxToRead) { 1228 break; 1229 } 1230 } 1231 if (readSoFar < totalSize) { 1232 Thread.currentThread().sleep(waitBetweenChunksMs); 1233 } 1234 } 1235 } 1236 } 1237 readAndReportTime(File file, long borderTime)1238 private long readAndReportTime(File file, long borderTime) throws Exception { 1239 final long startTime = SystemClock.uptimeMillis(); 1240 assertTrue(file.toString(), file.exists()); 1241 try (InputStream baseApkStream = new FileInputStream(file)) { 1242 final byte[] buffer = new byte[128 * 1024]; 1243 while (baseApkStream.read(buffer) != -1) { 1244 long readTime = SystemClock.uptimeMillis() - startTime; 1245 if (readTime >= borderTime) { 1246 break; 1247 } 1248 } 1249 } 1250 return SystemClock.uptimeMillis() - startTime; 1251 } 1252 uninstallPackageSilently(String packageName)1253 static String uninstallPackageSilently(String packageName) throws IOException { 1254 return executeShellCommand("pm uninstall " + packageName); 1255 } 1256 1257 interface Result { await()1258 boolean await() throws Exception; 1259 } 1260 executeShellCommand(String command)1261 static String executeShellCommand(String command) throws IOException { 1262 try (InputStream inputStream = executeShellCommandStream(command)) { 1263 return readFullStream(inputStream); 1264 } 1265 } 1266 executeShellCommandStream(String command)1267 private static InputStream executeShellCommandStream(String command) throws IOException { 1268 final ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(command); 1269 return new ParcelFileDescriptor.AutoCloseInputStream(stdout); 1270 } 1271 executeShellCommand(String command, File[] inputs)1272 private static String executeShellCommand(String command, File[] inputs) 1273 throws IOException { 1274 return executeShellCommand(command, inputs, Stream.of(inputs).mapToLong( 1275 File::length).toArray()); 1276 } 1277 executeShellCommand(String command, File[] inputs, long[] expected)1278 private static String executeShellCommand(String command, File[] inputs, long[] expected) 1279 throws IOException { 1280 try (InputStream inputStream = executeShellCommandRw(command, inputs, expected)) { 1281 return readFullStream(inputStream); 1282 } 1283 } 1284 executeShellCommandRw(String command, File[] inputs, long[] expected)1285 private static InputStream executeShellCommandRw(String command, File[] inputs, long[] expected) 1286 throws IOException { 1287 assertEquals(inputs.length, expected.length); 1288 final ParcelFileDescriptor[] pfds = 1289 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1290 .executeShellCommandRw(command); 1291 ParcelFileDescriptor stdout = pfds[0]; 1292 ParcelFileDescriptor stdin = pfds[1]; 1293 try (FileOutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream( 1294 stdin)) { 1295 for (int i = 0; i < inputs.length; i++) { 1296 try (FileInputStream inputStream = new FileInputStream(inputs[i])) { 1297 writeFullStream(inputStream, outputStream, expected[i]); 1298 } 1299 } 1300 } 1301 return new ParcelFileDescriptor.AutoCloseInputStream(stdout); 1302 } 1303 readFullStream(InputStream inputStream, long expected)1304 static String readFullStream(InputStream inputStream, long expected) 1305 throws IOException { 1306 ByteArrayOutputStream result = new ByteArrayOutputStream(); 1307 writeFullStream(inputStream, result, expected); 1308 return result.toString("UTF-8"); 1309 } 1310 readFullStream(InputStream inputStream)1311 static String readFullStream(InputStream inputStream) throws IOException { 1312 return readFullStream(inputStream, -1); 1313 } 1314 writeFullStream(InputStream inputStream, OutputStream outputStream, long expected)1315 static void writeFullStream(InputStream inputStream, OutputStream outputStream, 1316 long expected) 1317 throws IOException { 1318 final byte[] buffer = new byte[1024]; 1319 long total = 0; 1320 int length; 1321 while ((length = inputStream.read(buffer)) != -1 && (expected < 0 || total < expected)) { 1322 outputStream.write(buffer, 0, length); 1323 total += length; 1324 } 1325 if (expected > 0) { 1326 assertEquals(expected, total); 1327 } 1328 } 1329 cleanup()1330 private void cleanup() throws Exception { 1331 uninstallPackageSilently(TEST_APP_PACKAGE); 1332 assertFalse(isAppInstalled(TEST_APP_PACKAGE)); 1333 assertEquals(null, getSplits(TEST_APP_PACKAGE)); 1334 setDeviceProperty("incfs_default_timeouts", null); 1335 setDeviceProperty("known_digesters_list", null); 1336 setSystemProperty("debug.incremental.enforce_readlogs_max_interval_for_system_dataloaders", 1337 "0"); 1338 setSystemProperty("debug.incremental.readlogs_max_interval_sec", "10000"); 1339 setSystemProperty("debug.incremental.always_enable_read_timeouts_for_system_dataloaders", 1340 "1"); 1341 IoUtils.closeQuietly(mSession); 1342 mSession = null; 1343 } 1344 setDeviceProperty(String name, String value)1345 static void setDeviceProperty(String name, String value) { 1346 getUiAutomation().adoptShellPermissionIdentity(WRITE_DEVICE_CONFIG); 1347 try { 1348 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, name, value, 1349 false); 1350 } finally { 1351 getUiAutomation().dropShellPermissionIdentity(); 1352 } 1353 } 1354 setSystemProperty(String name, String value)1355 static void setSystemProperty(String name, String value) throws Exception { 1356 executeShellCommand("setprop " + name + " " + value); 1357 } 1358 1359 } 1360 1361