ExifInterface: do not throw an Exception when XMP

In JPEG, an APP1 section can contain EXIF or XMP. The old code throws an
Exception when XMP APP1 segment is showns.

Bug: 27580432
Change-Id: If42197ea0c33ec4446302c969b42afd3d4b4e3c3
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index e35fcf6..2b944b6 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -605,8 +605,9 @@
     // not only getting information from EXIF but also from some JPEG special segments such as
     // MARKER_COM for user comment and MARKER_SOFx for image width and height.
 
-    // Identifier for APP1 segment in JPEG
-    private static final byte[] IDENTIFIER_APP1 = "Exif\0\0".getBytes(Charset.forName("US-ASCII"));
+    // Identifier for EXIF APP1 segment in JPEG
+    private static final byte[] IDENTIFIER_EXIF_APP1 =
+            "Exif\0\0".getBytes(Charset.forName("US-ASCII"));
     // JPEG segment markers, that each marker consumes two bytes beginning with 0xff and ending with
     // the indicator. There is no SOF4, SOF8, SOF16 markers in JPEG and SOFx markers indicates start
     // of frame(baseline DCT) and the image size info exists in its beginning part.
@@ -1119,7 +1120,9 @@
         String time = getAttribute(TAG_GPS_TIMESTAMP);
         if (date == null || time == null
                 || (!sNonZeroTimePattern.matcher(date).matches()
-                && !sNonZeroTimePattern.matcher(time).matches())) return -1;
+                && !sNonZeroTimePattern.matcher(time).matches())) {
+            return -1;
+        }
 
         String dateTimeString = date + ' ' + time;
 
@@ -1170,7 +1173,6 @@
         DataInputStream dataInputStream = new DataInputStream(inputStream);
         byte marker;
         int bytesRead = 0;
-        ++bytesRead;
         if ((marker = dataInputStream.readByte()) != MARKER) {
             throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff));
         }
@@ -1178,8 +1180,8 @@
         if (dataInputStream.readByte() != MARKER_SOI) {
             throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff));
         }
+        ++bytesRead;
         while (true) {
-            ++bytesRead;
             marker = dataInputStream.readByte();
             if (marker != MARKER) {
                 throw new IOException("Invalid marker:" + Integer.toHexString(marker & 0xff));
@@ -1189,36 +1191,40 @@
             if (DEBUG) {
                 Log.d(TAG, "Found JPEG segment indicator: " + Integer.toHexString(marker & 0xff));
             }
+            ++bytesRead;
 
             // EOI indicates the end of an image and in case of SOS, JPEG image stream starts and
             // the image data will terminate right after.
             if (marker == MARKER_EOI || marker == MARKER_SOS) {
                 break;
             }
-            bytesRead += 2;
             int length = dataInputStream.readUnsignedShort() - 2;
+            bytesRead += 2;
+            if (DEBUG) {
+                Log.d(TAG, "JPEG segment: " + marker + " (length: " + (length + 2) + ")");
+            }
             if (length < 0) {
                 throw new IOException("Invalid length");
             }
-            bytesRead += length;
             switch (marker) {
                 case MARKER_APP1: {
                     if (DEBUG) {
                         Log.d(TAG, "MARKER_APP1");
                     }
-                    bytesRead -= length;
                     if (length < 6) {
-                        throw new IOException("Invalid exif");
+                        // Skip if it's not an EXIF APP1 segment.
+                        break;
                     }
                     byte[] identifier = new byte[6];
                     if (inputStream.read(identifier) != 6) {
                         throw new IOException("Invalid exif");
                     }
-                    if (!Arrays.equals(identifier, IDENTIFIER_APP1)) {
-                        throw new IOException("Invalid app1 identifier");
-                    }
                     bytesRead += 6;
                     length -= 6;
+                    if (!Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
+                        // Skip if it's not an EXIF APP1 segment.
+                        break;
+                    }
                     if (length <= 0) {
                         throw new IOException("Invalid exif");
                     }
@@ -1240,6 +1246,7 @@
                     if (dataInputStream.read(bytes) != length) {
                         throw new IOException("Invalid exif");
                     }
+                    length = 0;
                     setAttribute("UserComment", new String(bytes, Charset.forName("US-ASCII")));
                     break;
                 }
@@ -1273,6 +1280,7 @@
                 throw new IOException("Invalid length");
             }
             dataInputStream.skipBytes(length);
+            bytesRead += length;
         }
     }
 
