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