Loading api/current.txt +1 −0 Original line number Original line Diff line number Diff line Loading @@ -23964,6 +23964,7 @@ package android.media { ctor public ExifInterface(@NonNull String) throws java.io.IOException; ctor public ExifInterface(@NonNull String) throws java.io.IOException; ctor public ExifInterface(@NonNull java.io.FileDescriptor) throws java.io.IOException; ctor public ExifInterface(@NonNull java.io.FileDescriptor) throws java.io.IOException; ctor public ExifInterface(@NonNull java.io.InputStream) throws java.io.IOException; ctor public ExifInterface(@NonNull java.io.InputStream) throws java.io.IOException; method @NonNull public static android.media.ExifInterface fromStandalone(@NonNull java.io.InputStream) throws java.io.IOException; method public double getAltitude(double); method public double getAltitude(double); method @Nullable public String getAttribute(@NonNull String); method @Nullable public String getAttribute(@NonNull String); method @Nullable public byte[] getAttributeBytes(@NonNull String); method @Nullable public byte[] getAttributeBytes(@NonNull String); media/java/android/media/ExifInterface.java +124 −53 Original line number Original line Diff line number Diff line Loading @@ -1357,6 +1357,7 @@ public class ExifInterface { private AssetManager.AssetInputStream mAssetInputStream; private AssetManager.AssetInputStream mAssetInputStream; private boolean mIsInputStream; private boolean mIsInputStream; private int mMimeType; private int mMimeType; private boolean mIsStandalone; @UnsupportedAppUsage @UnsupportedAppUsage private final HashMap[] mAttributes = new HashMap[EXIF_TAGS.length]; private final HashMap[] mAttributes = new HashMap[EXIF_TAGS.length]; private Set<Integer> mHandledIfdOffsets = new HashSet<>(EXIF_TAGS.length); private Set<Integer> mHandledIfdOffsets = new HashSet<>(EXIF_TAGS.length); Loading Loading @@ -1411,6 +1412,7 @@ public class ExifInterface { if (fileDescriptor == null) { if (fileDescriptor == null) { throw new NullPointerException("fileDescriptor cannot be null"); throw new NullPointerException("fileDescriptor cannot be null"); } } mAssetInputStream = null; mAssetInputStream = null; mFilename = null; mFilename = null; // When FileDescriptor is duplicated and set to FileInputStream, ownership needs to be // When FileDescriptor is duplicated and set to FileInputStream, ownership needs to be Loading Loading @@ -1442,26 +1444,61 @@ public class ExifInterface { /** /** * Reads Exif tags from the specified image input stream. Attribute mutation is not supported * Reads Exif tags from the specified image input stream. Attribute mutation is not supported * for input streams. The given input stream will proceed its current position. Developers * for input streams. The given input stream will proceed from its current position. Developers * should close the input stream after use. * should close the input stream after use. */ */ public ExifInterface(@NonNull InputStream inputStream) throws IOException { public ExifInterface(@NonNull InputStream inputStream) throws IOException { this(inputStream, false); } /** * Reads Exif tags from the specified standalone input stream. Standalone data refers to Exif * data that exists by itself and is not contained in a file format such as jpeg or png. * The format of the standalone data must follow the below structure: * Exif Identifier Code ("Exif\0\0") + TIFF header + IFD data * See JEITA CP-3451C Section 4.5.2 and 4.5.4 specifications for more details. * <p> * Attribute mutation is not supported for this constructor. The given input stream will proceed * from its current position. Developers should close the input stream after use. This * constructor is not intended to be used with an input stream that performs any networking * operations. * * @throws IOException if the data does not follow the aforementioned structure. */ @NonNull public static ExifInterface fromStandalone(@NonNull InputStream inputStream) throws IOException { if (isStandalone(inputStream)) { return new ExifInterface(inputStream, true); } throw new IOException("Given data does not follow the structure of a standalone exif " + "data."); } private ExifInterface(@NonNull InputStream inputStream, boolean isFromStandalone) throws IOException { if (inputStream == null) { if (inputStream == null) { throw new NullPointerException("inputStream cannot be null"); throw new NullPointerException("inputStream cannot be null"); } } mFilename = null; mFilename = null; if (isFromStandalone) { mIsStandalone = true; mAssetInputStream = null; mSeekableFileDescriptor = null; } else { if (inputStream instanceof AssetManager.AssetInputStream) { if (inputStream instanceof AssetManager.AssetInputStream) { mAssetInputStream = (AssetManager.AssetInputStream) inputStream; mAssetInputStream = (AssetManager.AssetInputStream) inputStream; mSeekableFileDescriptor = null; mSeekableFileDescriptor = null; } else if (inputStream instanceof FileInputStream } else if (inputStream instanceof FileInputStream && isSeekableFD(((FileInputStream) inputStream).getFD())) { && (isSeekableFD(((FileInputStream) inputStream).getFD()))) { mAssetInputStream = null; mAssetInputStream = null; mSeekableFileDescriptor = ((FileInputStream) inputStream).getFD(); mSeekableFileDescriptor = ((FileInputStream) inputStream).getFD(); } else { } else { mAssetInputStream = null; mAssetInputStream = null; mSeekableFileDescriptor = null; mSeekableFileDescriptor = null; } } mIsInputStream = true; } loadAttributes(inputStream); loadAttributes(inputStream); } } Loading Loading @@ -1798,12 +1835,15 @@ public class ExifInterface { } } // Check file type // Check file type if (!mIsStandalone) { in = new BufferedInputStream(in, SIGNATURE_CHECK_SIZE); in = new BufferedInputStream(in, SIGNATURE_CHECK_SIZE); mMimeType = getMimeType((BufferedInputStream) in); mMimeType = getMimeType((BufferedInputStream) in); } // Create byte-ordered input stream // Create byte-ordered input stream ByteOrderedDataInputStream inputStream = new ByteOrderedDataInputStream(in); ByteOrderedDataInputStream inputStream = new ByteOrderedDataInputStream(in); if (!mIsStandalone) { switch (mMimeType) { switch (mMimeType) { case IMAGE_TYPE_JPEG: { case IMAGE_TYPE_JPEG: { getJpegAttributes(inputStream, 0, IFD_TYPE_PRIMARY); // 0 is offset getJpegAttributes(inputStream, 0, IFD_TYPE_PRIMARY); // 0 is offset Loading Loading @@ -1844,6 +1884,9 @@ public class ExifInterface { break; break; } } } } } else { getStandaloneAttributes(inputStream); } // Set thumbnail image offset and length // Set thumbnail image offset and length setThumbnailData(inputStream); setThumbnailData(inputStream); mIsSupportedFile = true; mIsSupportedFile = true; Loading Loading @@ -2124,6 +2167,9 @@ public class ExifInterface { } } if (mHasThumbnail) { if (mHasThumbnail) { if (mIsStandalone) { return new long[] { mThumbnailOffset + mExifOffset, mThumbnailLength }; } return new long[] { mThumbnailOffset, mThumbnailLength }; return new long[] { mThumbnailOffset, mThumbnailLength }; } else { } else { return null; return null; Loading Loading @@ -2368,6 +2414,7 @@ public class ExifInterface { // Checks the type of image file // Checks the type of image file private int getMimeType(BufferedInputStream in) throws IOException { private int getMimeType(BufferedInputStream in) throws IOException { // TODO (b/142218289): Need to handle case where input stream does not support mark in.mark(SIGNATURE_CHECK_SIZE); in.mark(SIGNATURE_CHECK_SIZE); byte[] signatureCheckBytes = new byte[SIGNATURE_CHECK_SIZE]; byte[] signatureCheckBytes = new byte[SIGNATURE_CHECK_SIZE]; in.read(signatureCheckBytes); in.read(signatureCheckBytes); Loading Loading @@ -2562,6 +2609,18 @@ public class ExifInterface { return true; return true; } } private static boolean isStandalone(InputStream inputStream) throws IOException { byte[] signatureCheckBytes = new byte[IDENTIFIER_EXIF_APP1.length]; inputStream.read(signatureCheckBytes); for (int i = 0; i < IDENTIFIER_EXIF_APP1.length; i++) { if (signatureCheckBytes[i] != IDENTIFIER_EXIF_APP1[i]) { return false; } } return true; } /** /** * Loads EXIF attributes from a JPEG input stream. * Loads EXIF attributes from a JPEG input stream. * * Loading Loading @@ -2633,7 +2692,6 @@ public class ExifInterface { final long offset = start + IDENTIFIER_EXIF_APP1.length; final long offset = start + IDENTIFIER_EXIF_APP1.length; final byte[] value = Arrays.copyOfRange(bytes, final byte[] value = Arrays.copyOfRange(bytes, IDENTIFIER_EXIF_APP1.length, bytes.length); IDENTIFIER_EXIF_APP1.length, bytes.length); readExifSegment(value, imageType); readExifSegment(value, imageType); // Save offset values for handleThumbnailFromJfif() function // Save offset values for handleThumbnailFromJfif() function Loading Loading @@ -2953,6 +3011,16 @@ public class ExifInterface { } } } } private void getStandaloneAttributes(ByteOrderedDataInputStream in) throws IOException { // TODO: Need to handle potential OutOfMemoryError byte[] data = new byte[in.available()]; in.readFully(data); readExifSegment(data, IFD_TYPE_PRIMARY); // Save offset values for handleThumbnailFromJfif() function mExifOffset = IDENTIFIER_EXIF_APP1.length; } /** /** * ORF files contains a primary image data and a MakerNote data that contains preview/thumbnail * ORF files contains a primary image data and a MakerNote data that contains preview/thumbnail * images. Both data takes the form of IFDs and can therefore be read with the * images. Both data takes the form of IFDs and can therefore be read with the Loading Loading @@ -3606,8 +3674,6 @@ public class ExifInterface { int thumbnailOffset = jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder); int thumbnailOffset = jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder); int thumbnailLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder); int thumbnailLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder); // The following code limits the size of thumbnail size not to overflow EXIF data area. thumbnailLength = Math.min(thumbnailLength, in.getLength() - thumbnailOffset); if (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_RAF if (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_RAF || mMimeType == IMAGE_TYPE_RW2) { || mMimeType == IMAGE_TYPE_RW2) { thumbnailOffset += mExifOffset; thumbnailOffset += mExifOffset; Loading @@ -3615,6 +3681,9 @@ public class ExifInterface { // Update offset value since RAF files have IFD data preceding MakerNote data. // Update offset value since RAF files have IFD data preceding MakerNote data. thumbnailOffset += mOrfMakerNoteOffset; thumbnailOffset += mOrfMakerNoteOffset; } } // The following code limits the size of thumbnail size not to overflow EXIF data area. thumbnailLength = Math.min(thumbnailLength, in.getLength() - thumbnailOffset); if (DEBUG) { if (DEBUG) { Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset + ", length: " + thumbnailLength); + ", length: " + thumbnailLength); Loading Loading @@ -4123,6 +4192,7 @@ public class ExifInterface { mDataInputStream = new DataInputStream(in); mDataInputStream = new DataInputStream(in); mLength = mDataInputStream.available(); mLength = mDataInputStream.available(); mPosition = 0; mPosition = 0; // TODO (b/142218289): Need to handle case where input stream does not support mark mDataInputStream.mark(mLength); mDataInputStream.mark(mLength); } } Loading @@ -4138,6 +4208,7 @@ public class ExifInterface { if (mPosition > byteCount) { if (mPosition > byteCount) { mPosition = 0; mPosition = 0; mDataInputStream.reset(); mDataInputStream.reset(); // TODO (b/142218289): Need to handle case where input stream does not support mark mDataInputStream.mark(mLength); mDataInputStream.mark(mLength); } else { } else { byteCount -= mPosition; byteCount -= mPosition; Loading Loading
api/current.txt +1 −0 Original line number Original line Diff line number Diff line Loading @@ -23964,6 +23964,7 @@ package android.media { ctor public ExifInterface(@NonNull String) throws java.io.IOException; ctor public ExifInterface(@NonNull String) throws java.io.IOException; ctor public ExifInterface(@NonNull java.io.FileDescriptor) throws java.io.IOException; ctor public ExifInterface(@NonNull java.io.FileDescriptor) throws java.io.IOException; ctor public ExifInterface(@NonNull java.io.InputStream) throws java.io.IOException; ctor public ExifInterface(@NonNull java.io.InputStream) throws java.io.IOException; method @NonNull public static android.media.ExifInterface fromStandalone(@NonNull java.io.InputStream) throws java.io.IOException; method public double getAltitude(double); method public double getAltitude(double); method @Nullable public String getAttribute(@NonNull String); method @Nullable public String getAttribute(@NonNull String); method @Nullable public byte[] getAttributeBytes(@NonNull String); method @Nullable public byte[] getAttributeBytes(@NonNull String);
media/java/android/media/ExifInterface.java +124 −53 Original line number Original line Diff line number Diff line Loading @@ -1357,6 +1357,7 @@ public class ExifInterface { private AssetManager.AssetInputStream mAssetInputStream; private AssetManager.AssetInputStream mAssetInputStream; private boolean mIsInputStream; private boolean mIsInputStream; private int mMimeType; private int mMimeType; private boolean mIsStandalone; @UnsupportedAppUsage @UnsupportedAppUsage private final HashMap[] mAttributes = new HashMap[EXIF_TAGS.length]; private final HashMap[] mAttributes = new HashMap[EXIF_TAGS.length]; private Set<Integer> mHandledIfdOffsets = new HashSet<>(EXIF_TAGS.length); private Set<Integer> mHandledIfdOffsets = new HashSet<>(EXIF_TAGS.length); Loading Loading @@ -1411,6 +1412,7 @@ public class ExifInterface { if (fileDescriptor == null) { if (fileDescriptor == null) { throw new NullPointerException("fileDescriptor cannot be null"); throw new NullPointerException("fileDescriptor cannot be null"); } } mAssetInputStream = null; mAssetInputStream = null; mFilename = null; mFilename = null; // When FileDescriptor is duplicated and set to FileInputStream, ownership needs to be // When FileDescriptor is duplicated and set to FileInputStream, ownership needs to be Loading Loading @@ -1442,26 +1444,61 @@ public class ExifInterface { /** /** * Reads Exif tags from the specified image input stream. Attribute mutation is not supported * Reads Exif tags from the specified image input stream. Attribute mutation is not supported * for input streams. The given input stream will proceed its current position. Developers * for input streams. The given input stream will proceed from its current position. Developers * should close the input stream after use. * should close the input stream after use. */ */ public ExifInterface(@NonNull InputStream inputStream) throws IOException { public ExifInterface(@NonNull InputStream inputStream) throws IOException { this(inputStream, false); } /** * Reads Exif tags from the specified standalone input stream. Standalone data refers to Exif * data that exists by itself and is not contained in a file format such as jpeg or png. * The format of the standalone data must follow the below structure: * Exif Identifier Code ("Exif\0\0") + TIFF header + IFD data * See JEITA CP-3451C Section 4.5.2 and 4.5.4 specifications for more details. * <p> * Attribute mutation is not supported for this constructor. The given input stream will proceed * from its current position. Developers should close the input stream after use. This * constructor is not intended to be used with an input stream that performs any networking * operations. * * @throws IOException if the data does not follow the aforementioned structure. */ @NonNull public static ExifInterface fromStandalone(@NonNull InputStream inputStream) throws IOException { if (isStandalone(inputStream)) { return new ExifInterface(inputStream, true); } throw new IOException("Given data does not follow the structure of a standalone exif " + "data."); } private ExifInterface(@NonNull InputStream inputStream, boolean isFromStandalone) throws IOException { if (inputStream == null) { if (inputStream == null) { throw new NullPointerException("inputStream cannot be null"); throw new NullPointerException("inputStream cannot be null"); } } mFilename = null; mFilename = null; if (isFromStandalone) { mIsStandalone = true; mAssetInputStream = null; mSeekableFileDescriptor = null; } else { if (inputStream instanceof AssetManager.AssetInputStream) { if (inputStream instanceof AssetManager.AssetInputStream) { mAssetInputStream = (AssetManager.AssetInputStream) inputStream; mAssetInputStream = (AssetManager.AssetInputStream) inputStream; mSeekableFileDescriptor = null; mSeekableFileDescriptor = null; } else if (inputStream instanceof FileInputStream } else if (inputStream instanceof FileInputStream && isSeekableFD(((FileInputStream) inputStream).getFD())) { && (isSeekableFD(((FileInputStream) inputStream).getFD()))) { mAssetInputStream = null; mAssetInputStream = null; mSeekableFileDescriptor = ((FileInputStream) inputStream).getFD(); mSeekableFileDescriptor = ((FileInputStream) inputStream).getFD(); } else { } else { mAssetInputStream = null; mAssetInputStream = null; mSeekableFileDescriptor = null; mSeekableFileDescriptor = null; } } mIsInputStream = true; } loadAttributes(inputStream); loadAttributes(inputStream); } } Loading Loading @@ -1798,12 +1835,15 @@ public class ExifInterface { } } // Check file type // Check file type if (!mIsStandalone) { in = new BufferedInputStream(in, SIGNATURE_CHECK_SIZE); in = new BufferedInputStream(in, SIGNATURE_CHECK_SIZE); mMimeType = getMimeType((BufferedInputStream) in); mMimeType = getMimeType((BufferedInputStream) in); } // Create byte-ordered input stream // Create byte-ordered input stream ByteOrderedDataInputStream inputStream = new ByteOrderedDataInputStream(in); ByteOrderedDataInputStream inputStream = new ByteOrderedDataInputStream(in); if (!mIsStandalone) { switch (mMimeType) { switch (mMimeType) { case IMAGE_TYPE_JPEG: { case IMAGE_TYPE_JPEG: { getJpegAttributes(inputStream, 0, IFD_TYPE_PRIMARY); // 0 is offset getJpegAttributes(inputStream, 0, IFD_TYPE_PRIMARY); // 0 is offset Loading Loading @@ -1844,6 +1884,9 @@ public class ExifInterface { break; break; } } } } } else { getStandaloneAttributes(inputStream); } // Set thumbnail image offset and length // Set thumbnail image offset and length setThumbnailData(inputStream); setThumbnailData(inputStream); mIsSupportedFile = true; mIsSupportedFile = true; Loading Loading @@ -2124,6 +2167,9 @@ public class ExifInterface { } } if (mHasThumbnail) { if (mHasThumbnail) { if (mIsStandalone) { return new long[] { mThumbnailOffset + mExifOffset, mThumbnailLength }; } return new long[] { mThumbnailOffset, mThumbnailLength }; return new long[] { mThumbnailOffset, mThumbnailLength }; } else { } else { return null; return null; Loading Loading @@ -2368,6 +2414,7 @@ public class ExifInterface { // Checks the type of image file // Checks the type of image file private int getMimeType(BufferedInputStream in) throws IOException { private int getMimeType(BufferedInputStream in) throws IOException { // TODO (b/142218289): Need to handle case where input stream does not support mark in.mark(SIGNATURE_CHECK_SIZE); in.mark(SIGNATURE_CHECK_SIZE); byte[] signatureCheckBytes = new byte[SIGNATURE_CHECK_SIZE]; byte[] signatureCheckBytes = new byte[SIGNATURE_CHECK_SIZE]; in.read(signatureCheckBytes); in.read(signatureCheckBytes); Loading Loading @@ -2562,6 +2609,18 @@ public class ExifInterface { return true; return true; } } private static boolean isStandalone(InputStream inputStream) throws IOException { byte[] signatureCheckBytes = new byte[IDENTIFIER_EXIF_APP1.length]; inputStream.read(signatureCheckBytes); for (int i = 0; i < IDENTIFIER_EXIF_APP1.length; i++) { if (signatureCheckBytes[i] != IDENTIFIER_EXIF_APP1[i]) { return false; } } return true; } /** /** * Loads EXIF attributes from a JPEG input stream. * Loads EXIF attributes from a JPEG input stream. * * Loading Loading @@ -2633,7 +2692,6 @@ public class ExifInterface { final long offset = start + IDENTIFIER_EXIF_APP1.length; final long offset = start + IDENTIFIER_EXIF_APP1.length; final byte[] value = Arrays.copyOfRange(bytes, final byte[] value = Arrays.copyOfRange(bytes, IDENTIFIER_EXIF_APP1.length, bytes.length); IDENTIFIER_EXIF_APP1.length, bytes.length); readExifSegment(value, imageType); readExifSegment(value, imageType); // Save offset values for handleThumbnailFromJfif() function // Save offset values for handleThumbnailFromJfif() function Loading Loading @@ -2953,6 +3011,16 @@ public class ExifInterface { } } } } private void getStandaloneAttributes(ByteOrderedDataInputStream in) throws IOException { // TODO: Need to handle potential OutOfMemoryError byte[] data = new byte[in.available()]; in.readFully(data); readExifSegment(data, IFD_TYPE_PRIMARY); // Save offset values for handleThumbnailFromJfif() function mExifOffset = IDENTIFIER_EXIF_APP1.length; } /** /** * ORF files contains a primary image data and a MakerNote data that contains preview/thumbnail * ORF files contains a primary image data and a MakerNote data that contains preview/thumbnail * images. Both data takes the form of IFDs and can therefore be read with the * images. Both data takes the form of IFDs and can therefore be read with the Loading Loading @@ -3606,8 +3674,6 @@ public class ExifInterface { int thumbnailOffset = jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder); int thumbnailOffset = jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder); int thumbnailLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder); int thumbnailLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder); // The following code limits the size of thumbnail size not to overflow EXIF data area. thumbnailLength = Math.min(thumbnailLength, in.getLength() - thumbnailOffset); if (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_RAF if (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_RAF || mMimeType == IMAGE_TYPE_RW2) { || mMimeType == IMAGE_TYPE_RW2) { thumbnailOffset += mExifOffset; thumbnailOffset += mExifOffset; Loading @@ -3615,6 +3681,9 @@ public class ExifInterface { // Update offset value since RAF files have IFD data preceding MakerNote data. // Update offset value since RAF files have IFD data preceding MakerNote data. thumbnailOffset += mOrfMakerNoteOffset; thumbnailOffset += mOrfMakerNoteOffset; } } // The following code limits the size of thumbnail size not to overflow EXIF data area. thumbnailLength = Math.min(thumbnailLength, in.getLength() - thumbnailOffset); if (DEBUG) { if (DEBUG) { Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset + ", length: " + thumbnailLength); + ", length: " + thumbnailLength); Loading Loading @@ -4123,6 +4192,7 @@ public class ExifInterface { mDataInputStream = new DataInputStream(in); mDataInputStream = new DataInputStream(in); mLength = mDataInputStream.available(); mLength = mDataInputStream.available(); mPosition = 0; mPosition = 0; // TODO (b/142218289): Need to handle case where input stream does not support mark mDataInputStream.mark(mLength); mDataInputStream.mark(mLength); } } Loading @@ -4138,6 +4208,7 @@ public class ExifInterface { if (mPosition > byteCount) { if (mPosition > byteCount) { mPosition = 0; mPosition = 0; mDataInputStream.reset(); mDataInputStream.reset(); // TODO (b/142218289): Need to handle case where input stream does not support mark mDataInputStream.mark(mLength); mDataInputStream.mark(mLength); } else { } else { byteCount -= mPosition; byteCount -= mPosition; Loading