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