1 /* 2 * Copyright (C) 2019 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.providers.media.util; 18 19 import static com.android.providers.media.util.FileCreationUtils.stageMp4File; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.junit.Assert.assertThrows; 24 25 import android.content.Context; 26 import android.media.ExifInterface; 27 import android.util.Xml; 28 29 import androidx.test.platform.app.InstrumentationRegistry; 30 31 import com.android.providers.media.R; 32 33 import org.junit.Test; 34 import org.xmlpull.v1.XmlPullParser; 35 36 import java.io.ByteArrayInputStream; 37 import java.io.File; 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.nio.charset.StandardCharsets; 41 42 public class XmpDataParserTest { 43 @Test testContainer_Empty()44 public void testContainer_Empty() throws Exception { 45 final Context context = InstrumentationRegistry.getInstrumentation().getContext(); 46 try (InputStream in = context.getResources().openRawResource(R.raw.test_image)) { 47 final ExifInterface exifInterface = new ExifInterface(in); 48 final XmpInterface xmp = XmpDataParser.createXmpInterface(exifInterface); 49 assertThat(xmp.getFormat()).isNull(); 50 assertThat(xmp.getDocumentId()).isNull(); 51 assertThat(xmp.getInstanceId()).isNull(); 52 assertThat(xmp.getOriginalDocumentId()).isNull(); 53 } 54 } 55 56 @Test testContainer_ValidAttrs()57 public void testContainer_ValidAttrs() throws Exception { 58 final Context context = InstrumentationRegistry.getInstrumentation().getContext(); 59 try (InputStream in = context.getResources().openRawResource(R.raw.lg_g4_iso_800_jpg)) { 60 final ExifInterface exifInterface = new ExifInterface(in); 61 final XmpInterface xmp = XmpDataParser.createXmpInterface(exifInterface); 62 assertThat(xmp.getFormat()).isEqualTo("image/dng"); 63 assertThat(xmp.getDocumentId()).isEqualTo( 64 "xmp.did:041dfd42-0b46-4302-918a-836fba5016ed"); 65 assertThat(xmp.getInstanceId()).isEqualTo( 66 "xmp.iid:041dfd42-0b46-4302-918a-836fba5016ed"); 67 assertThat(xmp.getOriginalDocumentId()).isEqualTo("3F9DD7A46B26513A7C35272F0D623A06"); 68 } 69 } 70 71 @Test testContainer_ValidTags()72 public void testContainer_ValidTags() throws Exception { 73 final Context context = InstrumentationRegistry.getInstrumentation().getContext(); 74 try (InputStream in = context.getResources().openRawResource(R.raw.lg_g4_iso_800_dng)) { 75 final ExifInterface exifInterface = new ExifInterface(in); 76 final XmpInterface xmp = XmpDataParser.createXmpInterface(exifInterface); 77 assertThat(xmp.getFormat()).isEqualTo("image/dng"); 78 assertThat(xmp.getDocumentId()).isEqualTo( 79 "xmp.did:041dfd42-0b46-4302-918a-836fba5016ed"); 80 assertThat(xmp.getInstanceId()).isEqualTo( 81 "xmp.iid:041dfd42-0b46-4302-918a-836fba5016ed"); 82 assertThat(xmp.getOriginalDocumentId()).isEqualTo("3F9DD7A46B26513A7C35272F0D623A06"); 83 } 84 } 85 86 @Test testContainer_ExifRedactionRanges()87 public void testContainer_ExifRedactionRanges() throws Exception { 88 final Context context = InstrumentationRegistry.getInstrumentation().getContext(); 89 try (InputStream in = context.getResources().openRawResource(R.raw.lg_g4_iso_800_jpg)) { 90 ExifInterface exif = new ExifInterface(in); 91 assertThat(exif.getAttributeRange(ExifInterface.TAG_XMP)[0]).isEqualTo(1809); 92 93 // Confirm redact range within entire file 94 // The XMP contents start at byte 1809. These are the file offsets. 95 final long[] expectedRanges = 96 new long[]{2625, 2675, 2678, 2730, 2733, 2792, 2795, 2841}; 97 assertThat(XmpDataParser.getRedactionRanges(exif).toArray()).isEqualTo(expectedRanges); 98 99 // Confirm redact range within local copy 100 final XmpInterface xmp = XmpDataParser.createXmpInterface(exif); 101 final String redactedXmp = new String(xmp.getRedactedXmp()); 102 assertThat(redactedXmp).doesNotContain("exif:GPSLatitude"); 103 assertThat(redactedXmp).doesNotContain("exif:GPSLongitude"); 104 assertThat(redactedXmp).contains("exif:ShutterSpeedValue"); 105 } 106 } 107 108 @Test testContainer_IsoRedactionRanges()109 public void testContainer_IsoRedactionRanges() throws Exception { 110 final File file = stageMp4File(R.raw.test_video_xmp); 111 final IsoInterface mp4 = IsoInterface.fromFile(file); 112 113 // Confirm redact range within entire file 114 // The XMP contents start at byte 30286. These are the file offsets. 115 final long[] expectedRanges = 116 new long[]{37299, 37349, 37352, 37404, 37407, 37466, 37469, 37515}; 117 assertThat(XmpDataParser.getRedactionRanges(mp4).toArray()).isEqualTo(expectedRanges); 118 119 XmpInterface xmpInterface = XmpDataParser.createXmpInterface(mp4); 120 // Confirm redact range within local copy 121 final String redactedXmp = new String(xmpInterface.getRedactedXmp()); 122 assertThat(redactedXmp).doesNotContain("exif:GPSLatitude"); 123 assertThat(redactedXmp).doesNotContain("exif:GPSLongitude"); 124 assertThat(redactedXmp).contains("exif:ShutterSpeedValue"); 125 } 126 127 @Test testContainer_IsoRedactionRanges_BadTagValue()128 public void testContainer_IsoRedactionRanges_BadTagValue() throws Exception { 129 // This file has some inner xml in the latitude tag. We should redact anyway. 130 final File file = stageMp4File(R.raw.test_video_xmp_bad_tag); 131 final IsoInterface mp4 = IsoInterface.fromFile(file); 132 133 // The XMP contents start at byte 30286. These are the file offsets. 134 final long[] expectedRanges = 135 new long[]{37299, 37349, 37352, 37404, 37407, 37466, 37469, 37515}; 136 assertThat(XmpDataParser.getRedactionRanges(mp4).toArray()).isEqualTo(expectedRanges); 137 } 138 139 @Test testContainer_IsoRedactionRanges_MalformedXml()140 public void testContainer_IsoRedactionRanges_MalformedXml() throws Exception { 141 // This file has malformed XML in the latitude tag. XML parsing will fail 142 final File file = stageMp4File(R.raw.test_video_xmp_malformed); 143 final IsoInterface mp4 = IsoInterface.fromFile(file); 144 assertThrows(IOException.class, () -> XmpDataParser.createXmpInterface(mp4)); 145 } 146 147 @Test testContainer_ExifRedactionRanges_MalformedXml()148 public void testContainer_ExifRedactionRanges_MalformedXml() throws Exception { 149 // This file has malformed XML in the latitude tag. XML parsing will fail 150 final File file = stageMp4File(R.raw.test_image_xmp_malformed); 151 final ExifInterface image = new ExifInterface(file); 152 assertThrows(IOException.class, () -> XmpDataParser.createXmpInterface(image)); 153 } 154 155 @Test testRedaction_IsoRedactionRanges_MalformedXml()156 public void testRedaction_IsoRedactionRanges_MalformedXml() throws Exception { 157 final File file = stageMp4File(R.raw.test_video_xmp_malformed); 158 final IsoInterface mp4 = IsoInterface.fromFile(file); 159 final long[] expectedRanges = new long[]{30286, 43483}; 160 final long[] actualRanges = XmpDataParser.getRedactionRanges(mp4).toArray(); 161 assertThat(actualRanges).isEqualTo(expectedRanges); 162 } 163 164 @Test testRedaction_ExifRedactionRanges_MalformedXml()165 public void testRedaction_ExifRedactionRanges_MalformedXml() throws Exception { 166 final File file = stageMp4File(R.raw.test_image_xmp_malformed); 167 ExifInterface exif = new ExifInterface(file); 168 final long[] expectedRanges = new long[]{289, 626}; 169 final long[] actualRanges = XmpDataParser.getRedactionRanges(exif).toArray(); 170 assertThat(actualRanges).isEqualTo(expectedRanges); 171 } 172 173 @Test testStream_LineOffsets()174 public void testStream_LineOffsets() throws Exception { 175 final String xml = 176 "<a:b xmlns:a='a' xmlns:c='c' c:d=''\n c:f='g'>\n <c:i>j</c:i>\n </a:b>"; 177 final InputStream xmlStream = new ByteArrayInputStream( 178 xml.getBytes(StandardCharsets.UTF_8)); 179 final XmpDataParser.ByteCountingInputStream stream = 180 new XmpDataParser.ByteCountingInputStream(xmlStream); 181 182 final long[] expectedElementOffsets = new long[]{46, 54, 61, 70}; 183 XmlPullParser parser = Xml.newPullParser(); 184 parser.setInput(stream, StandardCharsets.UTF_8.name()); 185 int type; 186 int i = 0; 187 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 188 if (type == XmlPullParser.START_TAG || type == XmlPullParser.END_TAG) { 189 assertThat(stream.getOffset(parser)).isEqualTo(expectedElementOffsets[i++]); 190 } 191 } 192 } 193 194 /** 195 * Exercise some random methods for code coverage purposes. 196 */ 197 @Test testStream_Misc()198 public void testStream_Misc() throws Exception { 199 final InputStream xmlStream = new ByteArrayInputStream( 200 "abcdefghijklmnoprstuvwxyz".getBytes(StandardCharsets.UTF_8)); 201 final XmpDataParser.ByteCountingInputStream stream = 202 new XmpDataParser.ByteCountingInputStream(xmlStream); 203 204 { 205 final byte[] buf = new byte[4]; 206 stream.read(buf); 207 assertThat(buf).isEqualTo("abcd".getBytes(StandardCharsets.UTF_8)); 208 } 209 { 210 final byte[] buf = new byte[4]; 211 stream.read(buf, 0, buf.length); 212 assertThat(buf).isEqualTo("efgh".getBytes(StandardCharsets.UTF_8)); 213 } 214 { 215 assertThat(stream.skip(4)).isEqualTo(4); 216 assertThat(stream.read()).isEqualTo('m'); 217 } 218 219 assertThat(stream.toString()).isNotNull(); 220 stream.close(); 221 } 222 } 223