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

Commit ce28927a authored by Steve McKay's avatar Steve McKay Committed by Android (Google) Code Review
Browse files

Merge "Introduce a separate MediaView for metadata."

parents 459ab2aa 20887ee1
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -37,8 +37,8 @@
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <com.android.documentsui.inspector.TableView
            android:id="@+id/inspector_metadata_view"
        <com.android.documentsui.inspector.MediaView
            android:id="@+id/inspector_media_view"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
@@ -65,5 +65,6 @@
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="gone" />

    </LinearLayout>
</ScrollView>
 No newline at end of file
+4 −5
Original line number Diff line number Diff line
@@ -373,11 +373,8 @@
    <!-- Title of inspector's debug info section. [CHAR LIMIT=48] -->
    <string name="inspector_debug_section">Debug info (dev only)</string>

    <!-- Title of inspector's metadata info section. Note that this is probably camera EXIF data. -->
    <string name="inspector_metadata_section">Metadata</string>

    <!-- Title of inspector section displaying image specific information (like size and embedded camera details, like lat/long). -->
    <string name="inspector_exif_section">Image information</string>
    <!-- Title of inspector's media details info section. Shows information related to camera, dimensions, and authors. -->
    <string name="inspector_metadata_section">Media details</string>

    <!-- File properties dialog section title. In this section we show information about the default handler application for this type of file.-->
    <string name="handler_app_file_opens_with">This kind of file opens with</string>
@@ -405,6 +402,8 @@
    <!--The value of a photos shutter speed. Note that this is probably camera EXIF data.-->
    <string name="metadata_shutter_speed">Shutter speed</string>
    <!--When a photo was taken. Note that this is probably camera EXIF data.-->
    <string name="metadata_duration">Duration</string>
    <!--When a photo was taken. Note that this is probably camera EXIF data.-->
    <string name="metadata_date_time">Taken on</string>


