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

Commit dc751a38 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add provider support for metadata extraction"

parents ea3ac27b b6505157
Loading
Loading
Loading
Loading
+102 −2
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.provider;

import static android.net.TrafficStats.KB_IN_BYTES;
import static android.system.OsConstants.SEEK_SET;

import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
@@ -28,7 +29,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
@@ -44,7 +44,6 @@ import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener;
import android.os.Parcelable;
import android.os.ParcelableException;
import android.os.RemoteException;
@@ -182,6 +181,15 @@ public final class DocumentsContract {
    /** {@hide} */
    public static final String PACKAGE_DOCUMENTS_UI = "com.android.documentsui";

    /** {@hide} */
    public static final String METADATA_TYPES = "android:documentMetadataType";

    /** {@hide} */
    public static final String METADATA_EXIF = "android:documentExif";

    /** {@hide} */
    public static final String EXTRA_METADATA_TAGS = "android:documentMetadataTags";

    /**
     * Constants related to a document, including {@link Cursor} column names
     * and flags.
@@ -442,6 +450,13 @@ public final class DocumentsContract {
         * @hide
         */
        public static final int FLAG_PARTIAL = 1 << 16;

        /**
         * Flag indicating that a document has available metadata that can be read
         * using DocumentsContract#getDocumentMetadata
         * @hide
         */
        public static final int FLAG_SUPPORTS_METADATA = 1 << 17;
    }

    /**
@@ -706,6 +721,8 @@ public final class DocumentsContract {
    public static final String METHOD_FIND_DOCUMENT_PATH = "android:findDocumentPath";
    /** {@hide} */
    public static final String METHOD_CREATE_WEB_LINK_INTENT = "android:createWebLinkIntent";
    /** {@hide} */
    public static final String METHOD_GET_DOCUMENT_METADATA = "android:getDocumentMetadata";

    /** {@hide} */
    public static final String EXTRA_PARENT_URI = "parentUri";
