1 /* 2 * Copyright (C) 2018 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.cts.mediastorageapp; 18 19 import static android.provider.MediaStore.VOLUME_EXTERNAL; 20 import static android.scopedstorage.cts.lib.TestUtils.doEscalation; 21 22 import static com.android.compatibility.common.util.SystemUtil.runShellCommand; 23 24 import static com.google.common.truth.Truth.assertThat; 25 import static com.google.common.truth.Truth.assertWithMessage; 26 27 import static org.junit.Assert.assertEquals; 28 import static org.junit.Assert.assertFalse; 29 import static org.junit.Assert.assertNotNull; 30 import static org.junit.Assert.assertNull; 31 import static org.junit.Assert.assertTrue; 32 import static org.junit.Assert.fail; 33 34 import android.app.RecoverableSecurityException; 35 import android.content.ContentResolver; 36 import android.content.ContentUris; 37 import android.content.ContentValues; 38 import android.content.Context; 39 import android.content.Intent; 40 import android.database.Cursor; 41 import android.graphics.Bitmap; 42 import android.net.Uri; 43 import android.os.Environment; 44 import android.os.FileUtils; 45 import android.os.ParcelFileDescriptor; 46 import android.provider.MediaStore; 47 import android.provider.MediaStore.MediaColumns; 48 import android.support.test.uiautomator.UiDevice; 49 import android.support.test.uiautomator.UiObject; 50 import android.support.test.uiautomator.UiObjectNotFoundException; 51 import android.support.test.uiautomator.UiScrollable; 52 import android.support.test.uiautomator.UiSelector; 53 54 import androidx.test.InstrumentationRegistry; 55 import androidx.test.runner.AndroidJUnit4; 56 57 import com.android.cts.mediastorageapp.MediaStoreUtils.PendingParams; 58 import com.android.cts.mediastorageapp.MediaStoreUtils.PendingSession; 59 60 import com.google.common.io.ByteStreams; 61 62 import org.junit.Before; 63 import org.junit.Test; 64 import org.junit.runner.RunWith; 65 66 import java.io.File; 67 import java.io.FileInputStream; 68 import java.io.FileNotFoundException; 69 import java.io.FileOutputStream; 70 import java.io.IOException; 71 import java.io.InputStream; 72 import java.io.OutputStream; 73 import java.io.PrintWriter; 74 import java.nio.charset.StandardCharsets; 75 import java.util.Arrays; 76 import java.util.HashSet; 77 import java.util.concurrent.Callable; 78 import java.util.concurrent.TimeoutException; 79 80 @RunWith(AndroidJUnit4.class) 81 public class MediaStorageTest { 82 private static final File TEST_JPG = Environment.buildPath( 83 Environment.getExternalStorageDirectory(), 84 Environment.DIRECTORY_DOWNLOADS, "mediastoragetest_file1.jpg"); 85 private static final File TEST_PDF = Environment.buildPath( 86 Environment.getExternalStorageDirectory(), 87 Environment.DIRECTORY_DOWNLOADS, "mediastoragetest_file2.pdf"); 88 89 private Context mContext; 90 private ContentResolver mContentResolver; 91 private int mUserId; 92 93 private static int currentAttempt = 0; 94 private static final int MAX_NUMBER_OF_ATTEMPT = 10; 95 96 @Before setUp()97 public void setUp() throws Exception { 98 mContext = InstrumentationRegistry.getTargetContext(); 99 mContentResolver = mContext.getContentResolver(); 100 mUserId = mContext.getUserId(); 101 } 102 103 @Test testLegacy()104 public void testLegacy() throws Exception { 105 assertTrue(Environment.isExternalStorageLegacy()); 106 107 // We can always see mounted state 108 assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState()); 109 110 // We might have top-level access 111 final File probe = new File(Environment.getExternalStorageDirectory(), 112 "cts" + System.nanoTime()); 113 assertTrue(probe.createNewFile()); 114 assertNotNull(Environment.getExternalStorageDirectory().list()); 115 116 // We always have our package directories 117 final File probePackage = new File(mContext.getExternalFilesDir(null), 118 "cts" + System.nanoTime()); 119 assertTrue(probePackage.createNewFile()); 120 121 assertTrue(TEST_JPG.exists()); 122 assertTrue(TEST_PDF.exists()); 123 124 final Uri jpgUri = MediaStore.scanFile(mContentResolver, TEST_JPG); 125 final Uri pdfUri = MediaStore.scanFile(mContentResolver, TEST_PDF); 126 127 final HashSet<Long> seen = new HashSet<>(); 128 try (Cursor c = mContentResolver.query( 129 MediaStore.Files.getContentUri(VOLUME_EXTERNAL), 130 new String[]{MediaColumns._ID}, null, null)) { 131 while (c.moveToNext()) { 132 seen.add(c.getLong(0)); 133 } 134 } 135 136 assertTrue(seen.contains(ContentUris.parseId(jpgUri))); 137 assertTrue(seen.contains(ContentUris.parseId(pdfUri))); 138 } 139 140 @Test testStageFiles()141 public void testStageFiles() throws Exception { 142 final File jpg = stageFile(TEST_JPG); 143 assertTrue(jpg.exists()); 144 final File pdf = stageFile(TEST_PDF); 145 assertTrue(pdf.exists()); 146 } 147 148 @Test testClearFiles()149 public void testClearFiles() throws Exception { 150 TEST_JPG.delete(); 151 assertNull(MediaStore.scanFile(mContentResolver, TEST_JPG)); 152 TEST_PDF.delete(); 153 assertNull(MediaStore.scanFile(mContentResolver, TEST_PDF)); 154 } 155 156 @Test testMediaNone()157 public void testMediaNone() throws Exception { 158 doMediaNone(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MediaStorageTest::createAudio); 159 doMediaNone(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, MediaStorageTest::createVideo); 160 doMediaNone(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MediaStorageTest::createImage); 161 162 // But since we don't hold the Music permission, we can't read the 163 // indexed metadata 164 try (Cursor c = mContentResolver.query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, 165 null, null, null)) { 166 assertEquals(0, c.getCount()); 167 } 168 try (Cursor c = mContentResolver.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, 169 null, null, null)) { 170 assertEquals(0, c.getCount()); 171 } 172 try (Cursor c = mContentResolver.query(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, 173 null, null, null)) { 174 assertEquals(0, c.getCount()); 175 } 176 } 177 doMediaNone(Uri collection, Callable<Uri> create)178 private void doMediaNone(Uri collection, Callable<Uri> create) throws Exception { 179 final Uri red = create.call(); 180 final Uri blue = create.call(); 181 182 clearMediaOwner(blue, mUserId); 183 184 // Since we have no permissions, we should only be able to see media 185 // that we've contributed 186 final HashSet<Long> seen = new HashSet<>(); 187 try (Cursor c = mContentResolver.query(collection, 188 new String[]{MediaColumns._ID}, null, null)) { 189 while (c.moveToNext()) { 190 seen.add(c.getLong(0)); 191 } 192 } 193 194 assertTrue(seen.contains(ContentUris.parseId(red))); 195 assertFalse(seen.contains(ContentUris.parseId(blue))); 196 197 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "rw")) { 198 } 199 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "r")) { 200 fail("Expected read access to be blocked"); 201 } catch (SecurityException | FileNotFoundException expected) { 202 } 203 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "w")) { 204 fail("Expected write access to be blocked"); 205 } catch (SecurityException | FileNotFoundException expected) { 206 } 207 208 // Verify that we can't grant ourselves access 209 for (int flag : new int[]{ 210 Intent.FLAG_GRANT_READ_URI_PERMISSION, 211 Intent.FLAG_GRANT_WRITE_URI_PERMISSION 212 }) { 213 try { 214 mContext.grantUriPermission(mContext.getPackageName(), blue, flag); 215 fail("Expected granting to be blocked for flag 0x" + Integer.toHexString(flag)); 216 } catch (SecurityException expected) { 217 } 218 } 219 } 220 221 /** 222 * If the app grants read UriPermission to the uri without id (E.g. 223 * MediaStore.Audio.Media.EXTERNAL_CONTENT_URI), the query result of the uri should be the 224 * same without granting permission. 225 */ 226 @Test testReadUriPermissionOnUriWithoutId_sameQueryResult()227 public void testReadUriPermissionOnUriWithoutId_sameQueryResult() throws Exception { 228 // For Audio, Image, Video, If the app doesn't have delete access to the uri, 229 // MediaProvider throws SecurityException to give callers interacting with a specific media 230 // item a chance to escalate access if they don't already have it. Check SecurityException 231 // for them. 232 doReadUriPermissionOnUriWithoutId_sameQueryResult( 233 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 234 MediaStorageTest::createAudio, /* checkExceptionForDelete= */ true); 235 doReadUriPermissionOnUriWithoutId_sameQueryResult( 236 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, 237 MediaStorageTest::createVideo,/* checkExceptionForDelete= */ true); 238 doReadUriPermissionOnUriWithoutId_sameQueryResult( 239 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 240 MediaStorageTest::createImage,/* checkExceptionForDelete= */ true); 241 242 doReadUriPermissionOnUriWithoutId_sameQueryResult( 243 MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, 244 MediaStorageTest::createAudio, /* checkExceptionForDelete= */ true); 245 doReadUriPermissionOnUriWithoutId_sameQueryResult( 246 MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, 247 MediaStorageTest::createAudio, /* checkExceptionForDelete= */ true); 248 doReadUriPermissionOnUriWithoutId_sameQueryResult( 249 MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, 250 MediaStorageTest::createAudio, /* checkExceptionForDelete= */ true); 251 252 doReadUriPermissionOnUriWithoutId_sameQueryResult( 253 MediaStore.Downloads.EXTERNAL_CONTENT_URI, 254 MediaStorageTest::createDownload,/* checkExceptionForDelete= */ false); 255 doReadUriPermissionOnUriWithoutId_sameQueryResult( 256 MediaStore.Files.getContentUri(VOLUME_EXTERNAL), 257 MediaStorageTest::createFile,/* checkExceptionForDelete= */ false); 258 doReadUriPermissionOnUriWithoutId_sameQueryResult( 259 MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, 260 MediaStorageTest::createPlaylist,/* checkExceptionForDelete= */ false); 261 } 262 263 /** 264 * If the app grants read UriPermission to the uri without id (E.g. 265 * MediaStore.Audio.Media.EXTERNAL_CONTENT_URI), the query result of the uri should be the 266 * same without granting permission. 267 */ doReadUriPermissionOnUriWithoutId_sameQueryResult(Uri collectionUri, Callable<Uri> create, boolean checkExceptionForDelete)268 private void doReadUriPermissionOnUriWithoutId_sameQueryResult(Uri collectionUri, 269 Callable<Uri> create, boolean checkExceptionForDelete) throws Exception { 270 final int flagGrantRead = Intent.FLAG_GRANT_READ_URI_PERMISSION; 271 final Uri red = create.call(); 272 final Uri blue = create.call(); 273 clearMediaOwner(blue, mUserId); 274 final int originalCount; 275 276 try { 277 try (Cursor c = mContentResolver.query(collectionUri, new String[]{MediaColumns._ID}, 278 null, null)) { 279 originalCount = c.getCount(); 280 } 281 282 mContext.grantUriPermission(mContext.getPackageName(), collectionUri, flagGrantRead); 283 try (Cursor c = mContentResolver.query(collectionUri, new String[]{MediaColumns._ID}, 284 null, null)) { 285 assertWithMessage("After grant read UriPermission to " + collectionUri.toString() 286 + ", the item count of the query result").that(c.getCount()).isEqualTo( 287 originalCount); 288 } 289 290 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "rw")) { 291 } 292 293 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "r")) { 294 fail("Expected read access to " + blue.toString() + " be blocked"); 295 } catch (SecurityException | FileNotFoundException expected) { 296 } 297 298 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "w")) { 299 fail("Expected write access to " + blue.toString() + " be blocked"); 300 } catch (SecurityException | FileNotFoundException expected) { 301 } 302 303 // If checkExceptionForDelete is true, throws SecurityException is as we expected. 304 // Otherwise, the app doesn't have delete access to the file, the deleted count is 0. 305 if (checkExceptionForDelete) { 306 try { 307 mContentResolver.delete(blue, null); 308 fail("Expected delete access to " + blue.toString() + " be blocked"); 309 } catch (SecurityException expected) { 310 } 311 } else { 312 final int count = mContentResolver.delete(blue, null); 313 assertThat(count).isEqualTo(0); 314 } 315 } finally { 316 mContext.revokeUriPermission(mContext.getPackageName(), collectionUri, flagGrantRead); 317 } 318 } 319 320 /** 321 * b/197302116. The apps can't be granted prefix UriPermissions to the uri, when the query 322 * result of the uri is 1. 323 */ 324 @Test testOwningOneFileNotGrantPrefixUriPermission()325 public void testOwningOneFileNotGrantPrefixUriPermission() throws Exception { 326 doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 327 MediaStorageTest::createAudio); 328 doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, 329 MediaStorageTest::createVideo); 330 doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 331 MediaStorageTest::createImage); 332 doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Downloads.EXTERNAL_CONTENT_URI, 333 MediaStorageTest::createDownload); 334 doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Files.getContentUri(VOLUME_EXTERNAL), 335 MediaStorageTest::createFile); 336 doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, 337 MediaStorageTest::createPlaylist); 338 } 339 340 /** 341 * The apps can't be granted prefix UriPermissions to the uri without id, when the query result 342 * of the uri is 1. 343 */ doOwningOneFileNotGrantPrefixUriPermission(Uri collectionUri, Callable<Uri> create)344 private void doOwningOneFileNotGrantPrefixUriPermission(Uri collectionUri, Callable<Uri> create) 345 throws Exception { 346 347 clearOwnFiles(collectionUri); 348 349 final Uri red = create.call(); 350 final Uri blue = create.call(); 351 clearMediaOwner(blue, mUserId); 352 353 try (Cursor c = mContentResolver.query(collectionUri, new String[]{MediaColumns._ID}, null, 354 null)) { 355 assertThat(c.getCount()).isEqualTo(1); 356 c.moveToFirst(); 357 assertThat(c.getLong(0)).isEqualTo(ContentUris.parseId(red)); 358 } 359 360 final int flagGrantReadPrefix = 361 Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; 362 try { 363 mContext.grantUriPermission(mContext.getPackageName(), collectionUri, 364 flagGrantReadPrefix); 365 fail("Expected granting to " + collectionUri.toString() + " be blocked for flag 0x" 366 + Integer.toHexString(flagGrantReadPrefix)); 367 } catch (SecurityException expected) { 368 } 369 370 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "r")) { 371 } 372 373 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "r")) { 374 fail("Expected read access to " + blue.toString() + " be blocked"); 375 } catch (SecurityException | FileNotFoundException expected) { 376 } 377 378 final int flagGrantWritePrefix = Intent.FLAG_GRANT_WRITE_URI_PERMISSION 379 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; 380 try { 381 mContext.grantUriPermission(mContext.getPackageName(), collectionUri, 382 flagGrantWritePrefix); 383 fail("Expected granting to " + collectionUri.toString() + " be blocked for flag 0x" 384 + Integer.toHexString(flagGrantWritePrefix)); 385 } catch (SecurityException expected) { 386 } 387 388 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "rw")) { 389 } 390 391 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "w")) { 392 fail("Expected write access to " + blue.toString() + " be blocked"); 393 } catch (SecurityException | FileNotFoundException expected) { 394 } 395 } 396 397 @Test testGrantUriPermission()398 public void testGrantUriPermission() throws Exception { 399 doGrantUriPermission_nonPrefixAndPrefix(); 400 doGrantUriPermission_prefix(); 401 } 402 403 /** 404 * Test prefix and non-prefix uri grant for all packages 405 */ doGrantUriPermission_nonPrefixAndPrefix()406 private void doGrantUriPermission_nonPrefixAndPrefix() { 407 final int flagGrantRead = Intent.FLAG_GRANT_READ_URI_PERMISSION; 408 final int flagGrantWrite = Intent.FLAG_GRANT_WRITE_URI_PERMISSION; 409 final int flagGrantReadPrefix = 410 Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; 411 final int flagGrantWritePrefix = 412 Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; 413 414 for (Uri uri : new Uri[]{ 415 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 416 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, 417 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 418 MediaStore.Downloads.EXTERNAL_CONTENT_URI, 419 MediaStore.Files.getContentUri(VOLUME_EXTERNAL) 420 }) { 421 // Non-prefix grant 422 checkGrantUriPermission(uri, flagGrantRead, /* isGrantAllowed */ true); 423 checkGrantUriPermission(uri, flagGrantWrite, /* isGrantAllowed */ true); 424 425 // Prefix grant 426 checkGrantUriPermission(uri, flagGrantReadPrefix, /* isGrantAllowed */ false); 427 checkGrantUriPermission(uri, flagGrantWritePrefix, /* isGrantAllowed */ false); 428 429 // revoke granted permissions 430 mContext.revokeUriPermission(uri, flagGrantRead | flagGrantWrite); 431 } 432 } 433 434 /** 435 * b/194539422. Test prefix uri grant for all packages 436 */ doGrantUriPermission_prefix()437 private void doGrantUriPermission_prefix() { 438 final int flagGrantReadPrefix = 439 Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; 440 final int flagGrantWritePrefix = 441 Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; 442 443 for (Uri uri : new Uri[]{ 444 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 445 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, 446 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 447 MediaStore.Downloads.EXTERNAL_CONTENT_URI, 448 MediaStore.Files.getContentUri(VOLUME_EXTERNAL) 449 }) { 450 checkGrantUriPermission(uri, flagGrantReadPrefix, /* isGrantAllowed */ false); 451 checkGrantUriPermission(uri, flagGrantWritePrefix, /* isGrantAllowed */ false); 452 } 453 } 454 checkGrantUriPermission(Uri uri, int mode, boolean isGrantAllowed)455 private void checkGrantUriPermission(Uri uri, int mode, boolean isGrantAllowed) { 456 if (isGrantAllowed) { 457 mContext.grantUriPermission(mContext.getPackageName(), uri, mode); 458 } else { 459 try { 460 mContext.grantUriPermission(mContext.getPackageName(), uri, mode); 461 fail("Expected granting to " + uri.toString() + " be blocked for flag 0x" 462 + Integer.toHexString(mode)); 463 } catch (SecurityException expected) { 464 } 465 } 466 } 467 468 @Test testMediaRead()469 public void testMediaRead() throws Exception { 470 doMediaRead(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MediaStorageTest::createAudio); 471 doMediaRead(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, MediaStorageTest::createVideo); 472 doMediaRead(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MediaStorageTest::createImage); 473 } 474 doMediaRead(Uri collection, Callable<Uri> create)475 private void doMediaRead(Uri collection, Callable<Uri> create) throws Exception { 476 final Uri red = create.call(); 477 final Uri blue = create.call(); 478 479 clearMediaOwner(blue, mUserId); 480 481 // Holding read permission we can see items we don't own 482 final HashSet<Long> seen = new HashSet<>(); 483 try (Cursor c = mContentResolver.query(collection, 484 new String[]{MediaColumns._ID}, null, null)) { 485 while (c.moveToNext()) { 486 seen.add(c.getLong(0)); 487 } 488 } 489 490 assertTrue(seen.contains(ContentUris.parseId(red))); 491 assertTrue(seen.contains(ContentUris.parseId(blue))); 492 493 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "rw")) { 494 } 495 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "r")) { 496 } 497 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "w")) { 498 fail("Expected write access to be blocked"); 499 } catch (SecurityException | FileNotFoundException expected) { 500 } 501 } 502 503 @Test testMediaWrite()504 public void testMediaWrite() throws Exception { 505 doMediaWrite(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MediaStorageTest::createAudio); 506 doMediaWrite(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, MediaStorageTest::createVideo); 507 doMediaWrite(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MediaStorageTest::createImage); 508 } 509 doMediaWrite(Uri collection, Callable<Uri> create)510 private void doMediaWrite(Uri collection, Callable<Uri> create) throws Exception { 511 final Uri red = create.call(); 512 final Uri blue = create.call(); 513 514 clearMediaOwner(blue, mUserId); 515 516 // Holding read permission we can see items we don't own 517 final HashSet<Long> seen = new HashSet<>(); 518 try (Cursor c = mContentResolver.query(collection, 519 new String[]{MediaColumns._ID}, null, null)) { 520 while (c.moveToNext()) { 521 seen.add(c.getLong(0)); 522 } 523 } 524 525 assertTrue(seen.contains(ContentUris.parseId(red))); 526 assertTrue(seen.contains(ContentUris.parseId(blue))); 527 528 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "rw")) { 529 } 530 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "r")) { 531 } 532 if (Environment.isExternalStorageLegacy()) { 533 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "w")) { 534 } 535 } else { 536 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "w")) { 537 fail("Expected write access to be blocked"); 538 } catch (SecurityException | FileNotFoundException expected) { 539 } 540 } 541 } 542 543 @Test testMediaEscalation_Open()544 public void testMediaEscalation_Open() throws Exception { 545 doMediaEscalation_Open(MediaStorageTest::createAudio); 546 doMediaEscalation_Open(MediaStorageTest::createVideo); 547 doMediaEscalation_Open(MediaStorageTest::createImage); 548 } 549 doMediaEscalation_Open(Callable<Uri> create)550 private void doMediaEscalation_Open(Callable<Uri> create) throws Exception { 551 final Uri red = create.call(); 552 clearMediaOwner(red, mUserId); 553 554 RecoverableSecurityException exception = null; 555 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) { 556 fail("Expected write access to be blocked"); 557 } catch (RecoverableSecurityException expected) { 558 exception = expected; 559 } 560 561 doEscalation(exception); 562 563 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) { 564 } 565 } 566 567 @Test testMediaEscalation_Update()568 public void testMediaEscalation_Update() throws Exception { 569 doMediaEscalation_Update(MediaStorageTest::createAudio); 570 doMediaEscalation_Update(MediaStorageTest::createVideo); 571 doMediaEscalation_Update(MediaStorageTest::createImage); 572 } 573 doMediaEscalation_Update(Callable<Uri> create)574 private void doMediaEscalation_Update(Callable<Uri> create) throws Exception { 575 final Uri red = create.call(); 576 clearMediaOwner(red, mUserId); 577 578 final ContentValues values = new ContentValues(); 579 values.put(MediaColumns.DISPLAY_NAME, "cts" + System.nanoTime()); 580 581 RecoverableSecurityException exception = null; 582 try { 583 mContentResolver.update(red, values, null, null); 584 fail("Expected update access to be blocked"); 585 } catch (RecoverableSecurityException expected) { 586 exception = expected; 587 } 588 589 doEscalation(exception); 590 591 assertEquals(1, mContentResolver.update(red, values, null, null)); 592 } 593 594 @Test testMediaEscalation_Delete()595 public void testMediaEscalation_Delete() throws Exception { 596 doMediaEscalation_Delete(MediaStorageTest::createAudio); 597 doMediaEscalation_Delete(MediaStorageTest::createVideo); 598 doMediaEscalation_Delete(MediaStorageTest::createImage); 599 } 600 doMediaEscalation_Delete(Callable<Uri> create)601 private void doMediaEscalation_Delete(Callable<Uri> create) throws Exception { 602 final Uri red = create.call(); 603 clearMediaOwner(red, mUserId); 604 605 RecoverableSecurityException exception = null; 606 try { 607 mContentResolver.delete(red, null, null); 608 fail("Expected update access to be blocked"); 609 } catch (RecoverableSecurityException expected) { 610 exception = expected; 611 } 612 613 doEscalation(exception); 614 615 assertEquals(1, mContentResolver.delete(red, null, null)); 616 } 617 618 @Test testMediaEscalation_RequestWriteFilePathSupport()619 public void testMediaEscalation_RequestWriteFilePathSupport() throws Exception { 620 doMediaEscalation_RequestWrite_withFilePathSupport(MediaStorageTest::createAudio); 621 doMediaEscalation_RequestWrite_withFilePathSupport(MediaStorageTest::createVideo); 622 doMediaEscalation_RequestWrite_withFilePathSupport(MediaStorageTest::createImage); 623 doMediaEscalation_RequestWrite_withFilePathSupport(MediaStorageTest::createPlaylist); 624 doMediaEscalation_RequestWrite_withFilePathSupport(MediaStorageTest::createSubtitle); 625 } 626 doMediaEscalation_RequestWrite_withFilePathSupport( Callable<Uri> create)627 private void doMediaEscalation_RequestWrite_withFilePathSupport( 628 Callable<Uri> create) throws Exception { 629 final Uri red = create.call(); 630 assertNotNull(red); 631 String path = queryForSingleColumn(red, MediaColumns.DATA); 632 File file = new File(path); 633 assertThat(file.exists()).isTrue(); 634 assertThat(file.canRead()).isTrue(); 635 assertThat(file.canWrite()).isTrue(); 636 637 clearMediaOwner(red, mUserId); 638 assertThat(file.canWrite()).isFalse(); 639 640 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) { 641 fail("Expected write access to be blocked"); 642 } catch (SecurityException expected) { 643 } 644 645 doEscalation(MediaStore.createWriteRequest(mContentResolver, Arrays.asList(red))); 646 647 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) { 648 } 649 // Wait for MediaStore to be idle to avoid flakiness due to race conditions 650 MediaStore.waitForIdle(mContentResolver); 651 652 // Check File API support 653 assertAccessFileAPISupport(file); 654 assertReadWriteFileAPISupport(file); 655 assertRenameFileAPISupport(file); 656 assertRenameAndReplaceFileAPISupport(file, create); 657 assertDeleteFileAPISupport(file); 658 } 659 assertAccessFileAPISupport(File file)660 private void assertAccessFileAPISupport(File file) throws Exception { 661 assertThat(file.canRead()).isTrue(); 662 assertThat(file.canWrite()).isTrue(); 663 } 664 assertReadWriteFileAPISupport(File file)665 private void assertReadWriteFileAPISupport(File file) throws Exception { 666 final String str = "Just some random text"; 667 final byte[] bytes = str.getBytes(); 668 // Write to file 669 try (FileOutputStream fos = new FileOutputStream(file)) { 670 fos.write(bytes); 671 } 672 // Read the same data from file 673 try (FileInputStream fis = new FileInputStream(file)) { 674 assertThat(ByteStreams.toByteArray(fis)).isEqualTo(bytes); 675 } 676 } 677 assertRenameFileAPISupport(File oldFile)678 public void assertRenameFileAPISupport(File oldFile) throws Exception { 679 final String oldName = oldFile.getAbsolutePath(); 680 final String extension = oldName.substring(oldName.lastIndexOf('.')).trim(); 681 // Rename to same extension so test app does not lose access to file. 682 final String newRelativeName = "cts" + System.nanoTime() + extension; 683 final File newFile = Environment.buildPath( 684 Environment.getExternalStorageDirectory(), 685 Environment.DIRECTORY_DOWNLOADS, 686 newRelativeName); 687 final String newName = newFile.getAbsolutePath(); 688 assertWithMessage("Rename from oldName [%s] to newName [%s]", oldName, newName) 689 .that(oldFile.renameTo(newFile)) 690 .isTrue(); 691 // Rename back to oldFile for other ops like delete 692 assertWithMessage("Rename back from newName [%s] to oldName [%s]", newName, oldName) 693 .that(newFile.renameTo(oldFile)) 694 .isTrue(); 695 } 696 assertRenameAndReplaceFileAPISupport(File oldFile, Callable<Uri> create)697 public void assertRenameAndReplaceFileAPISupport(File oldFile, Callable<Uri> create) 698 throws Exception { 699 final String oldName = oldFile.getAbsolutePath(); 700 701 // Create new file to which we do not have any access. 702 final Uri newUri = create.call(); 703 assertWithMessage("Check newFile created").that(newUri).isNotNull(); 704 File newFile = new File(queryForSingleColumn(newUri, MediaColumns.DATA)); 705 final String newName = newFile.getAbsolutePath(); 706 clearMediaOwner(newUri, mUserId); 707 708 assertWithMessage( 709 "Rename should fail without newFile grant from oldName [%s] to newName [%s]", 710 oldName, newName) 711 .that(oldFile.renameTo(newFile)) 712 .isFalse(); 713 714 // Grant access to newFile and rename should succeed. 715 doEscalation( 716 MediaStore.createWriteRequest(mContentResolver, Arrays.asList(newUri))); 717 assertWithMessage("Rename from oldName [%s] to newName [%s]", oldName, newName) 718 .that(oldFile.renameTo(newFile)) 719 .isTrue(); 720 721 // We need to request grant on newUri again, since the rename above caused the URI grant 722 // to be revoked. 723 doEscalation( 724 MediaStore.createWriteRequest(mContentResolver, Arrays.asList(newUri))); 725 // Rename back to oldFile for other ops like delete 726 assertWithMessage("Rename back from newName [%s] to oldName [%s]", newName, oldName) 727 .that(newFile.renameTo(oldFile)) 728 .isTrue(); 729 } 730 assertDeleteFileAPISupport(File file)731 private void assertDeleteFileAPISupport(File file) throws Exception { 732 assertThat(file.delete()).isTrue(); 733 } 734 735 @Test testMediaEscalation_RequestWrite()736 public void testMediaEscalation_RequestWrite() throws Exception { 737 doMediaEscalation_RequestWrite(true /* allowAccess */, 738 false /* shouldCheckDialogShownValue */, false /* isDialogShownExpected */); 739 } 740 741 @Test testMediaEscalationWithDenied_RequestWrite()742 public void testMediaEscalationWithDenied_RequestWrite() throws Exception { 743 doMediaEscalation_RequestWrite(false /* allowAccess */, 744 false /* shouldCheckDialogShownValue */, false /* isDialogShownExpected */); 745 } 746 747 @Test testMediaEscalation_RequestWrite_showConfirmDialog()748 public void testMediaEscalation_RequestWrite_showConfirmDialog() throws Exception { 749 doMediaEscalation_RequestWrite(true /* allowAccess */, 750 true /* shouldCheckDialogShownValue */, true /* isDialogShownExpected */); 751 } 752 753 @Test testMediaEscalation_RequestWrite_notShowConfirmDialog()754 public void testMediaEscalation_RequestWrite_notShowConfirmDialog() throws Exception { 755 doMediaEscalation_RequestWrite(true /* allowAccess */, 756 true /* shouldCheckDialogShownValue */, false /* isDialogShownExpected */); 757 } 758 doMediaEscalation_RequestWrite(boolean allowAccess, boolean shouldCheckDialogShownValue, boolean isDialogShownExpected)759 private void doMediaEscalation_RequestWrite(boolean allowAccess, 760 boolean shouldCheckDialogShownValue, boolean isDialogShownExpected) throws Exception { 761 doMediaEscalation_RequestWrite(MediaStorageTest::createAudio, allowAccess, 762 shouldCheckDialogShownValue, isDialogShownExpected); 763 doMediaEscalation_RequestWrite(MediaStorageTest::createVideo, allowAccess, 764 shouldCheckDialogShownValue, isDialogShownExpected); 765 doMediaEscalation_RequestWrite(MediaStorageTest::createImage, allowAccess, 766 shouldCheckDialogShownValue, isDialogShownExpected); 767 doMediaEscalation_RequestWrite(MediaStorageTest::createPlaylist, allowAccess, 768 shouldCheckDialogShownValue, isDialogShownExpected); 769 doMediaEscalation_RequestWrite(MediaStorageTest::createSubtitle, allowAccess, 770 shouldCheckDialogShownValue, isDialogShownExpected); 771 } 772 doMediaEscalation_RequestWrite(Callable<Uri> create, boolean allowAccess, boolean shouldCheckDialogShownValue, boolean isDialogShownExpected)773 private void doMediaEscalation_RequestWrite(Callable<Uri> create, boolean allowAccess, 774 boolean shouldCheckDialogShownValue, boolean isDialogShownExpected) throws Exception { 775 final Uri red = create.call(); 776 clearMediaOwner(red, mUserId); 777 778 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) { 779 fail("Expected write access to be blocked"); 780 } catch (SecurityException expected) { 781 } 782 783 if (allowAccess) { 784 doEscalation( 785 MediaStore.createWriteRequest(mContentResolver, Arrays.asList(red)), 786 true /* allowAccess */, shouldCheckDialogShownValue, isDialogShownExpected); 787 788 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) { 789 } 790 } else { 791 doEscalation( 792 MediaStore.createWriteRequest(mContentResolver, Arrays.asList(red)), 793 false /* allowAccess */, shouldCheckDialogShownValue, isDialogShownExpected); 794 try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) { 795 fail("Expected write access to be blocked"); 796 } catch (SecurityException expected) { 797 } 798 } 799 } 800 801 @Test testMediaEscalationWithDenied_RequestUnTrash()802 public void testMediaEscalationWithDenied_RequestUnTrash() throws Exception { 803 doMediaEscalationWithDenied_RequestUnTrash(MediaStorageTest::createAudio); 804 doMediaEscalationWithDenied_RequestUnTrash(MediaStorageTest::createVideo); 805 doMediaEscalationWithDenied_RequestUnTrash(MediaStorageTest::createImage); 806 doMediaEscalationWithDenied_RequestUnTrash(MediaStorageTest::createPlaylist); 807 doMediaEscalationWithDenied_RequestUnTrash(MediaStorageTest::createSubtitle); 808 } 809 doMediaEscalationWithDenied_RequestUnTrash(Callable<Uri> create)810 private void doMediaEscalationWithDenied_RequestUnTrash(Callable<Uri> create) throws Exception { 811 final Uri red = create.call(); 812 clearMediaOwner(red, mUserId); 813 814 assertEquals("0", queryForSingleColumn(red, MediaColumns.IS_TRASHED)); 815 doEscalation( 816 MediaStore.createTrashRequest(mContentResolver, Arrays.asList(red), true)); 817 assertEquals("1", queryForSingleColumn(red, MediaColumns.IS_TRASHED)); 818 doEscalation( 819 MediaStore.createTrashRequest(mContentResolver, Arrays.asList(red), false), 820 false /* allowAccess */, false /* shouldCheckDialogShownValue */, 821 false /* isDialogShownExpected */); 822 assertEquals("1", queryForSingleColumn(red, MediaColumns.IS_TRASHED)); 823 } 824 825 @Test testMediaEscalation_RequestTrash()826 public void testMediaEscalation_RequestTrash() throws Exception { 827 doMediaEscalation_RequestTrash(true /* allowAccess */, 828 false /* shouldCheckDialogShownValue */, false /* isDialogShownExpected */); 829 } 830 831 @Test testMediaEscalationWithDenied_RequestTrash()832 public void testMediaEscalationWithDenied_RequestTrash() throws Exception { 833 doMediaEscalation_RequestTrash(false /* allowAccess */, 834 false /* shouldCheckDialogShownValue */, false /* isDialogShownExpected */); 835 } 836 837 @Test testMediaEscalation_RequestTrash_showConfirmDialog()838 public void testMediaEscalation_RequestTrash_showConfirmDialog() throws Exception { 839 doMediaEscalation_RequestTrash(true /* allowAccess */, 840 true /* shouldCheckDialogShownValue */, true /* isDialogShownExpected */); 841 } 842 843 @Test testMediaEscalation_RequestTrash_notShowConfirmDialog()844 public void testMediaEscalation_RequestTrash_notShowConfirmDialog() throws Exception { 845 doMediaEscalation_RequestTrash(true /* allowAccess */, 846 true /* shouldCheckDialogShownValue */, false /* isDialogShownExpected */); 847 } 848 doMediaEscalation_RequestTrash(boolean allowAccess, boolean shouldCheckDialogShownValue, boolean isDialogShownExpected)849 private void doMediaEscalation_RequestTrash(boolean allowAccess, 850 boolean shouldCheckDialogShownValue, boolean isDialogShownExpected) throws Exception { 851 doMediaEscalation_RequestTrash(MediaStorageTest::createAudio, allowAccess, 852 shouldCheckDialogShownValue, isDialogShownExpected); 853 doMediaEscalation_RequestTrash(MediaStorageTest::createVideo, allowAccess, 854 shouldCheckDialogShownValue, isDialogShownExpected); 855 doMediaEscalation_RequestTrash(MediaStorageTest::createImage, allowAccess, 856 shouldCheckDialogShownValue, isDialogShownExpected); 857 doMediaEscalation_RequestTrash(MediaStorageTest::createPlaylist, allowAccess, 858 shouldCheckDialogShownValue, isDialogShownExpected); 859 doMediaEscalation_RequestTrash(MediaStorageTest::createSubtitle, allowAccess, 860 shouldCheckDialogShownValue, isDialogShownExpected); 861 } 862 doMediaEscalation_RequestTrash(Callable<Uri> create, boolean allowAccess, boolean shouldCheckDialogShownValue, boolean isDialogShownExpected)863 private void doMediaEscalation_RequestTrash(Callable<Uri> create, boolean allowAccess, 864 boolean shouldCheckDialogShownValue, boolean isDialogShownExpected) throws Exception { 865 final Uri red = create.call(); 866 clearMediaOwner(red, mUserId); 867 868 assertEquals("0", queryForSingleColumn(red, MediaColumns.IS_TRASHED)); 869 870 if (allowAccess) { 871 doEscalation( 872 MediaStore.createTrashRequest(mContentResolver, Arrays.asList(red), true), 873 true /* allowAccess */, shouldCheckDialogShownValue, isDialogShownExpected); 874 assertEquals("1", queryForSingleColumn(red, MediaColumns.IS_TRASHED)); 875 doEscalation( 876 MediaStore.createTrashRequest(mContentResolver, Arrays.asList(red), false), 877 true /* allowAccess */, shouldCheckDialogShownValue, isDialogShownExpected); 878 assertEquals("0", queryForSingleColumn(red, MediaColumns.IS_TRASHED)); 879 } else { 880 doEscalation( 881 MediaStore.createTrashRequest(mContentResolver, Arrays.asList(red), true), 882 false /* allowAccess */, shouldCheckDialogShownValue, isDialogShownExpected); 883 assertEquals("0", queryForSingleColumn(red, MediaColumns.IS_TRASHED)); 884 } 885 } 886 887 @Test testMediaEscalation_RequestFavorite()888 public void testMediaEscalation_RequestFavorite() throws Exception { 889 doMediaEscalation_RequestFavorite(MediaStorageTest::createAudio); 890 doMediaEscalation_RequestFavorite(MediaStorageTest::createVideo); 891 doMediaEscalation_RequestFavorite(MediaStorageTest::createImage); 892 doMediaEscalation_RequestFavorite(MediaStorageTest::createPlaylist); 893 doMediaEscalation_RequestFavorite(MediaStorageTest::createSubtitle); 894 } 895 doMediaEscalation_RequestFavorite(Callable<Uri> create)896 private void doMediaEscalation_RequestFavorite(Callable<Uri> create) throws Exception { 897 final Uri red = create.call(); 898 clearMediaOwner(red, mUserId); 899 900 assertEquals("0", queryForSingleColumn(red, MediaColumns.IS_FAVORITE)); 901 doEscalation( 902 MediaStore.createFavoriteRequest(mContentResolver, Arrays.asList(red), true)); 903 assertEquals("1", queryForSingleColumn(red, MediaColumns.IS_FAVORITE)); 904 doEscalation( 905 MediaStore.createFavoriteRequest(mContentResolver, Arrays.asList(red), false)); 906 assertEquals("0", queryForSingleColumn(red, MediaColumns.IS_FAVORITE)); 907 } 908 909 @Test testMediaEscalation_RequestDelete()910 public void testMediaEscalation_RequestDelete() throws Exception { 911 doMediaEscalation_RequestDelete(true /* allowAccess */, 912 false /* shouldCheckDialogShownValue */, false /* isDialogShownExpected */); 913 } 914 915 @Test testMediaEscalationWithDenied_RequestDelete()916 public void testMediaEscalationWithDenied_RequestDelete() throws Exception { 917 doMediaEscalation_RequestDelete(false /* allowAccess */, 918 false /* shouldCheckDialogShownValue */, false /* isDialogShownExpected */); 919 } 920 921 @Test testMediaEscalation_RequestDelete_showConfirmDialog()922 public void testMediaEscalation_RequestDelete_showConfirmDialog() throws Exception { 923 doMediaEscalation_RequestDelete(true /* allowAccess */, 924 true /* shouldCheckDialogShownValue */, true /* isDialogShownExpected */); 925 } 926 927 @Test testMediaEscalation_RequestDelete_notShowConfirmDialog()928 public void testMediaEscalation_RequestDelete_notShowConfirmDialog() throws Exception { 929 doMediaEscalation_RequestDelete(true /* allowAccess */, 930 true /* shouldCheckDialogShownValue */, false /* isDialogShownExpected */); 931 } 932 doMediaEscalation_RequestDelete(boolean allowAccess, boolean shouldCheckDialogShownValue, boolean isDialogShownExpected)933 private void doMediaEscalation_RequestDelete(boolean allowAccess, 934 boolean shouldCheckDialogShownValue, boolean isDialogShownExpected) throws Exception { 935 doMediaEscalation_RequestDelete(MediaStorageTest::createAudio, allowAccess, 936 shouldCheckDialogShownValue, isDialogShownExpected); 937 doMediaEscalation_RequestDelete(MediaStorageTest::createVideo, allowAccess, 938 shouldCheckDialogShownValue, isDialogShownExpected); 939 doMediaEscalation_RequestDelete(MediaStorageTest::createImage, allowAccess, 940 shouldCheckDialogShownValue, isDialogShownExpected); 941 doMediaEscalation_RequestDelete(MediaStorageTest::createPlaylist, allowAccess, 942 shouldCheckDialogShownValue, isDialogShownExpected); 943 doMediaEscalation_RequestDelete(MediaStorageTest::createSubtitle, allowAccess, 944 shouldCheckDialogShownValue, isDialogShownExpected); 945 } 946 doMediaEscalation_RequestDelete(Callable<Uri> create, boolean allowAccess, boolean shouldCheckDialogShownValue, boolean isDialogShownExpected)947 private void doMediaEscalation_RequestDelete(Callable<Uri> create, boolean allowAccess, 948 boolean shouldCheckDialogShownValue, boolean isDialogShownExpected) throws Exception { 949 final Uri red = create.call(); 950 clearMediaOwner(red, mUserId); 951 952 try (Cursor c = mContentResolver.query(red, null, null, null)) { 953 assertEquals(1, c.getCount()); 954 } 955 956 if (allowAccess) { 957 doEscalation( 958 MediaStore.createDeleteRequest(mContentResolver, Arrays.asList(red)), 959 true /* allowAccess */, shouldCheckDialogShownValue, isDialogShownExpected); 960 try (Cursor c = mContentResolver.query(red, null, null, null)) { 961 assertEquals(0, c.getCount()); 962 } 963 } else { 964 doEscalation( 965 MediaStore.createDeleteRequest(mContentResolver, Arrays.asList(red)), 966 false /* allowAccess */, shouldCheckDialogShownValue, isDialogShownExpected); 967 try (Cursor c = mContentResolver.query(red, null, null, null)) { 968 assertEquals(1, c.getCount()); 969 } 970 } 971 } 972 scrollIntoView(UiSelector selector)973 private static void scrollIntoView(UiSelector selector) { 974 UiScrollable uiScrollable = new UiScrollable(new UiSelector().scrollable(true)); 975 try { 976 uiScrollable.scrollIntoView(selector); 977 } catch (UiObjectNotFoundException e) { 978 // Scrolling can fail if the UI is not scrollable 979 } 980 } 981 createDownload()982 private static Uri createDownload() throws IOException { 983 final String content = "<html><body>Content</body></html>"; 984 final String displayName = "cts" + System.nanoTime(); 985 final String mimeType = "text/html"; 986 final Context context = InstrumentationRegistry.getTargetContext(); 987 final PendingParams params = new PendingParams( 988 MediaStore.Downloads.EXTERNAL_CONTENT_URI, displayName, mimeType); 989 990 final Uri pendingUri = MediaStoreUtils.createPending(context, params); 991 assertNotNull(pendingUri); 992 try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) { 993 try (PrintWriter pw = new PrintWriter(session.openOutputStream())) { 994 pw.print(content); 995 } 996 try (OutputStream out = session.openOutputStream()) { 997 out.write(content.getBytes(StandardCharsets.UTF_8)); 998 } 999 return session.publish(); 1000 } 1001 } 1002 createFile()1003 private static Uri createFile() throws IOException { 1004 return createSubtitle(); 1005 } 1006 createAudio()1007 private static Uri createAudio() throws IOException { 1008 final Context context = InstrumentationRegistry.getTargetContext(); 1009 final String displayName = "cts" + System.nanoTime(); 1010 final PendingParams params = new PendingParams( 1011 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, displayName, "audio/mpeg"); 1012 final Uri pendingUri = MediaStoreUtils.createPending(context, params); 1013 1014 try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) { 1015 try (InputStream in = context.getResources().getAssets().open("testmp3.mp3"); 1016 OutputStream out = session.openOutputStream()) { 1017 FileUtils.copy(in, out); 1018 } 1019 return session.publish(); 1020 } 1021 } 1022 createVideo()1023 private static Uri createVideo() throws IOException { 1024 final Context context = InstrumentationRegistry.getTargetContext(); 1025 final String displayName = "cts" + System.nanoTime(); 1026 final PendingParams params = new PendingParams( 1027 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, displayName, "video/mpeg"); 1028 final Uri pendingUri = MediaStoreUtils.createPending(context, params); 1029 try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) { 1030 try (InputStream in = context.getResources().getAssets().open("testmp3.mp3"); 1031 OutputStream out = session.openOutputStream()) { 1032 FileUtils.copy(in, out); 1033 } 1034 return session.publish(); 1035 } 1036 } 1037 createImage()1038 private static Uri createImage() throws IOException { 1039 final Context context = InstrumentationRegistry.getTargetContext(); 1040 final String displayName = "cts" + System.nanoTime(); 1041 final PendingParams params = new PendingParams( 1042 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, displayName, "image/png"); 1043 final Uri pendingUri = MediaStoreUtils.createPending(context, params); 1044 try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) { 1045 try (OutputStream out = session.openOutputStream()) { 1046 final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888); 1047 bitmap.compress(Bitmap.CompressFormat.PNG, 90, out); 1048 } 1049 return session.publish(); 1050 } 1051 } 1052 createPlaylist()1053 private static Uri createPlaylist() throws IOException { 1054 final Context context = InstrumentationRegistry.getTargetContext(); 1055 final String displayName = "cts" + System.nanoTime(); 1056 final PendingParams params = new PendingParams( 1057 MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, displayName, "audio/mpegurl"); 1058 final Uri pendingUri = MediaStoreUtils.createPending(context, params); 1059 try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) { 1060 return session.publish(); 1061 } 1062 } 1063 createSubtitle()1064 private static Uri createSubtitle() throws IOException { 1065 final Context context = InstrumentationRegistry.getTargetContext(); 1066 final String displayName = "cts" + System.nanoTime(); 1067 final PendingParams params = new PendingParams( 1068 MediaStore.Files.getContentUri(VOLUME_EXTERNAL), displayName, 1069 "application/x-subrip"); 1070 final Uri pendingUri = MediaStoreUtils.createPending(context, params); 1071 try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) { 1072 try (InputStream in = context.getResources().getAssets().open("testmp3.mp3"); 1073 OutputStream out = session.openOutputStream()) { 1074 FileUtils.copy(in, out); 1075 } 1076 return session.publish(); 1077 } 1078 } 1079 queryForSingleColumn(Uri uri, String column)1080 private static String queryForSingleColumn(Uri uri, String column) throws Exception { 1081 final ContentResolver resolver = InstrumentationRegistry.getTargetContext() 1082 .getContentResolver(); 1083 try (Cursor c = resolver.query(uri, new String[]{column}, null, null)) { 1084 assertEquals(1, c.getCount()); 1085 assertTrue(c.moveToFirst()); 1086 return c.getString(0); 1087 } 1088 } 1089 clearOwnFiles(Uri uri)1090 private static void clearOwnFiles(Uri uri) throws Exception { 1091 final ContentResolver resolver = InstrumentationRegistry.getTargetContext() 1092 .getContentResolver(); 1093 try (Cursor c = resolver.query(uri, new String[]{MediaColumns._ID}, null, null)) { 1094 while (c.moveToNext()) { 1095 final long id = c.getLong(0); 1096 final Uri contentUri = ContentUris.withAppendedId(uri, id); 1097 resolver.delete(contentUri, null); 1098 } 1099 } 1100 } 1101 clearMediaOwner(Uri uri, int userId)1102 private static void clearMediaOwner(Uri uri, int userId) throws IOException { 1103 final String cmd = String.format( 1104 "content update --uri %s --user %d --bind owner_package_name:n:", 1105 uri, userId); 1106 runShellCommand(InstrumentationRegistry.getInstrumentation(), cmd); 1107 } 1108 stageFile(File file)1109 static File stageFile(File file) throws Exception { 1110 // Sometimes file creation fails due to slow permission update, try more times 1111 while (currentAttempt < MAX_NUMBER_OF_ATTEMPT) { 1112 try { 1113 file.getParentFile().mkdirs(); 1114 file.createNewFile(); 1115 return file; 1116 } catch (IOException e) { 1117 currentAttempt++; 1118 // wait 500ms 1119 Thread.sleep(500); 1120 } 1121 } 1122 throw new TimeoutException("File creation failed due to slow permission update"); 1123 } 1124 1125 } 1126