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