+40 −105
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
@@ -52,7 +51,7 @@ public final class InspectorController {
    private final DataSupplier mLoader;
    private final HeaderDisplay mHeader;
    private final DetailsDisplay mDetails;
    private final TableDisplay mMetadata;
    private final MediaDisplay mMedia;
    private final ActionDisplay mShowProvider;
    private final ActionDisplay mAppDefaults;
    private final Consumer<DocumentInfo> mDebugView;
@@ -73,7 +72,7 @@ public final class InspectorController {
            ProvidersAccess providers,
            HeaderDisplay header,
            DetailsDisplay details,
            TableDisplay metadata,
            MediaDisplay media,
            ActionDisplay showProvider,
            ActionDisplay appDefaults,
            Consumer<DocumentInfo> debugView,
@@ -86,7 +85,7 @@ public final class InspectorController {
        checkArgument(providers != null);
        checkArgument(header != null);
        checkArgument(details != null);
        checkArgument(metadata != null);
        checkArgument(media != null);
        checkArgument(showProvider != null);
        checkArgument(appDefaults != null);
        checkArgument(debugView != null);
@@ -99,7 +98,7 @@ public final class InspectorController {
        mProviders = providers;
        mHeader = header;
        mDetails = details;
        mMetadata = metadata;
        mMedia = media;
        mShowProvider = showProvider;
        mAppDefaults = appDefaults;
        mArgs = args;
@@ -123,7 +122,7 @@ public final class InspectorController {
            DocumentsApplication.getProvidersCache (activity),
            (HeaderView) layout.findViewById(R.id.inspector_header_view),
            (DetailsView) layout.findViewById(R.id.inspector_details_view),
            (TableView) layout.findViewById(R.id.inspector_metadata_view),
            (MediaView) layout.findViewById(R.id.inspector_media_view),
            (ActionDisplay) layout.findViewById(R.id.inspector_show_in_provider_view),
            (ActionDisplay) layout.findViewById(R.id.inspector_app_defaults_view),
            (DebugView) layout.findViewById(R.id.inspector_debug_view),
@@ -153,9 +152,7 @@ public final class InspectorController {
    /**
     * Updates the view with documentInfo.
     */
    @Nullable
    public void updateView(@Nullable DocumentInfo docInfo) {

    private void updateView(@Nullable DocumentInfo docInfo) {
        if (docInfo == null) {
            mErrorSnackbar.run();
        } else {
@@ -194,11 +191,10 @@ public final class InspectorController {
                mLoader.getDocumentMetadata(
                        docInfo.derivedUri,
                        (Bundle bundle) -> {
                            onDocumentMetadataLoaded(docInfo.displayName, bundle);
                            onDocumentMetadataLoaded(docInfo, bundle);
                        });
            } else {
                mMetadata.setVisible(false);
            }
            mMedia.setVisible(!mMedia.isEmpty());

            if (mArgs.getBoolean(Shared.EXTRA_SHOW_DEBUG)) {
                mDebugView.accept(docInfo);
@@ -206,103 +202,24 @@ public final class InspectorController {
        }
    }

    @VisibleForTesting
    public void onDocumentMetadataLoaded(String displayName, Bundle bundle) {
        Bundle exif = bundle.getBundle(DocumentsContract.METADATA_EXIF);
        if (exif != null) {
            showExifData(displayName, exif);
        }
        mMetadata.setVisible(exif != null);
    }

    /**
     * Updates a files metadata to the view.
     * @param docName - the name of the doc. needed for launching a geo intent.
     * @param exif - bundle of metadata.
     */
    @VisibleForTesting
    public void showExifData(String docName, Bundle exif) {
        mMetadata.setTitle(R.string.inspector_exif_section);

        if (exif.containsKey(ExifInterface.TAG_IMAGE_WIDTH)
            && exif.containsKey(ExifInterface.TAG_IMAGE_LENGTH)) {
            int width = exif.getInt(ExifInterface.TAG_IMAGE_WIDTH);
            int height = exif.getInt(ExifInterface.TAG_IMAGE_LENGTH);
            mMetadata.put(R.string.metadata_dimensions, String.valueOf(width) + " x "
                + String.valueOf(height));
        }

        if (exif.containsKey(ExifInterface.TAG_DATETIME)) {
            String date = exif.getString(ExifInterface.TAG_DATETIME);
            mMetadata.put(R.string.metadata_date_time, date);
    private void onDocumentMetadataLoaded(DocumentInfo doc, @Nullable Bundle metadata) {
        if (metadata == null) {
            return;
        }


        if (hasExifGpsFields(exif)) {
            double[] coords = getExifGpsCoords(exif);

            Intent intent = createGeoIntent(coords[0], coords[1], docName);

        Bundle exif = metadata.getBundle(DocumentsContract.METADATA_EXIF);
        Runnable geoClickListener = null;
        if (exif != null && MetadataUtils.hasExifGpsFields(exif)) {
            double[] coords = MetadataUtils.getExifGpsCoords(exif);
            final Intent intent = createGeoIntent(coords[0], coords[1], doc.displayName);
            if (hasHandler(intent)) {
                mMetadata.put(R.string.metadata_location,
                        String.valueOf(coords[0]) + ",  " + String.valueOf(coords[1]),
                        view -> startActivity(intent)
                );
            } else {
                mMetadata.put(R.string.metadata_location, String.valueOf(coords[0]) + ",  "
                        + String.valueOf(coords[1]));
            }
        }

        if (exif.containsKey(ExifInterface.TAG_GPS_ALTITUDE)) {
            double altitude = exif.getDouble(ExifInterface.TAG_GPS_ALTITUDE);
            mMetadata.put(R.string.metadata_altitude, String.valueOf(altitude));
        }

        if (exif.containsKey(ExifInterface.TAG_MAKE)) {
            String make = exif.getString(ExifInterface.TAG_MAKE);
            mMetadata.put(R.string.metadata_make, make);
        }

        if (exif.containsKey(ExifInterface.TAG_MODEL)) {
            String model = exif.getString(ExifInterface.TAG_MODEL);
            mMetadata.put(R.string.metadata_model, model);
        }

        if (exif.containsKey(ExifInterface.TAG_APERTURE)) {
            String aperture = String.valueOf(exif.get(ExifInterface.TAG_APERTURE));
            mMetadata.put(R.string.metadata_aperture, aperture);
        }

        if (exif.containsKey(ExifInterface.TAG_SHUTTER_SPEED_VALUE)) {
            String shutterSpeed = String.valueOf(exif.get(ExifInterface.TAG_SHUTTER_SPEED_VALUE));
            mMetadata.put(R.string.metadata_shutter_speed, shutterSpeed);
                geoClickListener = () -> {
                    startActivity(intent);
                };
            }
        }

    private boolean hasExifGpsFields(Bundle exif) {
        return (exif.containsKey(ExifInterface.TAG_GPS_LATITUDE)
                && exif.containsKey(ExifInterface.TAG_GPS_LONGITUDE)
                && exif.containsKey(ExifInterface.TAG_GPS_LATITUDE_REF)
                && exif.containsKey(ExifInterface.TAG_GPS_LONGITUDE_REF));
    }

    private double[] getExifGpsCoords(Bundle exif) {
        String lat = exif.getString(ExifInterface.TAG_GPS_LATITUDE);
        String lon = exif.getString(ExifInterface.TAG_GPS_LONGITUDE);
        String latRef = exif.getString(ExifInterface.TAG_GPS_LATITUDE_REF);
        String lonRef = exif.getString(ExifInterface.TAG_GPS_LONGITUDE_REF);

        double round = 1000000.0;

        double[] coordinates = new double[2];

        coordinates[0] = Math.round(
                ExifInterface.convertRationalLatLonToFloat(lat, latRef) * round) / round;
        coordinates[1] = Math.round(
                ExifInterface.convertRationalLatLonToFloat(lon, lonRef) * round) / round;

        return coordinates;
        mMedia.accept(doc, metadata, geoClickListener);
    }

    /**
@@ -367,7 +284,8 @@ public final class InspectorController {
    }

    /**
     * Interface for loading all the various forms of document datal
     * Interface for loading all the various forms of document data. This primarily
     * allows us to easily supply test data in tests.
     */
    public interface DataSupplier {

@@ -439,7 +357,7 @@ public final class InspectorController {
    }

    /**
     * Provides details about a file.
     * Provides basic details about a file.
     */
    public interface DetailsDisplay {

@@ -448,6 +366,18 @@ public final class InspectorController {
        void setChildrenCount(int count);
    }

    /**
     * Provides details about a media file.
     */
    public interface MediaDisplay extends Display {
        void accept(DocumentInfo info, Bundle metadata, @Nullable Runnable geoClickListener);

        /**
         * Returns true if there are now rows in the display. Does not consider the title.
         */
        boolean isEmpty();
    }

    /**
     * Displays a table of image metadata.
     */
@@ -467,5 +397,10 @@ public final class InspectorController {
         * Adds a row in the table and makes it clickable.
         */
        void put(@StringRes int keyId, String value, OnClickListener callback);

        /**
         * Returns true if there are now rows in the display. Does not consider the title.
         */
        boolean isEmpty();
    }
}
 No newline at end of file
+139 −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 com.android.documentsui.inspector;

import android.content.Context;
import android.media.ExifInterface;
import android.media.MediaMetadata;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.support.annotation.VisibleForTesting;
import android.util.AttributeSet;

import com.android.documentsui.R;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.inspector.InspectorController.MediaDisplay;
import com.android.documentsui.inspector.InspectorController.TableDisplay;

import javax.annotation.Nullable;

/**
 * Organizes and Displays the debug information about a file. This view
 * should only be made visible when build is debuggable and system policies
 * allow debug "stuff".
 */
public class MediaView extends TableView implements MediaDisplay {

    private static final String METADATA_KEY_AUDIO = "android.media.metadata.audio";
    private static final String METADATA_KEY_VIDEO = "android.media.metadata.video";

    public MediaView(Context context) {
        this(context, null);
    }

    public MediaView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MediaView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void accept(DocumentInfo doc, Bundle metadata, @Nullable Runnable geoClickListener) {
        setTitle(R.string.inspector_metadata_section);

        Bundle exif = metadata.getBundle(DocumentsContract.METADATA_EXIF);
        if (exif != null) {
            showExifData(this, doc, exif, geoClickListener);
        }

        Bundle video = metadata.getBundle(METADATA_KEY_VIDEO);
        if (video != null) {
            showVideoData(doc, video);
        }

        setVisible(!isEmpty());
    }

    private void showVideoData(DocumentInfo doc, Bundle tags) {
        if (tags.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
            float seconds = tags.getInt(MediaMetadata.METADATA_KEY_DURATION) / 1000.0f;
            put(R.string.metadata_duration, seconds + "s");
        }
    }

    @VisibleForTesting
    public static void showExifData(
            TableDisplay table,
            DocumentInfo doc,
            Bundle tags,
            @Nullable Runnable geoClickListener) {

        if (tags.containsKey(ExifInterface.TAG_IMAGE_WIDTH)
            && tags.containsKey(ExifInterface.TAG_IMAGE_LENGTH)) {
            int width = tags.getInt(ExifInterface.TAG_IMAGE_WIDTH);
            int height = tags.getInt(ExifInterface.TAG_IMAGE_LENGTH);
            table.put(R.string.metadata_dimensions,
                    String.valueOf(width) + " x " + String.valueOf(height));
        }

        if (tags.containsKey(ExifInterface.TAG_DATETIME)) {
            String date = tags.getString(ExifInterface.TAG_DATETIME);
            table.put(R.string.metadata_date_time, date);
        }

        if (MetadataUtils.hasExifGpsFields(tags)) {
            double[] coords = MetadataUtils.getExifGpsCoords(tags);
            if (geoClickListener != null) {
                table.put(R.string.metadata_location,
                        String.valueOf(coords[0]) + ",  " + String.valueOf(coords[1]),
                        view -> {
                            geoClickListener.run();
                        }
                );
            } else {
                table.put(R.string.metadata_location,
                        String.valueOf(coords[0]) + ",  " + String.valueOf(coords[1]));
            }
        }

        if (tags.containsKey(ExifInterface.TAG_GPS_ALTITUDE)) {
            double altitude = tags.getDouble(ExifInterface.TAG_GPS_ALTITUDE);
            table.put(R.string.metadata_altitude, String.valueOf(altitude));
        }

        if (tags.containsKey(ExifInterface.TAG_MAKE)) {
            String make = tags.getString(ExifInterface.TAG_MAKE);
            table.put(R.string.metadata_make, make);
        }

        if (tags.containsKey(ExifInterface.TAG_MODEL)) {
            String model = tags.getString(ExifInterface.TAG_MODEL);
            table.put(R.string.metadata_model, model);
        }

        if (tags.containsKey(ExifInterface.TAG_APERTURE)) {
            String aperture = String.valueOf(tags.get(ExifInterface.TAG_APERTURE));
            table.put(R.string.metadata_aperture, aperture);
        }

        if (tags.containsKey(ExifInterface.TAG_SHUTTER_SPEED_VALUE)) {
            String shutterSpeed = String.valueOf(tags.get(ExifInterface.TAG_SHUTTER_SPEED_VALUE));
            table.put(R.string.metadata_shutter_speed, shutterSpeed);
        }
    }
}
+49 −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 com.android.documentsui.inspector;

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

final class MetadataUtils {

    private MetadataUtils() {}

    static boolean hasExifGpsFields(Bundle exif) {
        return (exif.containsKey(ExifInterface.TAG_GPS_LATITUDE)
                && exif.containsKey(ExifInterface.TAG_GPS_LONGITUDE)
                && exif.containsKey(ExifInterface.TAG_GPS_LATITUDE_REF)
                && exif.containsKey(ExifInterface.TAG_GPS_LONGITUDE_REF));
    }

    static double[] getExifGpsCoords(Bundle exif) {
        String lat = exif.getString(ExifInterface.TAG_GPS_LATITUDE);
        String lon = exif.getString(ExifInterface.TAG_GPS_LONGITUDE);
        String latRef = exif.getString(ExifInterface.TAG_GPS_LATITUDE_REF);
        String lonRef = exif.getString(ExifInterface.TAG_GPS_LONGITUDE_REF);

        double round = 1000000.0;

        double[] coordinates = new double[2];

        coordinates[0] = Math.round(
                ExifInterface.convertRationalLatLonToFloat(lat, latRef) * round) / round;
        coordinates[1] = Math.round(
                ExifInterface.convertRationalLatLonToFloat(lon, lonRef) * round) / round;

        return coordinates;
    }
}
Loading