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

Commit e7e18a44 authored by Jin Seok Park's avatar Jin Seok Park
Browse files

Add constructor for standalone exif data

Bug: 140250800
Test: Run cts ExifInterfaceTest
Change-Id: Ic2d31e5c5e6e9ca91c384cf87fc56e5940364509
parent 9b316d9c
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -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);
+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;