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