1 /**
2  * Copyright (C) 2020 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.lib;
18 
19 import static androidx.test.InstrumentationRegistry.getContext;
20 
21 import static org.junit.Assert.fail;
22 
23 import android.media.ExifInterface;
24 import android.net.Uri;
25 import android.os.FileUtils;
26 import android.provider.MediaStore;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.RawRes;
30 
31 import java.io.File;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.util.HashMap;
36 import java.util.Objects;
37 
38 /**
39  * Helper functions and utils for redactions tests
40  */
41 public class RedactionTestHelper {
42     private static final String TAG = "RedactionTestHelper";
43 
44     private static final String[] EXIF_GPS_TAGS = {
45             ExifInterface.TAG_GPS_ALTITUDE,
46             ExifInterface.TAG_GPS_DOP,
47             ExifInterface.TAG_GPS_DATESTAMP,
48             ExifInterface.TAG_GPS_LATITUDE,
49             ExifInterface.TAG_GPS_LATITUDE_REF,
50             ExifInterface.TAG_GPS_LONGITUDE,
51             ExifInterface.TAG_GPS_LONGITUDE_REF,
52             ExifInterface.TAG_GPS_PROCESSING_METHOD,
53             ExifInterface.TAG_GPS_TIMESTAMP,
54             ExifInterface.TAG_GPS_VERSION_ID,
55     };
56 
57     public static final String EXIF_METADATA_QUERY = "android.scopedstorage.cts.exif";
58 
59     /**
60      * Retrieve the EXIF metadata from the given file.
61      */
62     @NonNull
getExifMetadataFromFile(@onNull File file)63     public static HashMap<String, String> getExifMetadataFromFile(@NonNull File file)
64             throws IOException {
65         final ExifInterface exif = new ExifInterface(file);
66         return dumpExifGpsTagsToMap(exif);
67     }
68 
69     /**
70      * Retrieve the EXIF metadata from the given uri.
71      */
72     @NonNull
getExifMetadataFromUri(@onNull Uri uri)73     private static HashMap<String, String> getExifMetadataFromUri(@NonNull Uri uri)
74             throws IOException {
75         try (InputStream is = getContext().getContentResolver().openInputStream(uri)) {
76             final ExifInterface exif = new ExifInterface(is);
77             return dumpExifGpsTagsToMap(exif);
78         }
79     }
80 
81 
82     /**
83      * Retrieve the EXIF metadata from the given resource.
84      */
85     @NonNull
getExifMetadataFromRawResource(@awRes int resId)86     public static HashMap<String, String> getExifMetadataFromRawResource(@RawRes int resId)
87             throws IOException {
88         final ExifInterface exif;
89         try (InputStream in = getContext().getResources().openRawResource(resId)) {
90             exif = new ExifInterface(in);
91         }
92         return dumpExifGpsTagsToMap(exif);
93     }
94 
95     /**
96      * Asserts the 2 given EXIF maps have the same content.
97      */
assertExifMetadataMatch( @onNull HashMap<String, String> actual, @NonNull HashMap<String, String> expected)98     public static void assertExifMetadataMatch(
99             @NonNull HashMap<String, String> actual, @NonNull HashMap<String, String> expected) {
100         for (String tag : EXIF_GPS_TAGS) {
101             assertMetadataEntryMatch(tag, actual.get(tag), expected.get(tag));
102         }
103     }
104 
105     /**
106      * Asserts the 2 given EXIF maps don't have the same content.
107      */
assertExifMetadataMismatch( @onNull HashMap<String, String> actual, @NonNull HashMap<String, String> expected)108     public static void assertExifMetadataMismatch(
109             @NonNull HashMap<String, String> actual, @NonNull HashMap<String, String> expected) {
110         for (String tag : EXIF_GPS_TAGS) {
111             assertMetadataEntryMismatch(tag, actual.get(tag), expected.get(tag));
112         }
113     }
114 
assertMetadataEntryMatch(String tag, String actual, String expected)115     private static void assertMetadataEntryMatch(String tag, String actual, String expected) {
116         if (!Objects.equals(actual, expected)) {
117             fail("Unexpected metadata mismatch for tag: " + tag + "\n"
118                     + "expected:" + expected + "\n"
119                     + "but was: " + actual);
120         }
121     }
122 
assertMetadataEntryMismatch(String tag, String actual, String expected)123     private static void assertMetadataEntryMismatch(String tag, String actual, String expected) {
124         if (Objects.equals(actual, expected)) {
125             fail("Unexpected metadata match for tag: " + tag + "\n"
126                     + "expected not to be:" + expected);
127         }
128     }
129 
dumpExifGpsTagsToMap(ExifInterface exif)130     private static HashMap<String, String> dumpExifGpsTagsToMap(ExifInterface exif) {
131         final HashMap<String, String> res = new HashMap<>();
132         for (String tag : EXIF_GPS_TAGS) {
133             res.put(tag, exif.getAttribute(tag));
134         }
135         return res;
136     }
137 
assertConsistentNonRedactedAccess(File file, int metadataResId)138     public static void assertConsistentNonRedactedAccess(File file, int metadataResId)
139             throws Exception {
140         // Write some meta-data to the file to assert on redacted information access
141         try (InputStream in =
142                      getContext().getResources().openRawResource(metadataResId);
143              FileOutputStream out = new FileOutputStream(file)) {
144             FileUtils.copy(in, out);
145             out.getFD().sync();
146         }
147 
148         HashMap<String, String> originalExif = getExifMetadataFromRawResource(metadataResId);
149 
150         // Using File API
151         HashMap<String, String> exifFromFilePath = getExifMetadataFromFile(file);
152         assertExifMetadataMatch(exifFromFilePath, originalExif);
153 
154         Uri uri = MediaStore.scanFile(getContext().getContentResolver(), file);
155         // Using ContentResolver API
156         HashMap<String, String> exifFromContentResolver = getExifMetadataFromUri(uri);
157         assertExifMetadataMatch(exifFromContentResolver, originalExif);
158     }
159 }
160