• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tests.sdksandbox.host;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.fail;
22 import static org.junit.Assume.assumeTrue;
23 
24 import android.cts.install.lib.host.InstallUtilsHost;
25 
26 import com.android.tradefed.log.LogUtil.CLog;
27 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
28 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
29 
30 import org.junit.After;
31 import org.junit.Before;
32 import org.junit.Ignore;
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 
36 import java.io.BufferedInputStream;
37 import java.io.File;
38 import java.io.InputStream;
39 import java.security.MessageDigest;
40 import java.security.cert.Certificate;
41 import java.util.Arrays;
42 import java.util.jar.JarEntry;
43 import java.util.jar.JarFile;
44 
45 import javax.annotation.Nullable;
46 
47 @RunWith(DeviceJUnit4ClassRunner.class)
48 public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test {
49 
50     private int mOriginalUserId;
51     private int mSecondaryUserId = -1;
52     private boolean mWasRoot;
53 
54     private static final String CODE_PROVIDER_APK = "StorageTestCodeProvider.apk";
55     private static final String TEST_APP_STORAGE_PACKAGE = "com.android.tests.sdksandbox";
56     private static final String TEST_APP_STORAGE_APK = "SdkSandboxStorageTestApp.apk";
57     private static final String TEST_APP_STORAGE_V2_NO_SDK =
58             "SdkSandboxStorageTestAppV2_DoesNotConsumeSdk.apk";
59     private static final String SDK_PACKAGE = "com.android.tests.codeprovider.storagetest";
60 
61     private static final String SYS_PROP_DEFAULT_CERT_DIGEST =
62             "debug.pm.uses_sdk_library_default_cert_digest";
63 
64     private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60;
65     private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000;
66 
67     private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
68 
69     /**
70      * Runs the given phase of a test by calling into the device.
71      * Throws an exception if the test phase fails.
72      * <p>
73      * For example, <code>runPhase("testExample");</code>
74      */
runPhase(String phase)75     private void runPhase(String phase) throws Exception {
76         assertThat(runDeviceTests("com.android.tests.sdksandbox",
77                 "com.android.tests.sdksandbox.SdkSandboxStorageTestApp",
78                 phase)).isTrue();
79     }
80 
81     @Before
setUp()82     public void setUp() throws Exception {
83         // TODO(b/209061624): See if we can remove root privilege when instrumentation support for
84         // sdk sandbox is added.
85         mWasRoot = getDevice().isAdbRoot();
86         getDevice().enableAdbRoot();
87         uninstallPackage(TEST_APP_STORAGE_PACKAGE);
88         mOriginalUserId = getDevice().getCurrentUser();
89         setSystemProperty(SYS_PROP_DEFAULT_CERT_DIGEST, getPackageCertDigest(CODE_PROVIDER_APK));
90     }
91 
92     @After
tearDown()93     public void tearDown() throws Exception {
94         removeSecondaryUserIfNecessary();
95         uninstallPackage(TEST_APP_STORAGE_PACKAGE);
96         setSystemProperty(SYS_PROP_DEFAULT_CERT_DIGEST, "invalid");
97         if (!mWasRoot) {
98             getDevice().disableAdbRoot();
99         }
100     }
101 
102     @Test
testSelinuxLabel()103     public void testSelinuxLabel() throws Exception {
104         installPackage(TEST_APP_STORAGE_APK);
105 
106         assertSelinuxLabel("/data/misc_ce/0/sdksandbox", "sdk_sandbox_system_data_file");
107         assertSelinuxLabel("/data/misc_de/0/sdksandbox", "sdk_sandbox_system_data_file");
108 
109         // Check label of /data/misc_{ce,de}/0/sdksandbox/<package-name>
110         assertSelinuxLabel(getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true),
111                 "sdk_sandbox_system_data_file");
112         assertSelinuxLabel(getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false),
113                 "sdk_sandbox_system_data_file");
114         // Check label of /data/misc_{ce,de}/0/sdksandbox/<app-name>/shared
115         assertSelinuxLabel(getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, true),
116                 "sdk_sandbox_data_file");
117         assertSelinuxLabel(getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, false),
118                 "sdk_sandbox_data_file");
119         // Check label of /data/misc_{ce,de}/0/sdksandbox/<app-name>/<sdk-package>
120         assertSelinuxLabel(getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_PACKAGE, true),
121                 "sdk_sandbox_data_file");
122         assertSelinuxLabel(getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_PACKAGE, false),
123                 "sdk_sandbox_data_file");
124     }
125 
126     /**
127      * Verify that {@code /data/misc_{ce,de}/<user-id>/sdksandbox} is created when
128      * {@code <user-id>} is created.
129      */
130     @Test
testSdkSandboxDataRootDirectory_IsCreatedOnUserCreate()131     public void testSdkSandboxDataRootDirectory_IsCreatedOnUserCreate() throws Exception {
132         {
133             // Verify root directory exists for primary user
134             final String cePath = getSdkDataRootPath(0, true);
135             final String dePath = getSdkDataRootPath(0, false);
136             assertThat(getDevice().isDirectory(dePath)).isTrue();
137             assertThat(getDevice().isDirectory(cePath)).isTrue();
138         }
139 
140         {
141             // Verify root directory is created for new user
142             mSecondaryUserId = createAndStartSecondaryUser();
143             final String cePath = getSdkDataRootPath(mSecondaryUserId, true);
144             final String dePath = getSdkDataRootPath(mSecondaryUserId, false);
145             assertThat(getDevice().isDirectory(dePath)).isTrue();
146             assertThat(getDevice().isDirectory(cePath)).isTrue();
147         }
148     }
149 
150     /**
151      * Verify that {@code /data/misc_{ce,de}/<user-id>/sdksandbox} is not accessible by apps
152      */
153     @Test
testSdkSandboxDataRootDirectory_IsNotAccessibleByApps()154     public void testSdkSandboxDataRootDirectory_IsNotAccessibleByApps() throws Exception {
155         // Install the app
156         installPackage(TEST_APP_STORAGE_APK);
157 
158         // Verify root directory exists for primary user
159         final String cePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true);
160         final String dePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false);
161         assertThat(getDevice().isDirectory(dePath)).isTrue();
162         assertThat(getDevice().isDirectory(cePath)).isTrue();
163 
164         runPhase("testSdkSandboxDataRootDirectory_IsNotAccessibleByApps");
165     }
166 
167     @Test
testSdkSandboxDataAppDirectory_IsCreatedOnInstall()168     public void testSdkSandboxDataAppDirectory_IsCreatedOnInstall() throws Exception {
169         // Directory should not exist before install
170         final String cePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true);
171         final String dePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false);
172         assertThat(getDevice().isDirectory(cePath)).isFalse();
173         assertThat(getDevice().isDirectory(dePath)).isFalse();
174 
175         // Install the app
176         installPackage(TEST_APP_STORAGE_APK);
177 
178         // Verify directory is created
179         assertThat(getDevice().isDirectory(cePath)).isTrue();
180         assertThat(getDevice().isDirectory(dePath)).isTrue();
181     }
182 
183     @Test
testSdkSandboxDataAppDirectory_IsNotCreatedWithoutSdkConsumption()184     public void testSdkSandboxDataAppDirectory_IsNotCreatedWithoutSdkConsumption()
185             throws Exception {
186         // Install the an app that does not consume sdk
187         installPackage(TEST_APP_STORAGE_V2_NO_SDK);
188 
189         // Verify directories are not created
190         final String cePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true);
191         final String dePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false);
192         assertThat(getDevice().isDirectory(cePath)).isFalse();
193         assertThat(getDevice().isDirectory(dePath)).isFalse();
194     }
195 
196     @Test
testSdkSandboxDataAppDirectory_IsDestroyedOnUninstall()197     public void testSdkSandboxDataAppDirectory_IsDestroyedOnUninstall() throws Exception {
198         // Install the app
199         installPackage(TEST_APP_STORAGE_APK);
200 
201         //Uninstall the app
202         uninstallPackage(TEST_APP_STORAGE_PACKAGE);
203 
204         // Directory should not exist after uninstall
205         final String cePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true);
206         final String dePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false);
207         // Verify directory is destoyed
208         assertThat(getDevice().isDirectory(cePath)).isFalse();
209         assertThat(getDevice().isDirectory(dePath)).isFalse();
210     }
211 
212     @Test
testSdkSandboxDataAppDirectory_IsClearedOnClearAppData()213     public void testSdkSandboxDataAppDirectory_IsClearedOnClearAppData() throws Exception {
214         // Install the app
215         installPackage(TEST_APP_STORAGE_APK);
216         {
217             // Verify directory is not clear
218             final String ceDataSharedPath =
219                     getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, true);
220             final String[] ceChildren = getDevice().getChildren(ceDataSharedPath);
221             {
222                 final String fileToDelete = ceDataSharedPath + "/deleteme.txt";
223                 getDevice().executeShellCommand("echo something to delete > " + fileToDelete);
224                 assertThat(getDevice().doesFileExist(fileToDelete)).isTrue();
225             }
226             assertThat(ceChildren.length).isNotEqualTo(0);
227             final String deDataSharedPath =
228                     getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, false);
229             final String[] deChildren = getDevice().getChildren(deDataSharedPath);
230             {
231                 final String fileToDelete = deDataSharedPath + "/deleteme.txt";
232                 getDevice().executeShellCommand("echo something to delete > " + fileToDelete);
233                 assertThat(getDevice().doesFileExist(fileToDelete)).isTrue();
234             }
235             assertThat(deChildren.length).isNotEqualTo(0);
236         }
237 
238         // Clear the app data
239         getDevice().executeShellCommand("pm clear " + TEST_APP_STORAGE_PACKAGE);
240         {
241             // Verify directory is cleared
242             final String ceDataSharedPath =
243                     getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, true);
244             final String[] ceChildren = getDevice().getChildren(ceDataSharedPath);
245             assertThat(ceChildren.length).isEqualTo(0);
246             final String deDataSharedPath =
247                     getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, false);
248             final String[] deChildren = getDevice().getChildren(deDataSharedPath);
249             assertThat(deChildren.length).isEqualTo(0);
250         }
251     }
252     // TODO(b/221946754): Need to write tests for clearing cache and clearing code cache
253 
254     @Test
testSdkSandboxDataAppDirectory_IsDestroyedOnUserDeletion()255     public void testSdkSandboxDataAppDirectory_IsDestroyedOnUserDeletion() throws Exception {
256         // Create new user
257         mSecondaryUserId = createAndStartSecondaryUser();
258 
259         // Install the app
260         installPackage(TEST_APP_STORAGE_APK);
261 
262         // delete the new user
263         removeSecondaryUserIfNecessary();
264 
265         // Sdk Sandbox root directories should not exist as the user was removed
266         final String ceSdkSandboxDataRootPath = getSdkDataRootPath(mSecondaryUserId, true);
267         final String deSdkSandboxDataRootPath = getSdkDataRootPath(mSecondaryUserId, false);
268         assertThat(getDevice().isDirectory(ceSdkSandboxDataRootPath)).isFalse();
269         assertThat(getDevice().isDirectory(deSdkSandboxDataRootPath)).isFalse();
270     }
271 
272     @Test
testSdkSandboxDataAppDirectory_IsUserSpecific()273     public void testSdkSandboxDataAppDirectory_IsUserSpecific() throws Exception {
274         // Install first before creating the user
275         installPackage(TEST_APP_STORAGE_APK, "--user all");
276 
277         mSecondaryUserId = createAndStartSecondaryUser();
278 
279         // Data directories should not exist as the package is not installed on new user
280         final String ceAppPath = getAppDataPath(mSecondaryUserId, TEST_APP_STORAGE_PACKAGE, true);
281         final String deAppPath = getAppDataPath(mSecondaryUserId, TEST_APP_STORAGE_PACKAGE, false);
282         final String cePath = getSdkDataPackagePath(mSecondaryUserId,
283                 TEST_APP_STORAGE_PACKAGE, true);
284         final String dePath = getSdkDataPackagePath(mSecondaryUserId,
285                 TEST_APP_STORAGE_PACKAGE, false);
286 
287         assertThat(getDevice().isDirectory(ceAppPath)).isFalse();
288         assertThat(getDevice().isDirectory(deAppPath)).isFalse();
289         assertThat(getDevice().isDirectory(cePath)).isFalse();
290         assertThat(getDevice().isDirectory(dePath)).isFalse();
291 
292         // Install the app on new user
293         installPackage(TEST_APP_STORAGE_APK);
294 
295         assertThat(getDevice().isDirectory(cePath)).isTrue();
296         assertThat(getDevice().isDirectory(dePath)).isTrue();
297     }
298 
299     @Test
testSdkDataPackageDirectory_SharedStorageIsUsable()300     public void testSdkDataPackageDirectory_SharedStorageIsUsable() throws Exception {
301         installPackage(TEST_APP_STORAGE_APK);
302 
303         // Verify that shared storage exist
304         final String sharedCePath = getSdkDataSharedPath(0, TEST_APP_STORAGE_PACKAGE, true);
305         assertThat(getDevice().isDirectory(sharedCePath)).isTrue();
306 
307         // Write a file in the shared storage that code needs to read and write it back
308         // in another file
309         String fileToRead = sharedCePath + "/readme.txt";
310         getDevice().executeShellCommand("echo something to read > " + fileToRead);
311         assertThat(getDevice().doesFileExist(fileToRead)).isTrue();
312 
313         runPhase("testSdkSandboxDataAppDirectory_SharedStorageIsUsable");
314 
315         // Assert that code was able to create file and directories
316         assertThat(getDevice().isDirectory(sharedCePath + "/dir")).isTrue();
317         assertThat(getDevice().doesFileExist(sharedCePath + "/dir/file")).isTrue();
318         String content = getDevice().executeShellCommand("cat " + sharedCePath + "/dir/file");
319         assertThat(content).isEqualTo("something to read");
320     }
321 
322     @Test
testSdkDataPackageDirectory_OnUpdateDoesNotConsumeSdk()323     public void testSdkDataPackageDirectory_OnUpdateDoesNotConsumeSdk() throws Exception {
324         installPackage(TEST_APP_STORAGE_APK);
325 
326         final String cePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true);
327         final String dePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false);
328         assertThat(getDevice().isDirectory(cePath)).isTrue();
329         assertThat(getDevice().isDirectory(dePath)).isTrue();
330 
331         // Update app so that it no longer consumes any sdk
332         installPackage(TEST_APP_STORAGE_V2_NO_SDK);
333         assertThat(getDevice().isDirectory(cePath)).isFalse();
334         assertThat(getDevice().isDirectory(dePath)).isFalse();
335     }
336 
337     @Test
testSdkDataPerSdkDirectory_IsCreatedOnInstall()338     public void testSdkDataPerSdkDirectory_IsCreatedOnInstall() throws Exception {
339         // Directory should not exist before install
340         assertThat(getSdkDataPerSdkPath(
341                     0, TEST_APP_STORAGE_PACKAGE, SDK_PACKAGE, true)).isNull();
342         assertThat(getSdkDataPerSdkPath(
343                     0, TEST_APP_STORAGE_PACKAGE, SDK_PACKAGE, false)).isNull();
344 
345         // Install the app
346         installPackage(TEST_APP_STORAGE_APK);
347 
348         // Verify directory is created
349         assertThat(getSdkDataPerSdkPath(
350                     0, TEST_APP_STORAGE_PACKAGE, SDK_PACKAGE, true)).isNotNull();
351         assertThat(getSdkDataPerSdkPath(
352                     0, TEST_APP_STORAGE_PACKAGE, SDK_PACKAGE, false)).isNotNull();
353     }
354 
355     @Test
testSdkData_CanBeMovedToDifferentVolume()356     public void testSdkData_CanBeMovedToDifferentVolume() throws Exception {
357         assumeTrue(isAdoptableStorageSupported());
358 
359         installPackage(TEST_APP_STORAGE_APK);
360 
361         // Create a new adoptable storage where we will be moving our installed package
362         final String diskId = getAdoptionDisk();
363         try {
364             assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
365             final LocalVolumeInfo vol = getAdoptionVolume();
366 
367             assertSuccess(getDevice().executeShellCommand(
368                     "pm move-package " + TEST_APP_STORAGE_PACKAGE + " " + vol.uuid));
369 
370             // Verify that sdk data is moved
371             for (int i = 0; i < 2; i++) {
372                 boolean isCeData = (i == 0) ? true : false;
373                 final String sdkDataRootPath = "/mnt/expand/" + vol.uuid
374                         + (isCeData ? "/misc_ce" : "/misc_de") +  "/0/sdksandbox";
375                 final String sdkDataPackagePath = sdkDataRootPath + "/" + TEST_APP_STORAGE_PACKAGE;
376                 final String sdkDataSharedPath = sdkDataPackagePath + "/" + "shared";
377 
378                 assertThat(getDevice().isDirectory(sdkDataRootPath)).isTrue();
379                 assertThat(getDevice().isDirectory(sdkDataPackagePath)).isTrue();
380                 assertThat(getDevice().isDirectory(sdkDataSharedPath)).isTrue();
381 
382                 assertSelinuxLabel(sdkDataRootPath, "system_data_file");
383                 assertSelinuxLabel(sdkDataPackagePath, "system_data_file");
384                 assertSelinuxLabel(sdkDataSharedPath, "sdk_sandbox_data_file");
385             }
386         } finally {
387             getDevice().executeShellCommand("sm partition " + diskId + " public");
388             getDevice().executeShellCommand("sm forget all");
389         }
390 
391     }
392 
393     @Test
394     @Ignore("b/224763009")
testSdkDataIsAttributedToApp()395     public void testSdkDataIsAttributedToApp() throws Exception {
396         installPackage(TEST_APP_STORAGE_APK);
397         runPhase("testSdkDataIsAttributedToApp");
398     }
399 
getAppDataPath(int userId, String packageName, boolean isCeData)400     private String getAppDataPath(int userId, String packageName, boolean isCeData) {
401         if (isCeData) {
402             return String.format("/data/user/%d/%s", userId, packageName);
403         } else {
404             return String.format("/data/user_de/%d/%s", userId, packageName);
405         }
406     }
407 
getSdkDataRootPath(int userId, boolean isCeData)408     private String getSdkDataRootPath(int userId, boolean isCeData) {
409         if (isCeData) {
410             return String.format("/data/misc_ce/%d/sdksandbox", userId);
411         } else {
412             return String.format("/data/misc_de/%d/sdksandbox", userId);
413         }
414     }
415 
getSdkDataPackagePath(int userId, String packageName, boolean isCeData)416     private String getSdkDataPackagePath(int userId, String packageName, boolean isCeData) {
417         return String.format(
418             "%s/%s", getSdkDataRootPath(userId, isCeData), packageName);
419     }
420 
getSdkDataSharedPath(int userId, String packageName, boolean isCeData)421     private String getSdkDataSharedPath(int userId, String packageName,
422             boolean isCeData) {
423         return String.format(
424             "%s/shared", getSdkDataPackagePath(userId, packageName, isCeData));
425     }
426 
427     // Per-Sdk directory has random suffix. So we need to iterate over the app-level directory
428     // to find it.
429     @Nullable
getSdkDataPerSdkPath(int userId, String packageName, String sdkName, boolean isCeData)430     private String getSdkDataPerSdkPath(int userId, String packageName, String sdkName,
431             boolean isCeData) throws Exception {
432         final String appLevelPath = getSdkDataPackagePath(userId, packageName, isCeData);
433         final String[] children = getDevice().getChildren(appLevelPath);
434         String result = null;
435         for (String child : children) {
436             String[] tokens = child.split("@");
437             if (tokens.length != 2) {
438                 continue;
439             }
440             String sdkNameFound = tokens[0];
441             if (sdkName.equals(sdkNameFound)) {
442                 if (result == null) {
443                     result = appLevelPath + "/" + child;
444                 } else {
445                     throw new IllegalStateException("Found two per-sdk directory for " + sdkName);
446                 }
447             }
448         }
449         return result;
450     }
451 
assertSelinuxLabel(@ullable String path, String label)452     private void assertSelinuxLabel(@Nullable String path, String label) throws Exception {
453         assertThat(path).isNotNull();
454         final String output = getDevice().executeShellCommand("ls -ldZ " + path);
455         assertThat(output).contains("u:object_r:" + label);
456     }
457 
createAndStartSecondaryUser()458     private int createAndStartSecondaryUser() throws Exception {
459         String name = "SdkSandboxStorageHostTest_User" + System.currentTimeMillis();
460         int newId = getDevice().createUser(name);
461         getDevice().startUser(newId);
462         // Note we can't install apps on a locked user
463         awaitUserUnlocked(newId);
464         return newId;
465     }
466 
awaitUserUnlocked(int userId)467     private void awaitUserUnlocked(int userId) throws Exception {
468         for (int i = 0; i < SWITCH_USER_COMPLETED_NUMBER_OF_POLLS; ++i) {
469             String userState = getDevice().executeShellCommand("am get-started-user-state "
470                     + userId);
471             if (userState.contains("RUNNING_UNLOCKED")) {
472                 return;
473             }
474             Thread.sleep(SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS);
475         }
476         fail("Timed out in unlocking user: " + userId);
477     }
478 
removeSecondaryUserIfNecessary()479     private void removeSecondaryUserIfNecessary() throws Exception {
480         if (mSecondaryUserId != -1) {
481             // Can't remove the 2nd user without switching out of it
482             assertThat(getDevice().switchUser(mOriginalUserId)).isTrue();
483             getDevice().removeUser(mSecondaryUserId);
484             mSecondaryUserId = -1;
485         }
486     }
487 
488     /**
489      * Extracts the certificate used to sign an apk in HexEncoded form.
490      */
getPackageCertDigest(String apkFileName)491     private String getPackageCertDigest(String apkFileName) throws Exception {
492         File apkFile = mHostUtils.getTestFile(apkFileName);
493         JarFile apkJar = new JarFile(apkFile);
494         JarEntry manifestEntry = apkJar.getJarEntry("AndroidManifest.xml");
495         // #getCertificate can only be called once the JarEntry has been completely
496         // verified by reading from the entry input stream until the end of the
497         // stream has been reached.
498         byte[] readBuffer = new byte[8192];
499         InputStream input = new BufferedInputStream(apkJar.getInputStream(manifestEntry));
500         while (input.read(readBuffer, 0, readBuffer.length) != -1) {
501             // not used
502         }
503         // We can now call #getCertificates
504         Certificate[] certs = manifestEntry.getCertificates();
505 
506         // Create SHA256 digest of the certificate
507         MessageDigest sha256DigestCreator = MessageDigest.getInstance("SHA-256");
508         sha256DigestCreator.update(certs[0].getEncoded());
509         byte[] digest = sha256DigestCreator.digest();
510         return new String(encodeToHex(digest)).trim();
511     }
512 
513     /**
514      * Encodes the provided data as a sequence of hexadecimal characters.
515      */
encodeToHex(byte[] data)516     private static char[] encodeToHex(byte[] data) {
517         final char[] digits = {
518             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
519         };
520         char[] result = new char[data.length * 2];
521         for (int i = 0; i < data.length; i++) {
522             byte b = data[i];
523             int resultIndex = 2 * i;
524             result[resultIndex] = (digits[(b >> 4) & 0x0f]);
525             result[resultIndex + 1] = (digits[b & 0x0f]);
526         }
527 
528         return result;
529     }
530 
setSystemProperty(String name, String value)531     private void setSystemProperty(String name, String value) throws Exception {
532         assertThat(getDevice().executeShellCommand(
533               "setprop " + name + " " + value)).isEqualTo("");
534     }
535 
isAdoptableStorageSupported()536     private boolean isAdoptableStorageSupported() throws Exception {
537         boolean hasFeature = getDevice().hasFeature("feature:android.software.adoptable_storage");
538         boolean hasFstab = Boolean.parseBoolean(getDevice().executeShellCommand(
539                     "sm has-adoptable").trim());
540         return hasFeature && hasFstab;
541 
542     }
543 
getAdoptionDisk()544     private String getAdoptionDisk() throws Exception {
545         // In the case where we run multiple test we cleanup the state of the device. This
546         // results in the execution of sm forget all which causes the MountService to "reset"
547         // all its knowledge about available drives. This can cause the adoptable drive to
548         // become temporarily unavailable.
549         int attempt = 0;
550         String disks = getDevice().executeShellCommand("sm list-disks adoptable");
551         while ((disks == null || disks.isEmpty()) && attempt++ < 15) {
552             Thread.sleep(1000);
553             disks = getDevice().executeShellCommand("sm list-disks adoptable");
554         }
555 
556         if (disks == null || disks.isEmpty()) {
557             throw new AssertionError("Devices that claim to support adoptable storage must have "
558                     + "adoptable media inserted during CTS to verify correct behavior");
559         }
560         return disks.split("\n")[0].trim();
561     }
562 
assertSuccess(String str)563     private static void assertSuccess(String str) {
564         if (str == null || !str.startsWith("Success")) {
565             throw new AssertionError("Expected success string but found " + str);
566         }
567     }
568 
assertEmpty(String str)569     private static void assertEmpty(String str) {
570         if (str != null && str.trim().length() > 0) {
571             throw new AssertionError("Expected empty string but found " + str);
572         }
573     }
574 
575     private static class LocalVolumeInfo {
576         public String volId;
577         public String state;
578         public String uuid;
579 
LocalVolumeInfo(String line)580         LocalVolumeInfo(String line) {
581             final String[] split = line.split(" ");
582             volId = split[0];
583             state = split[1];
584             uuid = split[2];
585         }
586     }
587 
getAdoptionVolume()588     private LocalVolumeInfo getAdoptionVolume() throws Exception {
589         String[] lines = null;
590         int attempt = 0;
591         int mounted_count = 0;
592         while (attempt++ < 15) {
593             lines = getDevice().executeShellCommand("sm list-volumes private").split("\n");
594             CLog.w("getAdoptionVolume(): " + Arrays.toString(lines));
595             for (String line : lines) {
596                 final LocalVolumeInfo info = new LocalVolumeInfo(line.trim());
597                 if (!"private".equals(info.volId)) {
598                     if ("mounted".equals(info.state)) {
599                         // make sure the storage is mounted and stable for a while
600                         mounted_count++;
601                         attempt--;
602                         if (mounted_count >= 3) {
603                             return waitForVolumeReady(info);
604                         }
605                     } else {
606                         mounted_count = 0;
607                     }
608                 }
609             }
610             Thread.sleep(1000);
611         }
612         throw new AssertionError("Expected private volume; found " + Arrays.toString(lines));
613     }
614 
waitForVolumeReady(LocalVolumeInfo vol)615     private LocalVolumeInfo waitForVolumeReady(LocalVolumeInfo vol) throws Exception {
616         int attempt = 0;
617         while (attempt++ < 15) {
618             if (getDevice().executeShellCommand("dumpsys package volumes").contains(vol.volId)) {
619                 return vol;
620             }
621             Thread.sleep(1000);
622         }
623         throw new AssertionError("Volume not ready " + vol.volId);
624     }
625 }
626