@@ -1377,6 +1394,89 @@ public final class DocumentsContract {
        client.call(METHOD_EJECT_ROOT, null, in);
    }

    /**
     * Returns metadata associated with the document. The type of metadata returned
     * is specific to the document type. For example image files will largely return EXIF
     * metadata.
     *
     * <p>The returned {@link Bundle} will contain zero or more entries.
     * <p>Each entry represents a specific type of metadata.
     *
     * <p>if tags == null, then a list of default tags will be used.
     *
     * @param documentUri a Document URI
     * @param tags an array of keys to choose which data are added to the Bundle. If the Document
     *             is a JPG or ExifInterface compatible, send keys from {@link ExifInterface}.
     *             If tags are null, a set of default tags will be used. If the tags don't
     *             match with any relevant data, they will not be added to the Bundle.
     * @return a Bundle of Bundles. If metadata exists within the Bundle, there will also
     * be a String under DocumentsContract.METADATA_TYPES that will return a String[] of the
     * types of metadata gathered.
     *
     * <pre><code>
     *     Bundle metadata = DocumentsContract.getDocumentMetadata(resolver, imageDocUri, tags);
     *     int imageLength = metadata.getInt(ExifInterface.TAG_IMAGE_LENGTH);
     * </code></pre>
     *
     * {@hide}
     */
    public static Bundle getDocumentMetadata(ContentResolver resolver, Uri documentUri,
            @Nullable String[] tags)
            throws FileNotFoundException {
        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
                documentUri.getAuthority());

        try {
            return getDocumentMetadata(client, documentUri, tags);
        } catch (Exception e) {
            Log.w(TAG, "Failed to get document metadata");
            rethrowIfNecessary(resolver, e);
            return null;
        } finally {
            ContentProviderClient.releaseQuietly(client);
        }
    }

    /**
     * Returns metadata associated with the document. The type of metadata returned
     * is specific to the document type. For example image files will largely return EXIF
     * metadata.
     *
     * <p>The returned {@link Bundle} will contain zero or more entries.
     * <p>Each entry represents a specific type of metadata.
     *
     * <p>if tags == null, then a list of default tags will be used.
     *
     * @param documentUri a Document URI
     * @param tags an array of keys to choose which data are added to the Bundle. If the Document
     *             is a JPG or ExifInterface compatible, send keys from {@link ExifInterface}.
     *             If tags are null, a set of default tags will be used. If the tags don't
     *             match with any relevant data, they will not be added to the Bundle.
     * @return a Bundle of Bundles. If metadata exists within the Bundle, there will also
     * be a String under DocumentsContract.METADATA_TYPES that will return a String[] of the
     * types of metadata gathered.
     *
     * <pre><code>
     *     Bundle metadata = DocumentsContract.getDocumentMetadata(client, imageDocUri, tags);
     *     int imageLength = metadata.getInt(ExifInterface.TAG_IMAGE_LENGTH);
     * </code></pre>
     *
     * {@hide}
     */
    public static Bundle getDocumentMetadata(ContentProviderClient client,
            Uri documentUri, @Nullable String[] tags) throws RemoteException {
        final Bundle in = new Bundle();
        in.putParcelable(EXTRA_URI, documentUri);
        in.putStringArray(EXTRA_METADATA_TAGS, tags);

        final Bundle out = client.call(METHOD_GET_DOCUMENT_METADATA, null, in);

        if (out == null) {
            throw new RemoteException("Failed to get a response from getDocumentMetadata");
        }
        return out;
    }

    /**
     * Finds the canonical path from the top of the document tree.
     *
+10 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.provider.DocumentsContract.METHOD_CREATE_WEB_LINK_INTENT;
import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_EJECT_ROOT;
import static android.provider.DocumentsContract.METHOD_FIND_DOCUMENT_PATH;
import static android.provider.DocumentsContract.METHOD_GET_DOCUMENT_METADATA;
import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_REMOVE_DOCUMENT;
@@ -55,9 +56,7 @@ import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener;
import android.os.ParcelableException;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Path;
@@ -627,6 +626,12 @@ public abstract class DocumentsProvider extends ContentProvider {
        throw new UnsupportedOperationException("Eject not supported");
    }

    /** {@hide} */
    public @Nullable Bundle getDocumentMetadata(String documentId, @Nullable String[] tags)
            throws FileNotFoundException {
        throw new UnsupportedOperationException("Metadata not supported");
    }

    /**
     * Return concrete MIME type of the requested document. Must match the value
     * of {@link Document#COLUMN_MIME_TYPE} for this document. The default
@@ -1136,6 +1141,9 @@ public abstract class DocumentsProvider extends ContentProvider {
            }

            out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
        } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) {
            return getDocumentMetadata(
                    documentId, extras.getStringArray(DocumentsContract.EXTRA_METADATA_TAGS));
        } else {
            throw new UnsupportedOperationException("Method not supported " + method);
        }
+285 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.provider;

import android.annotation.Nullable;
import android.media.ExifInterface;
import android.os.Bundle;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Class providing support for extracting metadata from a file as a
 * {@link Bundle} suitable for use with {@link DocumentsContract#getDocumentMetadata}.
 * <p>Currently only EXIF data is supported.
 * <p>TODO: Add support for common video and audio types, as well as PDF files.
 * {@hide}
 */
public final class MetadataReader {

    private MetadataReader() {
    }

    private static final String[] DEFAULT_EXIF_TAGS = {
            ExifInterface.TAG_IMAGE_WIDTH,
            ExifInterface.TAG_IMAGE_LENGTH,
            ExifInterface.TAG_DATETIME,
            ExifInterface.TAG_GPS_LATITUDE,
            ExifInterface.TAG_GPS_LONGITUDE,
            ExifInterface.TAG_MAKE,
            ExifInterface.TAG_MODEL,
            ExifInterface.TAG_APERTURE,
            ExifInterface.TAG_SHUTTER_SPEED_VALUE
    };

    private static final Map<String, Integer> TYPE_MAPPING = new HashMap<>();
    private static final String[] ALL_KNOWN_EXIF_KEYS;
    private static final int TYPE_INT = 0;
    private static final int TYPE_DOUBLE = 1;
    private static final int TYPE_STRING = 2;