@@ -1286,68 +1294,84 @@
         }
         DataInputStream dataInputStream = new DataInputStream(inputStream);
         ExifDataOutputStream dataOutputStream = new ExifDataOutputStream(outputStream);
-        int bytesRead = 0;
-        ++bytesRead;
         if (dataInputStream.readByte() != MARKER) {
             throw new IOException("Invalid marker");
         }
         dataOutputStream.writeByte(MARKER);
-        ++bytesRead;
         if (dataInputStream.readByte() != MARKER_SOI) {
             throw new IOException("Invalid marker");
         }
         dataOutputStream.writeByte(MARKER_SOI);
 
+        // Write EXIF APP1 segment
+        dataOutputStream.writeByte(MARKER);
+        dataOutputStream.writeByte(MARKER_APP1);
+        writeExifSegment(dataOutputStream, 6);
+
         byte[] bytes = new byte[4096];
 
         while (true) {
-            ++bytesRead;
             if (dataInputStream.readByte() != MARKER) {
                 throw new IOException("Invalid marker");
             }
-            dataOutputStream.writeByte(MARKER);
-            ++bytesRead;
             byte marker = dataInputStream.readByte();
-            dataOutputStream.writeByte(marker);
             switch (marker) {
                 case MARKER_APP1: {
-                    // Rewrite EXIF segment
                     int length = dataInputStream.readUnsignedShort() - 2;
                     if (length < 0) {
                         throw new IOException("Invalid length");
                     }
-                    bytesRead += 2;
+                    byte[] identifier = new byte[6];
+                    if (length >= 6) {
+                        if (dataInputStream.read(identifier) != 6) {
+                            throw new IOException("Invalid exif");
+                        }
+                        if (Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
+                            // Skip the original EXIF APP1 segment.
+                            if (dataInputStream.skip(length - 6) != length - 6) {
+                                throw new IOException("Invalid length");
+                            }
+                            break;
+                        }
+                    }
+                    // Copy non-EXIF APP1 segment.
+                    dataOutputStream.writeUnsignedShort(length + 2);
+                    if (length >= 6) {
+                        length -= 6;
+                        dataOutputStream.write(identifier);
+                    }
                     int read;
-                    while ((read = dataInputStream.read(
-                            bytes, 0, Math.min(length, bytes.length))) > 0) {
+                    while (length > 0 && (read = dataInputStream.read(
+                            bytes, 0, Math.min(length, bytes.length))) >= 0) {
+                        dataOutputStream.write(bytes, 0, read);
                         length -= read;
                     }
-                    bytesRead += length;
-                    writeExifSegment(dataOutputStream, bytesRead);
                     break;
                 }
                 case MARKER_EOI:
                 case MARKER_SOS: {
+                    dataOutputStream.writeByte(MARKER);
+                    dataOutputStream.writeByte(marker);
                     // Copy all the remaining data
                     Streams.copy(dataInputStream, dataOutputStream);
                     return;
                 }
                 default: {
                     // Copy JPEG segment
+                    dataOutputStream.writeByte(MARKER);
+                    dataOutputStream.writeByte(marker);
                     int length = dataInputStream.readUnsignedShort();
                     dataOutputStream.writeUnsignedShort(length);
+                    length -= 2;
                     if (length < 0) {
                         throw new IOException("Invalid length");
                     }
-                    length -= 2;
-                    bytesRead += 2;
                     int read;
-                    while ((read = dataInputStream.read(
-                            bytes, 0, Math.min(length, bytes.length))) > 0) {
+                    while (length > 0 && (read = dataInputStream.read(
+                            bytes, 0, Math.min(length, bytes.length))) >= 0) {
                         dataOutputStream.write(bytes, 0, read);
                         length -= read;
                     }
-                    bytesRead += length;
                     break;
                 }
             }
@@ -1918,7 +1942,7 @@
 
         // Write TIFF Headers. See JEITA CP-3451C Table 1. page 10.
         dataOutputStream.writeUnsignedShort(totalSize);
-        dataOutputStream.write(IDENTIFIER_APP1);
+        dataOutputStream.write(IDENTIFIER_EXIF_APP1);
         dataOutputStream.writeShort(BYTE_ALIGN_MM);
         dataOutputStream.writeUnsignedShort(0x2a);
         dataOutputStream.writeUnsignedInt(8);