1 /* 2 * Copyright (C) 2021 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.scopedstorage.cts.redacturi; 18 19 20 import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; 21 import static android.database.Cursor.FIELD_TYPE_BLOB; 22 import static android.scopedstorage.cts.lib.TestUtils.addressStoragePermissions; 23 import static android.scopedstorage.cts.lib.TestUtils.assertThrows; 24 import static android.scopedstorage.cts.lib.TestUtils.canOpenRedactedUriForWrite; 25 import static android.scopedstorage.cts.lib.TestUtils.canQueryOnUri; 26 import static android.scopedstorage.cts.lib.TestUtils.checkPermission; 27 import static android.scopedstorage.cts.lib.TestUtils.forceStopApp; 28 import static android.scopedstorage.cts.lib.TestUtils.getContentResolver; 29 import static android.scopedstorage.cts.lib.TestUtils.grantPermission; 30 import static android.scopedstorage.cts.lib.TestUtils.isFileDescriptorRedacted; 31 import static android.scopedstorage.cts.lib.TestUtils.isFileOpenRedacted; 32 import static android.scopedstorage.cts.lib.TestUtils.setShouldForceStopTestApp; 33 34 import static androidx.test.InstrumentationRegistry.getContext; 35 36 import static com.google.common.truth.Truth.assertThat; 37 38 import static org.junit.Assert.assertEquals; 39 import static org.junit.Assert.assertNotEquals; 40 import static org.junit.Assert.assertNotNull; 41 import static org.junit.Assert.assertNull; 42 import static org.junit.Assert.assertTrue; 43 import static org.junit.Assume.assumeTrue; 44 45 46 import android.Manifest; 47 import android.content.ContentValues; 48 import android.content.Intent; 49 import android.database.Cursor; 50 import android.media.ExifInterface; 51 import android.net.Uri; 52 import android.scopedstorage.cts.lib.ScopedStorageBaseDeviceTest; 53 import android.os.Bundle; 54 import android.os.Environment; 55 import android.os.FileUtils; 56 import android.os.ParcelFileDescriptor; 57 import android.provider.MediaStore; 58 import android.system.Os; 59 60 import androidx.test.filters.SdkSuppress; 61 62 import com.android.cts.install.lib.TestApp; 63 import com.android.modules.utils.build.SdkLevel; 64 65 import org.junit.AfterClass; 66 import org.junit.BeforeClass; 67 import org.junit.Test; 68 import org.junit.runner.RunWith; 69 import org.junit.runners.Parameterized; 70 71 import java.io.File; 72 import java.io.FileDescriptor; 73 import java.io.FileNotFoundException; 74 import java.io.FileOutputStream; 75 import java.io.IOException; 76 import java.io.InputStream; 77 import java.io.OutputStream; 78 import java.util.ArrayList; 79 import java.util.Arrays; 80 import java.util.Collection; 81 import java.util.List; 82 83 /** 84 * Device-side test suite to verify redacted URI operations. 85 */ 86 @RunWith(Parameterized.class) 87 @SdkSuppress(minSdkVersion = 31, codeName = "S") 88 public class RedactUriDeviceTest extends ScopedStorageBaseDeviceTest { 89 90 /** 91 * To help avoid flaky tests, give ourselves a unique nonce to be used for 92 * all filesystem paths, so that we don't risk conflicting with previous 93 * test runs. 94 */ 95 static final String NONCE = String.valueOf(System.nanoTime()); 96 97 static final String IMAGE_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".jpg"; 98 99 static final String FUZZER_HEIC_FILE_NAME = 100 "ScopedStorageDeviceTest_file_fuzzer_" + NONCE + ".heic"; 101 102 // An app with no permissions 103 private static final TestApp APP_B_NO_PERMS = new TestApp("TestAppB", 104 "android.scopedstorage.cts.testapp.B.noperms", 1, false, 105 "CtsScopedStorageTestAppB.apk"); 106 107 private static final TestApp APP_E = new TestApp("TestAppE", 108 "android.scopedstorage.cts.testapp.E", 1, false, "CtsScopedStorageTestAppE.apk"); 109 110 @Parameterized.Parameter(0) 111 public String mVolumeName; 112 113 /** Parameters data. */ 114 @Parameterized.Parameters(name = "volume={0}") data()115 public static Iterable<? extends Object> data() { 116 return getTestParameters(); 117 } 118 119 @BeforeClass setupApps()120 public static void setupApps() { 121 // Installed by target preparer 122 assertThat(checkPermission(APP_B_NO_PERMS, 123 Manifest.permission.READ_EXTERNAL_STORAGE)).isFalse(); 124 setShouldForceStopTestApp(false); 125 } 126 127 @AfterClass destroy()128 public static void destroy() { 129 setShouldForceStopTestApp(true); 130 } 131 132 @Test testRedactedUri_single()133 public void testRedactedUri_single() throws Exception { 134 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 135 136 try { 137 final Uri uri = MediaStore.scanFile(getContentResolver(), img); 138 final Uri redactedUri = MediaStore.getRedactedUri(getContentResolver(), uri); 139 testRedactedUriCommon(uri, redactedUri); 140 } finally { 141 img.delete(); 142 } 143 } 144 145 @Test testRedactedUri_list()146 public void testRedactedUri_list() throws Exception { 147 List<Uri> uris = new ArrayList<>(); 148 List<File> files = new ArrayList<>(); 149 150 try { 151 for (int i = 0; i < 10; i++) { 152 File file = stageImageFileWithMetadata("img_metadata" + String.valueOf( 153 System.nanoTime()) + i + ".jpg"); 154 files.add(file); 155 uris.add(MediaStore.scanFile(getContentResolver(), file)); 156 } 157 158 final Collection<Uri> redactedUris = MediaStore.getRedactedUri(getContentResolver(), 159 uris); 160 int i = 0; 161 for (Uri redactedUri : redactedUris) { 162 Uri uri = uris.get(i++); 163 testRedactedUriCommon(uri, redactedUri); 164 } 165 } finally { 166 files.forEach(file -> file.delete()); 167 } 168 } 169 170 @Test testQueryOnRedactionUri()171 public void testQueryOnRedactionUri() throws Exception { 172 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 173 final Uri uri = MediaStore.scanFile(getContentResolver(), img); 174 final Uri redactedUri = MediaStore.getRedactedUri(getContentResolver(), uri); 175 final Cursor uriCursor = getContentResolver().query(uri, null, null, null); 176 final String redactedUriDir = ".transforms/synthetic/redacted"; 177 final String redactedUriDirAbsolutePath = 178 Environment.getExternalStorageDirectory() + "/" + redactedUriDir; 179 try { 180 assertNotNull(uriCursor); 181 assertThat(uriCursor.moveToFirst()).isTrue(); 182 183 final Cursor redactedUriCursor = getContentResolver().query(redactedUri, null, null, 184 null); 185 assertNotNull(redactedUriCursor); 186 assertThat(redactedUriCursor.moveToFirst()).isTrue(); 187 188 assertEquals(redactedUriCursor.getColumnCount(), uriCursor.getColumnCount()); 189 190 final String data = getStringFromCursor(redactedUriCursor, 191 MediaStore.MediaColumns.DATA); 192 final String redactedUriDisplayName = redactedUri.getLastPathSegment() + ".jpg"; 193 assertEquals(redactedUriDirAbsolutePath + "/" + redactedUriDisplayName, data); 194 195 final String name = getStringFromCursor(redactedUriCursor, 196 MediaStore.MediaColumns.DISPLAY_NAME); 197 assertEquals(redactedUriDisplayName, name); 198 199 final String relativePath = getStringFromCursor(redactedUriCursor, 200 MediaStore.MediaColumns.RELATIVE_PATH); 201 assertEquals(redactedUriDir, relativePath); 202 203 final String bucketDisplayName = getStringFromCursor(redactedUriCursor, 204 MediaStore.MediaColumns.BUCKET_DISPLAY_NAME); 205 assertEquals(redactedUriDir, bucketDisplayName); 206 207 final String docId = getStringFromCursor(redactedUriCursor, 208 MediaStore.MediaColumns.DOCUMENT_ID); 209 assertNull(docId); 210 211 final String insId = getStringFromCursor(redactedUriCursor, 212 MediaStore.MediaColumns.INSTANCE_ID); 213 assertNull(insId); 214 215 final String bucId = getStringFromCursor(redactedUriCursor, 216 MediaStore.MediaColumns.BUCKET_ID); 217 assertNull(bucId); 218 219 final Collection<String> updatedCols = Arrays.asList(MediaStore.MediaColumns._ID, 220 MediaStore.MediaColumns.DISPLAY_NAME, 221 MediaStore.MediaColumns.RELATIVE_PATH, 222 MediaStore.MediaColumns.BUCKET_DISPLAY_NAME, 223 MediaStore.MediaColumns.DATA, 224 MediaStore.MediaColumns.DOCUMENT_ID, 225 MediaStore.MediaColumns.INSTANCE_ID, 226 MediaStore.MediaColumns.BUCKET_ID); 227 for (String colName : uriCursor.getColumnNames()) { 228 if (!updatedCols.contains(colName)) { 229 if (uriCursor.getType(uriCursor.getColumnIndex(colName)) == FIELD_TYPE_BLOB) { 230 assertThat( 231 Arrays.equals(uriCursor.getBlob(uriCursor.getColumnIndex(colName)), 232 redactedUriCursor.getBlob(redactedUriCursor.getColumnIndex( 233 colName)))).isTrue(); 234 } else { 235 assertEquals(getStringFromCursor(uriCursor, colName), 236 getStringFromCursor(redactedUriCursor, colName)); 237 } 238 } 239 } 240 } finally { 241 img.delete(); 242 } 243 } 244 245 /* 246 * Verify that app can't open the shared redacted URI for write. 247 **/ 248 @Test testSharedRedactedUri_openFdForWrite()249 public void testSharedRedactedUri_openFdForWrite() throws Exception { 250 forceStopApp(APP_B_NO_PERMS.getPackageName()); 251 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 252 try { 253 Uri redactedUri = shareAndGetRedactedUri(img, APP_B_NO_PERMS); 254 assertThrows(UnsupportedOperationException.class, 255 () -> canOpenRedactedUriForWrite(APP_B_NO_PERMS, redactedUri)); 256 } finally { 257 img.delete(); 258 } 259 } 260 261 /* 262 * Verify that app with correct permission can open the shared redacted URI for read in 263 * redacted mode. 264 **/ 265 @Test testSharedRedactedUri_openFdForRead()266 public void testSharedRedactedUri_openFdForRead() throws Exception { 267 forceStopApp(APP_B_NO_PERMS.getPackageName()); 268 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 269 try { 270 final Uri redactedUri = shareAndGetRedactedUri(img, APP_B_NO_PERMS); 271 assertThat(isFileDescriptorRedacted(APP_B_NO_PERMS, redactedUri)).isTrue(); 272 } finally { 273 img.delete(); 274 } 275 } 276 277 /* 278 * Verify that app with correct permission can open the shared redacted URI for read in 279 * redacted mode. 280 **/ 281 @Test testSharedRedactedUri_openFileForRead()282 public void testSharedRedactedUri_openFileForRead() throws Exception { 283 forceStopApp(APP_B_NO_PERMS.getPackageName()); 284 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 285 try { 286 Uri redactedUri = shareAndGetRedactedUri(img, APP_B_NO_PERMS); 287 assertThat(isFileOpenRedacted(APP_B_NO_PERMS, redactedUri)).isTrue(); 288 } finally { 289 img.delete(); 290 } 291 } 292 293 /* 294 * Verify that the app with redacted URI granted can query it. 295 **/ 296 @Test testSharedRedactedUri_query()297 public void testSharedRedactedUri_query() throws Exception { 298 forceStopApp(APP_B_NO_PERMS.getPackageName()); 299 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 300 try { 301 Uri redactedUri = shareAndGetRedactedUri(img, APP_B_NO_PERMS); 302 assertThat(canQueryOnUri(APP_B_NO_PERMS, redactedUri)).isTrue(); 303 } finally { 304 img.delete(); 305 } 306 } 307 308 /* 309 * Verify that for app with AML permission shared redacted URI opens for read in redacted mode. 310 **/ 311 @Test testSharedRedactedUri_openFileForRead_withLocationPerm()312 public void testSharedRedactedUri_openFileForRead_withLocationPerm() throws Exception { 313 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 314 forceStopApp(APP_E.getPackageName()); 315 try { 316 addressStoragePermissions(APP_E.getPackageName(), true); 317 grantPermission(APP_E.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION); 318 319 Uri redactedUri = shareAndGetRedactedUri(img, APP_E); 320 assertThat(isFileOpenRedacted(APP_E, redactedUri)).isTrue(); 321 } finally { 322 img.delete(); 323 } 324 } 325 326 /* 327 * Verify that for app with AML permission shared redacted URI opens for read in redacted mode. 328 **/ 329 @Test testSharedRedactedUri_openFdForRead_withLocationPerm()330 public void testSharedRedactedUri_openFdForRead_withLocationPerm() throws Exception { 331 forceStopApp(APP_E.getPackageName()); 332 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 333 try { 334 addressStoragePermissions(APP_E.getPackageName(), true); 335 grantPermission(APP_E.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION); 336 337 Uri redactedUri = shareAndGetRedactedUri(img, APP_E); 338 assertThat(isFileDescriptorRedacted(APP_E, redactedUri)).isTrue(); 339 } finally { 340 img.delete(); 341 } 342 } 343 344 /* 345 * Verify that the test app can't access unshared redacted uri via file descriptor 346 **/ 347 @Test testUnsharedRedactedUri_openFdForRead()348 public void testUnsharedRedactedUri_openFdForRead() throws Exception { 349 forceStopApp(APP_B_NO_PERMS.getPackageName()); 350 forceStopApp(APP_E.getPackageName()); 351 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 352 try { 353 addressStoragePermissions(APP_E.getPackageName(), true); 354 355 final Uri redactedUri = getRedactedUri(img); 356 // APP_E has R_E_S, so should have access to redactedUri 357 assertThat(isFileDescriptorRedacted(APP_E, redactedUri)).isTrue(); 358 assertThrows(SecurityException.class, 359 () -> isFileDescriptorRedacted(APP_B_NO_PERMS, redactedUri)); 360 } finally { 361 img.delete(); 362 } 363 } 364 365 /* 366 * Verify that the test app can't access unshared redacted uri via file path 367 **/ 368 @Test testUnsharedRedactedUri_openFileForRead()369 public void testUnsharedRedactedUri_openFileForRead() throws Exception { 370 forceStopApp(APP_B_NO_PERMS.getPackageName()); 371 forceStopApp(APP_E.getPackageName()); 372 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 373 try { 374 addressStoragePermissions(APP_E.getPackageName(), true); 375 376 final Uri redactedUri = getRedactedUri(img); 377 // APP_E has R_E_S 378 assertThat(isFileOpenRedacted(APP_E, redactedUri)).isTrue(); 379 assertThrows(IOException.class, () -> isFileOpenRedacted(APP_B_NO_PERMS, redactedUri)); 380 } finally { 381 img.delete(); 382 } 383 } 384 385 @Test testGrantUriPermissionsForRedactedUri()386 public void testGrantUriPermissionsForRedactedUri() throws Exception { 387 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 388 final Uri redactedUri = getRedactedUri(img); 389 try { 390 getContext().grantUriPermission(APP_B_NO_PERMS.getPackageName(), redactedUri, 391 FLAG_GRANT_READ_URI_PERMISSION); 392 assertThrows(SecurityException.class, () -> 393 getContext().grantUriPermission(APP_B_NO_PERMS.getPackageName(), redactedUri, 394 Intent.FLAG_GRANT_WRITE_URI_PERMISSION)); 395 } finally { 396 img.delete(); 397 } 398 } 399 400 @Test testDisallowedOperationsOnRedactedUri()401 public void testDisallowedOperationsOnRedactedUri() throws Exception { 402 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 403 final Uri redactedUri = getRedactedUri(img); 404 try { 405 ContentValues cv = new ContentValues(); 406 cv.put(MediaStore.MediaColumns.DATE_ADDED, 1); 407 assertEquals(0, getContentResolver().update(redactedUri, new ContentValues(), 408 new Bundle())); 409 assertEquals(0, getContentResolver().delete(redactedUri, new Bundle())); 410 } finally { 411 img.delete(); 412 } 413 } 414 415 @Test testOpenOnRedactedUri_file()416 public void testOpenOnRedactedUri_file() throws Exception { 417 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 418 final Uri redactedUri = getRedactedUri(img); 419 try { 420 assertUriIsUnredacted(img); 421 422 final Cursor redactedUriCursor = getRedactedCursor(redactedUri); 423 File file = new File( 424 getStringFromCursor(redactedUriCursor, MediaStore.MediaColumns.DATA)); 425 ExifInterface redactedExifInf = new ExifInterface(file); 426 assertUriIsRedacted(redactedExifInf); 427 428 assertThrows(FileNotFoundException.class, () -> new FileOutputStream(file)); 429 } finally { 430 img.delete(); 431 } 432 } 433 434 @Test testOpenOnRedactedUri_write()435 public void testOpenOnRedactedUri_write() throws Exception { 436 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 437 final Uri redactedUri = getRedactedUri(img); 438 try { 439 assertThrows(UnsupportedOperationException.class, 440 () -> getContentResolver().openFileDescriptor(redactedUri, 441 "w")); 442 } finally { 443 img.delete(); 444 } 445 } 446 447 @Test testOpenOnRedactedUri_inputstream()448 public void testOpenOnRedactedUri_inputstream() throws Exception { 449 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 450 final Uri redactedUri = getRedactedUri(img); 451 try { 452 assertUriIsUnredacted(img); 453 454 try (InputStream is = getContentResolver().openInputStream(redactedUri)) { 455 ExifInterface redactedExifInf = new ExifInterface(is); 456 assertUriIsRedacted(redactedExifInf); 457 } 458 } finally { 459 img.delete(); 460 } 461 } 462 463 @Test testOpenOnRedactedUri_read()464 public void testOpenOnRedactedUri_read() throws Exception { 465 final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); 466 final Uri redactedUri = getRedactedUri(img); 467 try { 468 assertUriIsUnredacted(img); 469 470 try (ParcelFileDescriptor pfd = 471 getContentResolver().openFileDescriptor(redactedUri, "r")) { 472 FileDescriptor fd = pfd.getFileDescriptor(); 473 ExifInterface redactedExifInf = new ExifInterface(fd); 474 assertUriIsRedacted(redactedExifInf); 475 } 476 } finally { 477 img.delete(); 478 } 479 } 480 481 @Test testOpenOnRedactedUri_readFuzzer()482 public void testOpenOnRedactedUri_readFuzzer() throws Exception { 483 assumeTrue(SdkLevel.isAtLeastV()); 484 final File img = stageFuzzerImageFileWithMetadata(FUZZER_HEIC_FILE_NAME); 485 final Uri redactedUri = getRedactedUri(img); 486 try { 487 assertUriIsUnredacted(img); 488 489 try (ParcelFileDescriptor pfd = 490 getContentResolver().openFileDescriptor(redactedUri, "r")) { 491 FileDescriptor fd = pfd.getFileDescriptor(); 492 int bufSize = 0x1000000; 493 byte[] data = new byte[bufSize]; 494 int fileSize = Os.read(fd, data, 0, bufSize); 495 assertUriIsRedacted(data, fileSize); 496 } 497 } finally { 498 img.delete(); 499 } 500 } 501 testRedactedUriCommon(Uri uri, Uri redactedUri)502 private void testRedactedUriCommon(Uri uri, Uri redactedUri) { 503 assertEquals(redactedUri.getAuthority(), uri.getAuthority()); 504 assertEquals(redactedUri.getScheme(), uri.getScheme()); 505 assertNotEquals(redactedUri.getPath(), uri.getPath()); 506 assertNotEquals(redactedUri.getPathSegments(), uri.getPathSegments()); 507 508 final String uriId = redactedUri.getLastPathSegment(); 509 assertThat(uriId.startsWith("RUID")).isTrue(); 510 assertEquals(uriId.length(), 36); 511 } 512 shareAndGetRedactedUri(File file, TestApp testApp)513 private Uri shareAndGetRedactedUri(File file, TestApp testApp) { 514 final Uri redactedUri = getRedactedUri(file); 515 getContext().grantUriPermission(testApp.getPackageName(), redactedUri, 516 FLAG_GRANT_READ_URI_PERMISSION); 517 518 return redactedUri; 519 } 520 getRedactedUri(File file)521 private Uri getRedactedUri(File file) { 522 final Uri uri = MediaStore.scanFile(getContentResolver(), file); 523 return MediaStore.getRedactedUri(getContentResolver(), uri); 524 } 525 assertUriIsUnredacted(File img)526 private void assertUriIsUnredacted(File img) throws Exception { 527 final ExifInterface exifInterface = new ExifInterface(img); 528 assertNotEquals(exifInterface.getGpsDateTime(), -1); 529 530 float[] latLong = new float[]{0, 0}; 531 exifInterface.getLatLong(latLong); 532 assertNotEquals(latLong[0], 0); 533 assertNotEquals(latLong[1], 0); 534 } 535 assertUriIsRedacted(ExifInterface redactedExifInf)536 private void assertUriIsRedacted(ExifInterface redactedExifInf) { 537 assertEquals(redactedExifInf.getGpsDateTime(), -1); 538 float[] latLong = new float[]{0, 0}; 539 redactedExifInf.getLatLong(latLong); 540 assertEquals(latLong[0], 0.0, 0.0); 541 assertEquals(latLong[1], 0.0, 0.0); 542 } 543 assertUriIsRedacted(byte[] data, int fileSize)544 private void assertUriIsRedacted(byte[] data, int fileSize) { 545 // Data in redaction ranges should be zero. 546 int[] start = new int[]{50538, 712941, 712965, 712989, 713033, 713101}; 547 int[] end = new int[]{711958, 712943, 712967, 712990, 713100, 713125}; 548 549 assertTrue(fileSize == 4407744); 550 for (int index = 0; index < start.length && index < end.length; index++) { 551 for (int c = start[index]; c < end[index]; c++) { 552 assertTrue("It should be zero!", data[c] == (byte) 0); 553 } 554 } 555 } 556 getRedactedCursor(Uri redactedUri)557 private Cursor getRedactedCursor(Uri redactedUri) { 558 Cursor redactedUriCursor = getContentResolver().query(redactedUri, null, null, null); 559 assertNotNull(redactedUriCursor); 560 assertThat(redactedUriCursor.moveToFirst()).isTrue(); 561 562 return redactedUriCursor; 563 } 564 getStringFromCursor(Cursor c, String colName)565 private String getStringFromCursor(Cursor c, String colName) { 566 return c.getString(c.getColumnIndex(colName)); 567 } 568 stageImageFileWithMetadata(String name)569 private File stageImageFileWithMetadata(String name) throws Exception { 570 return stageImageFileWithMetadata(name, R.raw.img_with_metadata); 571 } 572 stageFuzzerImageFileWithMetadata(String name)573 private File stageFuzzerImageFileWithMetadata(String name) throws Exception { 574 return stageImageFileWithMetadata(name, R.raw.fuzzer); 575 } 576 stageImageFileWithMetadata(String name, int sourceId)577 private File stageImageFileWithMetadata(String name, int sourceId) throws Exception { 578 final File img = new File( 579 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), name); 580 581 try (InputStream in = 582 getContext().getResources().openRawResource(sourceId); 583 OutputStream out = new FileOutputStream(img)) { 584 // Dump the image we have to external storage 585 FileUtils.copy(in, out); 586 } 587 588 return img; 589 } 590 } 591