Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 25e071c7 authored by Jin Seok Park's avatar Jin Seok Park Committed by Android (Google) Code Review
Browse files

Merge "Add constructor for standalone exif data"

parents d59d8add e7e18a44
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -23968,6 +23968,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);
+124 −53
Original line number Original line Diff line number Diff line
@@ -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);
@@ -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
@@ -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);
    }
    }


@@ -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
@@ -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;
@@ -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;
@@ -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);
@@ -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.
     *
     *
@@ -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
@@ -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
@@ -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;
@@ -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);
@@ -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);
        }
        }


@@ -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;