Loading res/layout/inspector_fragment.xml +3 −2 Original line number Diff line number Diff line Loading @@ -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"/> Loading @@ -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 res/values/strings.xml +4 −5 Original line number Diff line number Diff line Loading @@ -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> Loading Loading @@ -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> Loading src/com/android/documentsui/inspector/InspectorController.java +40 −105 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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, Loading @@ -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); Loading @@ -99,7 +98,7 @@ public final class InspectorController { mProviders = providers; mHeader = header; mDetails = details; mMetadata = metadata; mMedia = media; mShowProvider = showProvider; mAppDefaults = appDefaults; mArgs = args; Loading @@ -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), Loading Loading @@ -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 { Loading Loading @@ -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); Loading @@ -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); } /** Loading Loading @@ -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 { Loading Loading @@ -439,7 +357,7 @@ public final class InspectorController { } /** * Provides details about a file. * Provides basic details about a file. */ public interface DetailsDisplay { Loading @@ -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. */ Loading @@ -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 src/com/android/documentsui/inspector/MediaView.java 0 → 100644 +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); } } } src/com/android/documentsui/inspector/MetadataUtils.java 0 → 100644 +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
res/layout/inspector_fragment.xml +3 −2 Original line number Diff line number Diff line Loading @@ -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"/> Loading @@ -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
res/values/strings.xml +4 −5 Original line number Diff line number Diff line Loading @@ -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> Loading Loading @@ -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> Loading
src/com/android/documentsui/inspector/InspectorController.java +40 −105 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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, Loading @@ -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); Loading @@ -99,7 +98,7 @@ public final class InspectorController { mProviders = providers; mHeader = header; mDetails = details; mMetadata = metadata; mMedia = media; mShowProvider = showProvider; mAppDefaults = appDefaults; mArgs = args; Loading @@ -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), Loading Loading @@ -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 { Loading Loading @@ -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); Loading @@ -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); } /** Loading Loading @@ -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 { Loading Loading @@ -439,7 +357,7 @@ public final class InspectorController { } /** * Provides details about a file. * Provides basic details about a file. */ public interface DetailsDisplay { Loading @@ -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. */ Loading @@ -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
src/com/android/documentsui/inspector/MediaView.java 0 → 100644 +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); } } }
src/com/android/documentsui/inspector/MetadataUtils.java 0 → 100644 +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; } }