1 /* 2 * Copyright (C) 2011 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.os.storage.cts; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.mockito.Mockito.mock; 22 import static org.mockito.Mockito.when; 23 import static org.testng.Assert.assertThrows; 24 25 import static java.util.stream.Collectors.joining; 26 27 import android.app.PendingIntent; 28 import android.content.res.Resources; 29 import android.content.res.Resources.NotFoundException; 30 import android.os.Environment; 31 import android.os.Handler; 32 import android.os.Looper; 33 import android.os.Parcel; 34 import android.os.ParcelFileDescriptor; 35 import android.os.Process; 36 import android.os.ProxyFileDescriptorCallback; 37 import android.os.UserHandle; 38 import android.os.cts.R; 39 import android.os.storage.OnObbStateChangeListener; 40 import android.os.storage.StorageManager; 41 import android.os.storage.StorageManager.StorageVolumeCallback; 42 import android.os.storage.StorageVolume; 43 import android.platform.test.annotations.AppModeFull; 44 import android.provider.DeviceConfig; 45 import android.system.ErrnoException; 46 import android.system.Os; 47 import android.system.OsConstants; 48 import android.test.AndroidTestCase; 49 import android.test.ComparisonFailure; 50 import android.util.Log; 51 52 import androidx.test.platform.app.InstrumentationRegistry; 53 54 import com.android.compatibility.common.util.FileUtils; 55 import com.android.compatibility.common.util.SystemUtil; 56 57 import junit.framework.AssertionFailedError; 58 59 import java.io.ByteArrayOutputStream; 60 import java.io.File; 61 import java.io.FileDescriptor; 62 import java.io.FileInputStream; 63 import java.io.IOException; 64 import java.io.InputStream; 65 import java.io.InterruptedIOException; 66 import java.io.SyncFailedException; 67 import java.lang.reflect.Field; 68 import java.lang.reflect.Modifier; 69 import java.util.ArrayList; 70 import java.util.List; 71 import java.util.Optional; 72 import java.util.Set; 73 import java.util.UUID; 74 import java.util.concurrent.CountDownLatch; 75 import java.util.concurrent.SynchronousQueue; 76 import java.util.concurrent.TimeUnit; 77 import java.util.stream.Collectors; 78 79 public class StorageManagerTest extends AndroidTestCase { 80 81 private static final String TAG = StorageManager.class.getSimpleName(); 82 83 private static final long MAX_WAIT_TIME = 25*1000; 84 private static final long WAIT_TIME_INCR = 5*1000; 85 86 private static final String OBB_MOUNT_PREFIX = "/mnt/obb/"; 87 private static final String TEST1_NEW_CONTENTS = "1\n"; 88 89 private StorageManager mStorageManager; 90 private final Handler mHandler = new Handler(Looper.getMainLooper()); 91 92 @Override setUp()93 protected void setUp() throws Exception { 94 super.setUp(); 95 mStorageManager = mContext.getSystemService(StorageManager.class); 96 } 97 98 @AppModeFull(reason = "Instant apps cannot access external storage") testMountAndUnmountObbNormal()99 public void testMountAndUnmountObbNormal() throws IOException { 100 for (File target : getTargetFiles()) { 101 target = new File(target, "test1_new.obb"); 102 Log.d(TAG, "Testing path " + target); 103 doMountAndUnmountObbNormal(target); 104 } 105 } 106 doMountAndUnmountObbNormal(File outFile)107 private void doMountAndUnmountObbNormal(File outFile) throws IOException { 108 final String canonPath = mountObb(R.raw.test1_new, outFile, OnObbStateChangeListener.MOUNTED); 109 110 mountObb(R.raw.test1_new, outFile, OnObbStateChangeListener.ERROR_ALREADY_MOUNTED); 111 112 try { 113 final String mountPath = checkMountedPath(canonPath); 114 final File mountDir = new File(mountPath); 115 final File testFile = new File(mountDir, "test1.txt"); 116 117 assertTrue("OBB mounted path should be a directory", mountDir.isDirectory()); 118 assertTrue("test1.txt does not exist in OBB dir", testFile.exists()); 119 assertFileContains(testFile, TEST1_NEW_CONTENTS); 120 } finally { 121 unmountObb(outFile, OnObbStateChangeListener.UNMOUNTED); 122 } 123 } 124 125 @AppModeFull(reason = "Instant apps cannot access external storage") testAttemptMountNonObb()126 public void testAttemptMountNonObb() { 127 for (File target : getTargetFiles()) { 128 target = new File(target, "test1_nosig.obb"); 129 Log.d(TAG, "Testing path " + target); 130 doAttemptMountNonObb(target); 131 } 132 } 133 doAttemptMountNonObb(File outFile)134 private void doAttemptMountNonObb(File outFile) { 135 try { 136 mountObb(R.raw.test1_nosig, outFile, OnObbStateChangeListener.ERROR_INTERNAL); 137 fail("mountObb should've failed with an exception"); 138 } catch (IllegalArgumentException e) { 139 // Expected 140 } 141 142 assertFalse("OBB should not be mounted", 143 mStorageManager.isObbMounted(outFile.getPath())); 144 145 assertNull("OBB's mounted path should be null", 146 mStorageManager.getMountedObbPath(outFile.getPath())); 147 } 148 149 @AppModeFull(reason = "Instant apps cannot access external storage") testAttemptMountObbWrongPackage()150 public void testAttemptMountObbWrongPackage() { 151 for (File target : getTargetFiles()) { 152 target = new File(target, "test1_wrongpackage.obb"); 153 Log.d(TAG, "Testing path " + target); 154 doAttemptMountObbWrongPackage(target); 155 } 156 } 157 doAttemptMountObbWrongPackage(File outFile)158 private void doAttemptMountObbWrongPackage(File outFile) { 159 mountObb(R.raw.test1_wrongpackage, outFile, 160 OnObbStateChangeListener.ERROR_PERMISSION_DENIED); 161 162 assertFalse("OBB should not be mounted", 163 mStorageManager.isObbMounted(outFile.getPath())); 164 165 assertNull("OBB's mounted path should be null", 166 mStorageManager.getMountedObbPath(outFile.getPath())); 167 } 168 169 @AppModeFull(reason = "Instant apps cannot access external storage") testMountAndUnmountTwoObbs()170 public void testMountAndUnmountTwoObbs() throws IOException { 171 for (File target : getTargetFiles()) { 172 Log.d(TAG, "Testing target " + target); 173 final File test1 = new File(target, "test1.obb"); 174 final File test2 = new File(target, "test2.obb"); 175 doMountAndUnmountTwoObbs(test1, test2); 176 } 177 } 178 doMountAndUnmountTwoObbs(File file1, File file2)179 private void doMountAndUnmountTwoObbs(File file1, File file2) throws IOException { 180 ObbObserver oo1 = mountObbWithoutWait(R.raw.test1_new, file1); 181 ObbObserver oo2 = mountObbWithoutWait(R.raw.test1_new, file2); 182 183 Log.d(TAG, "Waiting for OBB #1 to complete mount"); 184 waitForObbActionCompletion(file1, oo1, OnObbStateChangeListener.MOUNTED); 185 Log.d(TAG, "Waiting for OBB #2 to complete mount"); 186 waitForObbActionCompletion(file2, oo2, OnObbStateChangeListener.MOUNTED); 187 188 try { 189 final String mountPath1 = checkMountedPath(oo1.getPath()); 190 final File mountDir1 = new File(mountPath1); 191 final File testFile1 = new File(mountDir1, "test1.txt"); 192 assertTrue("OBB mounted path should be a directory", mountDir1.isDirectory()); 193 assertTrue("test1.txt does not exist in OBB dir", testFile1.exists()); 194 assertFileContains(testFile1, TEST1_NEW_CONTENTS); 195 196 final String mountPath2 = checkMountedPath(oo2.getPath()); 197 final File mountDir2 = new File(mountPath2); 198 final File testFile2 = new File(mountDir2, "test1.txt"); 199 assertTrue("OBB mounted path should be a directory", mountDir2.isDirectory()); 200 assertTrue("test1.txt does not exist in OBB dir", testFile2.exists()); 201 assertFileContains(testFile2, TEST1_NEW_CONTENTS); 202 } finally { 203 unmountObb(file1, OnObbStateChangeListener.UNMOUNTED); 204 unmountObb(file2, OnObbStateChangeListener.UNMOUNTED); 205 } 206 } 207 testGetPrimaryVolume()208 public void testGetPrimaryVolume() throws Exception { 209 final StorageVolume volume = mStorageManager.getPrimaryStorageVolume(); 210 assertNotNull("Did not get primary storage", volume); 211 212 // Tests some basic Scoped Directory Access requests. 213 assertNull("Should not grant access for root directory", volume.createAccessIntent(null)); 214 assertNull("Should not grant access for invalid directory", 215 volume.createAccessIntent("/system")); 216 assertNotNull("Should grant access for valid directory " + Environment.DIRECTORY_DOCUMENTS, 217 volume.createAccessIntent(Environment.DIRECTORY_DOCUMENTS)); 218 219 // Tests basic properties. 220 assertNotNull("Should have description", volume.getDescription(mContext)); 221 assertTrue("Should be primary", volume.isPrimary()); 222 assertEquals("Wrong state", Environment.MEDIA_MOUNTED, volume.getState()); 223 224 // Tests properties that depend on storage type (emulated or physical) 225 final String fsUuid = volume.getUuid(); 226 final UUID uuid = volume.getStorageUuid(); 227 final boolean removable = volume.isRemovable(); 228 final boolean emulated = volume.isEmulated(); 229 if (emulated) { 230 assertFalse("Should not be externally managed", volume.isExternallyManaged()); 231 assertFalse("Should not be removable", removable); 232 assertNull("Should not have fsUuid", fsUuid); 233 assertEquals("Should have uuid_default", StorageManager.UUID_DEFAULT, uuid); 234 } else { 235 assertTrue("Should be removable", removable); 236 assertNotNull("Should have fsUuid", fsUuid); 237 assertNull("Should not have uuid", uuid); 238 } 239 240 // Tests path - although it's not a public API, sm.getPrimaryStorageVolume() 241 // explicitly states it should match Environment#getExternalStorageDirectory 242 final String path = volume.getPath(); 243 assertEquals("Path does not match Environment's", 244 Environment.getExternalStorageDirectory().getAbsolutePath(), path); 245 246 // Tests Parcelable contract. 247 assertEquals("Wrong describeContents", 0, volume.describeContents()); 248 final Parcel parcel = Parcel.obtain(); 249 try { 250 volume.writeToParcel(parcel, 0); 251 parcel.setDataPosition(0); 252 final StorageVolume clone = StorageVolume.CREATOR.createFromParcel(parcel); 253 assertStorageVolumesEquals(volume, clone); 254 } finally { 255 parcel.recycle(); 256 } 257 } 258 259 @AppModeFull(reason = "Instant apps cannot access external storage") testGetStorageVolumes()260 public void testGetStorageVolumes() throws Exception { 261 final List<StorageVolume> volumes = mStorageManager.getStorageVolumes(); 262 assertFalse("No volume return", volumes.isEmpty()); 263 StorageVolume primary = null; 264 for (StorageVolume volume : volumes) { 265 if (volume.isPrimary()) { 266 assertNull("There can be only one primary volume: " + volumes, primary); 267 primary = volume; 268 } 269 } 270 assertNotNull("No primary volume on " + volumes, primary); 271 assertStorageVolumesEquals(primary, mStorageManager.getPrimaryStorageVolume()); 272 } 273 274 @AppModeFull(reason = "Instant apps cannot access external storage") testGetRecentStorageVolumes()275 public void testGetRecentStorageVolumes() throws Exception { 276 // At a minimum recent volumes should include current volumes 277 final Set<String> currentNames = mStorageManager.getStorageVolumes().stream() 278 .map((v) -> v.getMediaStoreVolumeName()).collect(Collectors.toSet()); 279 final Set<String> recentNames = mStorageManager.getRecentStorageVolumes().stream() 280 .map((v) -> v.getMediaStoreVolumeName()).collect(Collectors.toSet()); 281 assertTrue(recentNames.containsAll(currentNames)); 282 } 283 284 @AppModeFull(reason = "Instant apps cannot access external storage") testGetStorageVolume()285 public void testGetStorageVolume() throws Exception { 286 assertNull("Should not get volume for null path", 287 mStorageManager.getStorageVolume((File) null)); 288 assertNull("Should not get volume for invalid path", 289 mStorageManager.getStorageVolume(new File("/system"))); 290 assertNull("Should not get volume for storage directory", 291 mStorageManager.getStorageVolume(Environment.getStorageDirectory())); 292 293 final File root = Environment.getExternalStorageDirectory(); 294 Log.d("StorageManagerTest", "root: " + root); 295 final StorageVolume primary = mStorageManager.getPrimaryStorageVolume(); 296 final StorageVolume rootVolume = mStorageManager.getStorageVolume(root); 297 assertNotNull("No volume for root (" + root + ")", rootVolume); 298 assertStorageVolumesEquals(primary, rootVolume); 299 300 final File child = new File(root, "child"); 301 StorageVolume childVolume = mStorageManager.getStorageVolume(child); 302 assertNotNull("No volume for child (" + child + ")", childVolume); 303 Log.d("StorageManagerTest", "child: " + childVolume.getPath()); 304 assertStorageVolumesEquals(primary, childVolume); 305 } 306 307 @AppModeFull(reason = "Instant apps cannot access external storage") testGetStorageVolumeUSB()308 public void testGetStorageVolumeUSB() throws Exception { 309 String volumeName = StorageManagerHelper.createUSBVirtualDisk(); 310 Log.d(TAG, "testGetStorageVolumeUSB#volumeName: " + volumeName); 311 List<StorageVolume> storageVolumes = mStorageManager.getStorageVolumes(); 312 Optional<StorageVolume> usbStorageVolume = 313 storageVolumes.stream().filter(sv-> 314 sv != null && sv.getPath() != null && sv.getPath().contains(volumeName) 315 ).findFirst(); 316 assertTrue("The USB storage volume mounted on the main user is not present in " 317 + storageVolumes.stream().map(StorageVolume::getPath) 318 .collect(joining("\n")), usbStorageVolume.isPresent()); 319 } 320 321 @AppModeFull(reason = "Instant apps cannot access external storage") testGetStorageVolumeSDCard()322 public void testGetStorageVolumeSDCard() throws Exception { 323 String volumeName = StorageManagerHelper.createSDCardVirtualDisk(); 324 Log.d(TAG, "testGetStorageVolumeSDCard#volumeName: " + volumeName); 325 List<StorageVolume> storageVolumes = mStorageManager.getStorageVolumes(); 326 Optional<StorageVolume> sdCardStorageVolume = 327 storageVolumes.stream().filter(sv-> 328 sv != null && sv.getPath() != null && sv.getPath().contains(volumeName) 329 ).findFirst(); 330 assertTrue("The SdCard storage volume mounted on the main user is not present in " 331 + storageVolumes.stream().map(StorageVolume::getPath) 332 .collect(joining("\n")), sdCardStorageVolume.isPresent()); 333 } 334 assertNoUuid(File file)335 private void assertNoUuid(File file) { 336 try { 337 final UUID uuid = mStorageManager.getUuidForPath(file); 338 fail("Unexpected UUID " + uuid + " for " + file); 339 } catch (IOException expected) { 340 } 341 } 342 testGetUuidForPath()343 public void testGetUuidForPath() throws Exception { 344 assertEquals(StorageManager.UUID_DEFAULT, 345 mStorageManager.getUuidForPath(Environment.getDataDirectory())); 346 assertEquals(StorageManager.UUID_DEFAULT, 347 mStorageManager.getUuidForPath(mContext.getDataDir())); 348 349 assertNoUuid(new File("/")); 350 assertNoUuid(new File("/proc/")); 351 } 352 353 @AppModeFull(reason = "Instant apps cannot access external storage") testGetExternalUuidForPath()354 public void testGetExternalUuidForPath() throws Exception { 355 final UUID extUuid = mStorageManager 356 .getUuidForPath(Environment.getExternalStorageDirectory()); 357 if (Environment.isExternalStorageEmulated()) { 358 assertEquals(StorageManager.UUID_DEFAULT, extUuid); 359 } 360 361 assertEquals(extUuid, mStorageManager.getUuidForPath(mContext.getExternalCacheDir())); 362 assertEquals(extUuid, mStorageManager.getUuidForPath(new File("/sdcard/"))); 363 } 364 365 @AppModeFull(reason = "Instant apps cannot access external storage") testCallback()366 public void testCallback() throws Exception { 367 final CountDownLatch mounted = new CountDownLatch(1); 368 final CountDownLatch unmounted = new CountDownLatch(1); 369 final StorageVolumeCallback callback = new StorageVolumeCallback() { 370 @Override 371 public void onStateChanged(StorageVolume volume) { 372 switch (volume.getState()) { 373 case Environment.MEDIA_MOUNTED: mounted.countDown(); break; 374 case Environment.MEDIA_UNMOUNTED: unmounted.countDown(); break; 375 } 376 } 377 }; 378 379 // Unmount storage data and obb dirs before test so test process won't be killed. 380 InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( 381 "sm unmount-app-data-dirs " + mContext.getPackageName() + " " 382 + Process.myPid() + " " + UserHandle.myUserId()); 383 384 // Unmount primary storage, verify we can see it take effect 385 mStorageManager.registerStorageVolumeCallback(mContext.getMainExecutor(), callback); 386 InstrumentationRegistry.getInstrumentation().getUiAutomation() 387 .executeShellCommand("sm unmount emulated;" + UserHandle.myUserId()); 388 assertTrue(unmounted.await(30, TimeUnit.SECONDS)); 389 390 // Now unregister and verify we don't hear future events 391 mStorageManager.unregisterStorageVolumeCallback(callback); 392 InstrumentationRegistry.getInstrumentation().getUiAutomation() 393 .executeShellCommand("sm mount emulated;" + UserHandle.myUserId()); 394 assertFalse(mounted.await(15, TimeUnit.SECONDS)); 395 } 396 397 private static class TestProxyFileDescriptorCallback extends ProxyFileDescriptorCallback { 398 final byte[] bytes; 399 int fsyncCount; 400 int releaseCount; 401 ErrnoException onGetSizeError = null; 402 ErrnoException onReadError = null; 403 ErrnoException onWriteError = null; 404 ErrnoException onFsyncError = null; 405 TestProxyFileDescriptorCallback(int size, String seed)406 TestProxyFileDescriptorCallback(int size, String seed) { 407 final byte[] seedBytes = seed.getBytes(); 408 bytes = new byte[size]; 409 for (int i = 0; i < size; i++) { 410 bytes[i] = seedBytes[i % seedBytes.length]; 411 } 412 } 413 414 @Override onWrite(long offset, int size, byte[] data)415 public int onWrite(long offset, int size, byte[] data) throws ErrnoException { 416 if (onWriteError != null) { 417 throw onWriteError; 418 } 419 for (int i = 0; i < size; i++) { 420 bytes[(int)(i + offset)] = data[i]; 421 } 422 return size; 423 } 424 425 @Override onRead(long offset, int size, byte[] data)426 public int onRead(long offset, int size, byte[] data) throws ErrnoException { 427 if (onReadError != null) { 428 throw onReadError; 429 } 430 final int len = (int)(Math.min(size, bytes.length - offset)); 431 for (int i = 0; i < len; i++) { 432 data[i] = bytes[(int)(i + offset)]; 433 } 434 return len; 435 } 436 437 @Override onGetSize()438 public long onGetSize() throws ErrnoException { 439 if (onGetSizeError != null) { 440 throw onGetSizeError; 441 } 442 return bytes.length; 443 } 444 445 @Override onFsync()446 public void onFsync() throws ErrnoException { 447 if (onFsyncError != null) { 448 throw onFsyncError; 449 } 450 fsyncCount++; 451 } 452 453 @Override onRelease()454 public void onRelease() { 455 releaseCount++; 456 } 457 } 458 testOpenProxyFileDescriptor()459 public void testOpenProxyFileDescriptor() throws Exception { 460 final TestProxyFileDescriptorCallback appleCallback = 461 new TestProxyFileDescriptorCallback(1024 * 1024, "Apple"); 462 final TestProxyFileDescriptorCallback orangeCallback = 463 new TestProxyFileDescriptorCallback(1024 * 128, "Orange"); 464 final TestProxyFileDescriptorCallback cherryCallback = 465 new TestProxyFileDescriptorCallback(1024 * 1024, "Cherry"); 466 try (final ParcelFileDescriptor appleFd = mStorageManager.openProxyFileDescriptor( 467 ParcelFileDescriptor.MODE_READ_ONLY, appleCallback, mHandler); 468 final ParcelFileDescriptor orangeFd = mStorageManager.openProxyFileDescriptor( 469 ParcelFileDescriptor.MODE_WRITE_ONLY, orangeCallback, mHandler); 470 final ParcelFileDescriptor cherryFd = mStorageManager.openProxyFileDescriptor( 471 ParcelFileDescriptor.MODE_READ_WRITE, cherryCallback, mHandler)) { 472 // Stat 473 assertEquals(appleCallback.onGetSize(), appleFd.getStatSize()); 474 assertEquals(orangeCallback.onGetSize(), orangeFd.getStatSize()); 475 assertEquals(cherryCallback.onGetSize(), cherryFd.getStatSize()); 476 477 final byte[] bytes = new byte[100]; 478 479 // Read 480 for (int i = 0; i < 2; i++) { 481 Os.read(appleFd.getFileDescriptor(), bytes, 0, 100); 482 for (int j = 0; j < 100; j++) { 483 assertEquals(appleCallback.bytes[i * 100 + j], bytes[j]); 484 } 485 } 486 try { 487 Os.read(orangeFd.getFileDescriptor(), bytes, 0, 100); 488 fail(); 489 } catch (ErrnoException exp) { 490 assertEquals(OsConstants.EBADF, exp.errno); 491 } 492 for (int i = 0; i < 2; i++) { 493 Os.read(cherryFd.getFileDescriptor(), bytes, 0, 100); 494 for (int j = 0; j < 100; j++) { 495 assertEquals(cherryCallback.bytes[i * 100 + j], bytes[j]); 496 } 497 } 498 499 // Pread 500 Os.pread(appleFd.getFileDescriptor(), bytes, 0, 100, 500); 501 for (int j = 0; j < 100; j++) { 502 assertEquals(appleCallback.bytes[500 + j], bytes[j]); 503 } 504 try { 505 Os.pread(orangeFd.getFileDescriptor(), bytes, 0, 100, 500); 506 fail(); 507 } catch (ErrnoException exp) { 508 assertEquals(OsConstants.EBADF, exp.errno); 509 } 510 Os.pread(cherryFd.getFileDescriptor(), bytes, 0, 100, 500); 511 for (int j = 0; j < 100; j++) { 512 assertEquals(cherryCallback.bytes[500 + j], bytes[j]); 513 } 514 515 // Write 516 final byte[] writeSeed = "Strawberry".getBytes(); 517 for (int i = 0; i < bytes.length; i++) { 518 bytes[i] = writeSeed[i % writeSeed.length]; 519 } 520 try { 521 Os.write(appleFd.getFileDescriptor(), bytes, 0, 100); 522 fail(); 523 } catch (ErrnoException exp) { 524 assertEquals(OsConstants.EBADF, exp.errno); 525 } 526 for (int i = 0; i < 2; i++) { 527 Os.write(orangeFd.getFileDescriptor(), bytes, 0, 100); 528 for (int j = 0; j < 100; j++) { 529 assertEquals(bytes[j], orangeCallback.bytes[i * 100 + j]); 530 } 531 } 532 Os.lseek(cherryFd.getFileDescriptor(), 0, OsConstants.SEEK_SET); 533 for (int i = 0; i < 2; i++) { 534 Os.write(cherryFd.getFileDescriptor(), bytes, 0, 100); 535 for (int j = 0; j < 100; j++) { 536 assertEquals(bytes[j], cherryCallback.bytes[i * 100 + j]); 537 } 538 } 539 540 // Pwrite 541 try { 542 Os.pwrite(appleFd.getFileDescriptor(), bytes, 0, 100, 500); 543 fail(); 544 } catch (ErrnoException exp) { 545 assertEquals(OsConstants.EBADF, exp.errno); 546 } 547 Os.pwrite(orangeFd.getFileDescriptor(), bytes, 0, 100, 500); 548 for (int j = 0; j < 100; j++) { 549 assertEquals(bytes[j], orangeCallback.bytes[500 + j]); 550 } 551 Os.pwrite(cherryFd.getFileDescriptor(), bytes, 0, 100, 500); 552 for (int j = 0; j < 100; j++) { 553 assertEquals(bytes[j], cherryCallback.bytes[500 + j]); 554 } 555 556 // Flush 557 assertEquals(0, appleCallback.fsyncCount); 558 assertEquals(0, orangeCallback.fsyncCount); 559 assertEquals(0, cherryCallback.fsyncCount); 560 appleFd.getFileDescriptor().sync(); 561 orangeFd.getFileDescriptor().sync(); 562 cherryFd.getFileDescriptor().sync(); 563 assertEquals(1, appleCallback.fsyncCount); 564 assertEquals(1, orangeCallback.fsyncCount); 565 assertEquals(1, cherryCallback.fsyncCount); 566 567 // Before release 568 assertEquals(0, appleCallback.releaseCount); 569 assertEquals(0, orangeCallback.releaseCount); 570 assertEquals(0, cherryCallback.releaseCount); 571 } 572 573 // Release 574 int retry = 3; 575 while (true) { 576 try { 577 assertEquals(1, appleCallback.releaseCount); 578 assertEquals(1, orangeCallback.releaseCount); 579 assertEquals(1, cherryCallback.releaseCount); 580 break; 581 } catch (AssertionFailedError error) { 582 if (retry-- > 0) { 583 Thread.sleep(500); 584 continue; 585 } else { 586 throw error; 587 } 588 } 589 } 590 } 591 testOpenProxyFileDescriptor_error()592 public void testOpenProxyFileDescriptor_error() throws Exception { 593 final TestProxyFileDescriptorCallback callback = 594 new TestProxyFileDescriptorCallback(1024 * 1024, "Error"); 595 final byte[] bytes = new byte[128]; 596 callback.onGetSizeError = new ErrnoException("onGetSize", OsConstants.ENOENT); 597 try { 598 try (final ParcelFileDescriptor fd = mStorageManager.openProxyFileDescriptor( 599 ParcelFileDescriptor.MODE_READ_WRITE, callback, mHandler)) {} 600 fail(); 601 } catch (IOException exp) {} 602 callback.onGetSizeError = null; 603 604 try (final ParcelFileDescriptor fd = mStorageManager.openProxyFileDescriptor( 605 ParcelFileDescriptor.MODE_READ_WRITE, callback, mHandler)) { 606 callback.onReadError = new ErrnoException("onRead", OsConstants.EIO); 607 try { 608 Os.read(fd.getFileDescriptor(), bytes, 0, bytes.length); 609 fail(); 610 } catch (ErrnoException error) {} 611 612 callback.onReadError = new ErrnoException("onRead", OsConstants.ENOSYS); 613 try { 614 Os.read(fd.getFileDescriptor(), bytes, 0, bytes.length); 615 fail(); 616 } catch (ErrnoException error) {} 617 618 callback.onReadError = null; 619 Os.read(fd.getFileDescriptor(), bytes, 0, bytes.length); 620 621 callback.onWriteError = new ErrnoException("onWrite", OsConstants.EIO); 622 try { 623 Os.write(fd.getFileDescriptor(), bytes, 0, bytes.length); 624 fail(); 625 } catch (ErrnoException error) {} 626 627 callback.onWriteError = new ErrnoException("onWrite", OsConstants.ENOSYS); 628 try { 629 Os.write(fd.getFileDescriptor(), bytes, 0, bytes.length); 630 fail(); 631 } catch (ErrnoException error) {} 632 633 callback.onWriteError = null; 634 Os.write(fd.getFileDescriptor(), bytes, 0, bytes.length); 635 636 callback.onFsyncError = new ErrnoException("onFsync", OsConstants.EIO); 637 try { 638 fd.getFileDescriptor().sync(); 639 fail(); 640 } catch (SyncFailedException error) {} 641 642 callback.onFsyncError = new ErrnoException("onFsync", OsConstants.ENOSYS); 643 try { 644 fd.getFileDescriptor().sync(); 645 fail(); 646 } catch (SyncFailedException error) {} 647 648 callback.onFsyncError = null; 649 fd.getFileDescriptor().sync(); 650 } 651 } 652 testOpenProxyFileDescriptor_async()653 public void testOpenProxyFileDescriptor_async() throws Exception { 654 final CountDownLatch blockReadLatch = new CountDownLatch(1); 655 final CountDownLatch readBlockedLatch = new CountDownLatch(1); 656 final CountDownLatch releaseLatch = new CountDownLatch(2); 657 final TestProxyFileDescriptorCallback blockCallback = 658 new TestProxyFileDescriptorCallback(1024 * 1024, "Block") { 659 @Override 660 public int onRead(long offset, int size, byte[] data) throws ErrnoException { 661 try { 662 readBlockedLatch.countDown(); 663 blockReadLatch.await(); 664 } catch (InterruptedException e) { 665 fail(e.getMessage()); 666 } 667 return super.onRead(offset, size, data); 668 } 669 670 @Override 671 public void onRelease() { 672 releaseLatch.countDown(); 673 } 674 }; 675 final TestProxyFileDescriptorCallback asyncCallback = 676 new TestProxyFileDescriptorCallback(1024 * 1024, "Async") { 677 @Override 678 public void onRelease() { 679 releaseLatch.countDown(); 680 } 681 }; 682 final SynchronousQueue<Handler> handlerChannel = new SynchronousQueue<>(); 683 final Thread looperThread = new Thread(() -> { 684 Looper.prepare(); 685 try { 686 handlerChannel.put(new Handler()); 687 } catch (InterruptedException e) { 688 fail(e.getMessage()); 689 } 690 Looper.loop(); 691 }); 692 looperThread.start(); 693 694 final Handler handler = handlerChannel.take(); 695 696 try (final ParcelFileDescriptor blockFd = 697 mStorageManager.openProxyFileDescriptor( 698 ParcelFileDescriptor.MODE_READ_ONLY, blockCallback, mHandler); 699 final ParcelFileDescriptor asyncFd = 700 mStorageManager.openProxyFileDescriptor( 701 ParcelFileDescriptor.MODE_READ_ONLY, asyncCallback, handler)) { 702 final Thread readingThread = new Thread(() -> { 703 final byte[] bytes = new byte[128]; 704 try { 705 706 Os.read(blockFd.getFileDescriptor(), bytes, 0, 128); 707 } catch (ErrnoException | InterruptedIOException e) { 708 fail(e.getMessage()); 709 } 710 }); 711 readingThread.start(); 712 713 readBlockedLatch.countDown(); 714 assertEquals(Thread.State.RUNNABLE, readingThread.getState()); 715 716 final byte[] bytes = new byte[128]; 717 Log.d("StorageManagerTest", "start read async"); 718 assertEquals(128, Os.read(asyncFd.getFileDescriptor(), bytes, 0, 128)); 719 Log.d("StorageManagerTest", "stop read async"); 720 721 blockReadLatch.countDown(); 722 readingThread.join(); 723 } 724 725 releaseLatch.await(); 726 handler.getLooper().quit(); 727 looperThread.join(); 728 } 729 testOpenProxyFileDescriptor_largeFile()730 public void testOpenProxyFileDescriptor_largeFile() throws Exception { 731 final ProxyFileDescriptorCallback callback = new ProxyFileDescriptorCallback() { 732 @Override 733 public int onRead(long offset, int size, byte[] data) throws ErrnoException { 734 for (int i = 0; i < size; i++) { 735 data[i] = 'L'; 736 } 737 return size; 738 } 739 740 @Override 741 public long onGetSize() throws ErrnoException { 742 return 8L * 1024L * 1024L * 1024L; // 8GB 743 } 744 745 @Override 746 public void onRelease() {} 747 }; 748 final byte[] bytes = new byte[128]; 749 try (final ParcelFileDescriptor fd = mStorageManager.openProxyFileDescriptor( 750 ParcelFileDescriptor.MODE_READ_ONLY, callback, mHandler)) { 751 assertEquals(8L * 1024L * 1024L * 1024L, fd.getStatSize()); 752 753 final int readBytes = Os.pread( 754 fd.getFileDescriptor(), bytes, 0, bytes.length, fd.getStatSize() - 64L); 755 assertEquals(64, readBytes); 756 for (int i = 0; i < 64; i++) { 757 assertEquals('L', bytes[i]); 758 } 759 } 760 } 761 testOpenProxyFileDescriptor_largeRead()762 public void testOpenProxyFileDescriptor_largeRead() throws Exception { 763 final int SIZE = 1024 * 1024; 764 final TestProxyFileDescriptorCallback callback = 765 new TestProxyFileDescriptorCallback(SIZE, "abcdefghijklmnopqrstuvwxyz"); 766 final byte[] bytes = new byte[SIZE]; 767 try (final ParcelFileDescriptor fd = mStorageManager.openProxyFileDescriptor( 768 ParcelFileDescriptor.MODE_READ_ONLY, callback, mHandler)) { 769 final int readBytes = Os.read( 770 fd.getFileDescriptor(), bytes, 0, bytes.length); 771 assertEquals(bytes.length, readBytes); 772 for (int i = 0; i < bytes.length; i++) { 773 assertEquals(callback.bytes[i], bytes[i]); 774 } 775 } 776 } 777 testOpenProxyFileDescriptor_largeWrite()778 public void testOpenProxyFileDescriptor_largeWrite() throws Exception { 779 final int SIZE = 1024 * 1024; 780 final TestProxyFileDescriptorCallback callback = 781 new TestProxyFileDescriptorCallback(SIZE, "abcdefghijklmnopqrstuvwxyz"); 782 final byte[] bytes = new byte[SIZE]; 783 for (int i = 0; i < SIZE; i++) { 784 bytes[i] = (byte)(i % 123); 785 } 786 try (final ParcelFileDescriptor fd = mStorageManager.openProxyFileDescriptor( 787 ParcelFileDescriptor.MODE_WRITE_ONLY, callback, mHandler)) { 788 final int writtenBytes = Os.write( 789 fd.getFileDescriptor(), bytes, 0, bytes.length); 790 assertEquals(bytes.length, writtenBytes); 791 for (int i = 0; i < bytes.length; i++) { 792 assertEquals(bytes[i], callback.bytes[i]); 793 } 794 } 795 } 796 testIsAllocationSupported()797 public void testIsAllocationSupported() throws Exception { 798 FileDescriptor good = Os.open( 799 File.createTempFile("StorageManagerTest", "").getAbsolutePath(), 800 OsConstants.O_RDONLY, 0); 801 FileDescriptor bad = Os.open("/proc/self/cmdline", OsConstants.O_RDONLY, 0); 802 try { 803 assertTrue(mStorageManager.isAllocationSupported(good)); 804 assertFalse(mStorageManager.isAllocationSupported(bad)); 805 } finally { 806 try { 807 Os.close(good); 808 } catch (ErrnoException ignored) {} 809 810 try { 811 Os.close(bad); 812 } catch (ErrnoException ignored) {} 813 } 814 } 815 testFatUuidHandling()816 public void testFatUuidHandling() throws Exception { 817 assertEquals(UUID.fromString("fafafafa-fafa-5afa-8afa-fafa01234567"), 818 StorageManager.convert("0123-4567")); 819 assertEquals(UUID.fromString("fafafafa-fafa-5afa-8afa-fafadeadbeef"), 820 StorageManager.convert("DEAD-BEEF")); 821 assertEquals(UUID.fromString("fafafafa-fafa-5afa-8afa-fafadeadbeef"), 822 StorageManager.convert("dead-BEEF")); 823 824 try { 825 StorageManager.convert("DEADBEEF"); 826 fail(); 827 } catch (IllegalArgumentException expected) {} 828 829 try { 830 StorageManager.convert("DEAD-BEEF0"); 831 fail(); 832 } catch (IllegalArgumentException expected) {} 833 834 assertEquals("0123-4567", 835 StorageManager.convert(UUID.fromString("fafafafa-fafa-5afa-8afa-fafa01234567"))); 836 assertEquals("DEAD-BEEF", 837 StorageManager.convert(UUID.fromString("fafafafa-fafa-5afa-8afa-fafadeadbeef"))); 838 } 839 840 @AppModeFull(reason = "Instant apps cannot hold MANAGE_EXTERNAL_STORAGE permission") testGetManageSpaceActivityIntent()841 public void testGetManageSpaceActivityIntent() throws Exception { 842 String packageName = "android.os.cts"; 843 int REQUEST_CODE = 1; 844 PendingIntent piActual = null; 845 846 // Test should only pass with MANAGE_EXTERNAL_STORAGE permission 847 assertThat(Environment.isExternalStorageManager()).isTrue(); 848 849 // Invalid packageName should throw an IllegalArgumentException 850 String invalidPackageName = "this.is.invalid"; 851 assertThrows( 852 IllegalArgumentException.class, 853 () -> mStorageManager.getManageSpaceActivityIntent(invalidPackageName, 854 REQUEST_CODE)); 855 856 piActual = mStorageManager.getManageSpaceActivityIntent(packageName, 857 REQUEST_CODE); 858 assertThat(piActual.isActivity()).isTrue(); 859 860 // Nothing to assert, but call send to make sure it does not throw an exception 861 piActual.send(); 862 863 // Drop MANAGE_EXTERNAL_STORAGE permission 864 InstrumentationRegistry.getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 865 } 866 assertStorageVolumesEquals(StorageVolume volume, StorageVolume clone)867 private void assertStorageVolumesEquals(StorageVolume volume, StorageVolume clone) 868 throws Exception { 869 // Asserts equals() method. 870 assertEquals("StorageVolume.equals() mismatch", volume, clone); 871 // Make sure all fields match. 872 for (Field field : StorageVolume.class.getDeclaredFields()) { 873 if (Modifier.isStatic(field.getModifiers())) continue; 874 field.setAccessible(true); 875 final Object originalValue = field.get(volume); 876 final Object clonedValue = field.get(clone); 877 assertEquals("Mismatch for field " + field.getName(), originalValue, clonedValue); 878 } 879 } 880 assertStartsWith(String message, String prefix, String actual)881 private static void assertStartsWith(String message, String prefix, String actual) { 882 if (!actual.startsWith(prefix)) { 883 throw new ComparisonFailure(message, prefix, actual); 884 } 885 } 886 assertFileContains(File file, String contents)887 private static void assertFileContains(File file, String contents) throws IOException { 888 byte[] actual = readFully(new FileInputStream(file)); 889 byte[] expected = contents.getBytes("UTF-8"); 890 assertEquals("unexpected size", expected.length, actual.length); 891 for (int i = 0; i < expected.length; i++) { 892 assertEquals("unexpected value at offset " + i, expected[i], actual[i]); 893 } 894 } 895 896 private static class ObbObserver extends OnObbStateChangeListener { 897 private String path; 898 899 public int state = -1; 900 boolean done = false; 901 902 @Override onObbStateChange(String path, int state)903 public void onObbStateChange(String path, int state) { 904 Log.d(TAG, "Received message. path=" + path + ", state=" + state); 905 synchronized (this) { 906 this.path = path; 907 this.state = state; 908 done = true; 909 notifyAll(); 910 } 911 } 912 getPath()913 public String getPath() { 914 assertTrue("Expected ObbObserver to have received a state change.", done); 915 return path; 916 } 917 getState()918 public int getState() { 919 assertTrue("Expected ObbObserver to have received a state change.", done); 920 return state; 921 } 922 isDone()923 public boolean isDone() { 924 return done; 925 } 926 waitForCompletion()927 public boolean waitForCompletion() { 928 long waitTime = 0; 929 synchronized (this) { 930 while (!isDone() && waitTime < MAX_WAIT_TIME) { 931 try { 932 wait(WAIT_TIME_INCR); 933 waitTime += WAIT_TIME_INCR; 934 } catch (InterruptedException e) { 935 Log.i(TAG, "Interrupted during sleep", e); 936 } 937 } 938 } 939 940 return isDone(); 941 } 942 } 943 getTargetFiles()944 private List<File> getTargetFiles() { 945 final List<File> targets = new ArrayList<File>(); 946 for (File dir : mContext.getObbDirs()) { 947 assertNotNull("Valid media must be inserted during CTS", dir); 948 assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED, 949 Environment.getStorageState(dir)); 950 targets.add(dir); 951 } 952 return targets; 953 } 954 copyRawToFile(int rawResId, File outFile)955 private void copyRawToFile(int rawResId, File outFile) { 956 Resources res = mContext.getResources(); 957 InputStream is = null; 958 try { 959 is = res.openRawResource(rawResId); 960 } catch (NotFoundException e) { 961 fail("Failed to load resource with id: " + rawResId); 962 } 963 assertTrue(FileUtils.copyToFile(is, outFile)); 964 exposeFile(outFile); 965 } 966 exposeFile(File file)967 private File exposeFile(File file) { 968 file.setReadable(true, false); 969 file.setReadable(true, true); 970 971 File dir = file.getParentFile(); 972 do { 973 dir.setExecutable(true, false); 974 dir.setExecutable(true, true); 975 dir = dir.getParentFile(); 976 } while (dir != null); 977 978 return file; 979 } 980 mountObb(final int resource, final File file, int expectedState)981 private String mountObb(final int resource, final File file, int expectedState) { 982 copyRawToFile(resource, file); 983 984 final ObbObserver observer = new ObbObserver(); 985 assertTrue("mountObb call on " + file.getPath() + " should succeed", 986 mStorageManager.mountObb(file.getPath(), null, observer)); 987 988 assertTrue("Mount should have completed", 989 observer.waitForCompletion()); 990 991 if (expectedState == OnObbStateChangeListener.MOUNTED) { 992 assertTrue("OBB should be mounted", mStorageManager.isObbMounted(observer.getPath())); 993 } 994 995 assertEquals(expectedState, observer.getState()); 996 997 return observer.getPath(); 998 } 999 mountObbWithoutWait(final int resource, final File file)1000 private ObbObserver mountObbWithoutWait(final int resource, final File file) { 1001 copyRawToFile(resource, file); 1002 1003 final ObbObserver observer = new ObbObserver(); 1004 assertTrue("mountObb call on " + file.getPath() + " should succeed", 1005 mStorageManager.mountObb(file.getPath(), null, observer)); 1006 1007 return observer; 1008 } 1009 waitForObbActionCompletion(final File file, final ObbObserver observer, int expectedState)1010 private void waitForObbActionCompletion(final File file, final ObbObserver observer, 1011 int expectedState) { 1012 assertTrue("Mount should have completed", observer.waitForCompletion()); 1013 1014 assertTrue("OBB should be mounted", mStorageManager.isObbMounted(observer.getPath())); 1015 1016 assertEquals(expectedState, observer.getState()); 1017 } 1018 checkMountedPath(final String path)1019 private String checkMountedPath(final String path) { 1020 final String mountPath = mStorageManager.getMountedObbPath(path); 1021 assertStartsWith("Path should be in " + OBB_MOUNT_PREFIX, 1022 OBB_MOUNT_PREFIX, 1023 mountPath); 1024 return mountPath; 1025 } 1026 unmountObb(final File file, int expectedState)1027 private void unmountObb(final File file, int expectedState) { 1028 final ObbObserver observer = new ObbObserver(); 1029 1030 assertTrue("unmountObb call on test1_new.obb should succeed", 1031 mStorageManager.unmountObb(file.getPath(), false, observer)); 1032 1033 assertTrue("Unmount should have completed", 1034 observer.waitForCompletion()); 1035 1036 assertEquals(expectedState, observer.getState()); 1037 1038 if (expectedState == OnObbStateChangeListener.UNMOUNTED) { 1039 assertFalse("OBB should not be mounted", mStorageManager.isObbMounted(file.getPath())); 1040 } 1041 } 1042 testComputeStorageCacheBytes()1043 public void testComputeStorageCacheBytes() throws Exception { 1044 File mockFile = mock(File.class); 1045 1046 final int[] storageThresholdPercentHigh = new int[1]; 1047 SystemUtil.runWithShellPermissionIdentity(() -> { 1048 storageThresholdPercentHigh[0] = DeviceConfig.getInt( 1049 DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, 1050 StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH_KEY, 0); 1051 }); 1052 assertTrue("storageThresholdPercentHigh [" + storageThresholdPercentHigh[0] 1053 + "] expected to be greater than equal to 0", storageThresholdPercentHigh[0] >= 0); 1054 assertTrue("storageThresholdPercentHigh [" + storageThresholdPercentHigh[0] 1055 + "] expected to be lesser than equal to 100", 1056 storageThresholdPercentHigh[0] <= 100); 1057 1058 when(mockFile.getUsableSpace()).thenReturn(10000L); 1059 when(mockFile.getTotalSpace()).thenReturn(15000L); 1060 final long[] resultHigh = new long[1]; 1061 SystemUtil.runWithShellPermissionIdentity(() -> { 1062 resultHigh[0] = mStorageManager.computeStorageCacheBytes(mockFile); 1063 }); 1064 assertTrue("" + resultHigh[0] + " expected to be greater than equal to 0", 1065 resultHigh[0] >= 0L); 1066 assertTrue("" + resultHigh[0] + " expected to be less than equal to total space", 1067 resultHigh[0] <= mockFile.getTotalSpace()); 1068 1069 when(mockFile.getUsableSpace()).thenReturn(10000L); 1070 when(mockFile.getTotalSpace()).thenReturn(250000L); 1071 final long[] resultLow = new long[1]; 1072 SystemUtil.runWithShellPermissionIdentity(() -> { 1073 resultLow[0] = mStorageManager.computeStorageCacheBytes(mockFile); 1074 }); 1075 assertTrue("" + resultLow[0] + " expected to be greater than equal to 0", 1076 resultLow[0] >= 0L); 1077 assertTrue("" + resultLow[0] + " expected to be less than equal to total space", 1078 resultLow[0] <= mockFile.getTotalSpace()); 1079 1080 when(mockFile.getUsableSpace()).thenReturn(10000L); 1081 when(mockFile.getTotalSpace()).thenReturn(100000L); 1082 final long[] resultModerate = new long[1]; 1083 SystemUtil.runWithShellPermissionIdentity(() -> { 1084 resultModerate[0] = mStorageManager.computeStorageCacheBytes(mockFile); 1085 }); 1086 assertTrue("" + resultModerate[0] + " expected to be greater than equal to 0", 1087 resultModerate[0] >= 0L); 1088 assertTrue("" + resultModerate[0] + " expected to be less than equal to total space", 1089 resultModerate[0] <= mockFile.getTotalSpace()); 1090 } 1091 readFully(InputStream in)1092 public static byte[] readFully(InputStream in) throws IOException { 1093 // Shamelessly borrowed from libcore.io.Streams 1094 try { 1095 return readFullyNoClose(in); 1096 } finally { 1097 in.close(); 1098 } 1099 } 1100 readFullyNoClose(InputStream in)1101 public static byte[] readFullyNoClose(InputStream in) throws IOException { 1102 // Shamelessly borrowed from libcore.io.Streams 1103 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 1104 byte[] buffer = new byte[1024]; 1105 int count; 1106 while ((count = in.read(buffer)) != -1) { 1107 bytes.write(buffer, 0, count); 1108 } 1109 return bytes.toByteArray(); 1110 } 1111 } 1112