    static {
        // TODO: Move this over to ExifInterface.java
        // Since each ExifInterface item has a type, and there's currently no way to get the type
        // from the tag, here we identify the tag to the type so that we can call the correct
        // ExifInterface method
        TYPE_MAPPING.put(ExifInterface.TAG_ARTIST, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_BITS_PER_SAMPLE, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_COMPRESSION, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_COPYRIGHT, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_DATETIME, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_IMAGE_DESCRIPTION, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_IMAGE_LENGTH, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_IMAGE_WIDTH, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_MAKE, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_MODEL, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_ORIENTATION, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_PLANAR_CONFIGURATION, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_PRIMARY_CHROMATICITIES, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_REFERENCE_BLACK_WHITE, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_RESOLUTION_UNIT, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_ROWS_PER_STRIP, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_SAMPLES_PER_PIXEL, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_SOFTWARE, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_STRIP_BYTE_COUNTS, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_STRIP_OFFSETS, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_TRANSFER_FUNCTION, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_WHITE_POINT, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_X_RESOLUTION, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_Y_CB_CR_COEFFICIENTS, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_Y_CB_CR_POSITIONING, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_Y_RESOLUTION, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_APERTURE_VALUE, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_BRIGHTNESS_VALUE, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_CFA_PATTERN, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_COLOR_SPACE, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_COMPONENTS_CONFIGURATION, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_CONTRAST, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_CUSTOM_RENDERED, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_DATETIME_DIGITIZED, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_DATETIME_ORIGINAL, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_DIGITAL_ZOOM_RATIO, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_EXIF_VERSION, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_EXPOSURE_BIAS_VALUE, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_EXPOSURE_INDEX, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_EXPOSURE_MODE, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_EXPOSURE_PROGRAM, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_EXPOSURE_TIME, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_F_NUMBER, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_FILE_SOURCE, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_FLASH, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_FLASH_ENERGY, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_FLASHPIX_VERSION, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_FOCAL_LENGTH, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_GAIN_CONTROL, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_ISO_SPEED_RATINGS, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_IMAGE_UNIQUE_ID, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_LIGHT_SOURCE, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_MAKER_NOTE, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_MAX_APERTURE_VALUE, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_METERING_MODE, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_NEW_SUBFILE_TYPE, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_OECF, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_PIXEL_X_DIMENSION, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_PIXEL_Y_DIMENSION, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_RELATED_SOUND_FILE, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_SATURATION, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_SCENE_CAPTURE_TYPE, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_SCENE_TYPE, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_SENSING_METHOD, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_SHARPNESS, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_SPECTRAL_SENSITIVITY, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_SUBFILE_TYPE, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_SUBSEC_TIME, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_SUBJECT_AREA, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_SUBJECT_DISTANCE, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_SUBJECT_LOCATION, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_USER_COMMENT, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_WHITE_BALANCE, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_ALTITUDE, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_ALTITUDE_REF, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_AREA_INFORMATION, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_DOP, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_DATESTAMP, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_DEST_BEARING, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_DEST_BEARING_REF, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_DEST_DISTANCE, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_DEST_DISTANCE_REF, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_DEST_LATITUDE, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_DEST_LATITUDE_REF, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_DEST_LONGITUDE, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_DEST_LONGITUDE_REF, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_DIFFERENTIAL, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_IMG_DIRECTION, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_IMG_DIRECTION_REF, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_LATITUDE, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_LATITUDE_REF, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_LONGITUDE, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_LONGITUDE_REF, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_MAP_DATUM, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_MEASURE_MODE, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_PROCESSING_METHOD, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_SATELLITES, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_SPEED, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_SPEED_REF, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_STATUS, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_TIMESTAMP, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_TRACK, TYPE_DOUBLE);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_TRACK_REF, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_GPS_VERSION_ID, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_INTEROPERABILITY_INDEX, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_DNG_VERSION, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_DEFAULT_CROP_SIZE, TYPE_INT);
        //I don't know how to represent this. Type is unknown
        //TYPE_MAPPING.put(ExifInterface.TAG_ORF_THUMBNAIL_IMAGE, TYPE_STRING);
        TYPE_MAPPING.put(ExifInterface.TAG_ORF_PREVIEW_IMAGE_START, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_ORF_ASPECT_FRAME, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_RW2_SENSOR_TOP_BORDER, TYPE_INT);
        TYPE_MAPPING.put(ExifInterface.TAG_RW2_ISO, TYPE_INT);
        ALL_KNOWN_EXIF_KEYS = TYPE_MAPPING.keySet().toArray(new String[TYPE_MAPPING.size()]);
    }
    private static final String JPG_MIME_TYPE = "image/jpg";
    private static final String JPEG_MIME_TYPE = "image/jpeg";

    /**
     * Generic metadata retrieval method that can retrieve any available metadata from a given doc
     * Currently only functions for exifdata
     *
     * @param metadata the bundle to which we add any relevant metadata
     * @param stream InputStream containing a file
     * @param mimeType type of the given file
     * @param tags a variable amount of keys to differentiate which tags the user wants
     *             if null, returns a default set of data from the following keys:
     *             Exif data:
     *             ExifInterface.TAG_IMAGE_WIDTH,
     *             ExifInterface.TAG_IMAGE_LENGTH,
     *             ExifInterface.TAG_DATETIME,
     *             ExifInterface.TAG_GPS_LATITUDE,
     *             ExifInterface.TAG_GPS_LONGITUDE,
     *             ExifInterface.TAG_MAKE,
     *             ExifInterface.TAG_MODEL,
     *             ExifInterface.TAG_APERTURE,
     *             ExifInterface.TAG_SHUTTER_SPEED_VALUE
     * @throws IOException when the file doesn't exist
     */
    public static void getMetadata(Bundle metadata, InputStream stream, String mimeType,
            @Nullable String[] tags) throws IOException {
        List<String> metadataTypes = new ArrayList();
        if (mimeType.equals(JPG_MIME_TYPE) || mimeType.equals(JPEG_MIME_TYPE)) {
            ExifInterface exifInterface = new ExifInterface(stream);
            Bundle exifData = getExifData(exifInterface, tags);
            if (exifData.size() > 0) {
                metadata.putBundle(DocumentsContract.METADATA_EXIF, exifData);
                metadataTypes.add(DocumentsContract.METADATA_EXIF);
            }
        }
        metadata.putStringArray(DocumentsContract.METADATA_TYPES,
                metadataTypes.toArray(new String[metadataTypes.size()]));
        // TODO: Add support for PDF and Video metadata
        // TODO: Broaden image support to all images
    }

    /**
     * Helper method that is called if getMetadata is called for an image mimeType.
     *
     * @param exif the bundle to which we add exif data.
     * @param exifInterface an ExifInterface for an image
     * @param tags a list of ExifInterface tags that are used to retrieve data.
     *             if null, returns a default set of data from the following keys:
     *             ExifInterface.TAG_IMAGE_WIDTH,
     *             ExifInterface.TAG_IMAGE_LENGTH,
     *             ExifInterface.TAG_DATETIME,
     *             ExifInterface.TAG_GPS_LATITUDE,
     *             ExifInterface.TAG_GPS_LONGITUDE,
     *             ExifInterface.TAG_MAKE,
     *             ExifInterface.TAG_MODEL,
     *             ExifInterface.TAG_APERTURE,
     *             ExifInterface.TAG_SHUTTER_SPEED_VALUE
     */
    private static Bundle getExifData(ExifInterface exifInterface, @Nullable String[] tags)
            throws IOException {
        if (tags == null) {
            tags = DEFAULT_EXIF_TAGS;
        }
        Bundle exif = new Bundle();
        for (int i = 0; i < tags.length; i++) {
            if (TYPE_MAPPING.get(tags[i]).equals(TYPE_INT)) {
                int data = exifInterface.getAttributeInt(tags[i], Integer.MIN_VALUE);
                if (data != Integer.MIN_VALUE) {
                    exif.putInt(tags[i], data);
                }
            } else if (TYPE_MAPPING.get(tags[i]).equals(TYPE_DOUBLE)) {
                double data = exifInterface.getAttributeDouble(tags[i], Double.MIN_VALUE);
                if (data != Double.MIN_VALUE) {
                    exif.putDouble(tags[i], data);
                }
            } else if (TYPE_MAPPING.get(tags[i]).equals(TYPE_STRING)) {
                String data = exifInterface.getAttribute(tags[i]);
                if (data != null) {
                    exif.putString(tags[i], data);
                }
            }
        }
        return exif;
    }
}
+35 −0

File changed.

Preview size limit exceeded, changes collapsed.

+4.04 MiB
Loading image diff...
Loading