diff options
25 files changed, 573 insertions, 152 deletions
diff --git a/res/drawable/ic_usb_shortcut.xml b/res/drawable/ic_usb_shortcut.xml index 55f5ed26f..cf954454c 100644 --- a/res/drawable/ic_usb_shortcut.xml +++ b/res/drawable/ic_usb_shortcut.xml @@ -19,13 +19,13 @@ <background android:drawable="@color/shortcut_background" /> <foreground> <inset android:inset="33%"> - <vector + <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:viewportHeight="24" + android:viewportWidth="24"> <path - android:fillColor="@color/ic_shorcut_foreground" + android:fillColor="@color/shortcut_foreground" android:pathData="M15 7v4h1v2h-3V5h2l-3,-4,-3 4h2v8H8v-2.07c.7,-.37 1.2,-1.08 1.2,-1.93 0,-1.21,-.99,-2.2,-2.2,-2.2,-1.21 0,-2.2.99,-2.2 2.2 0 .85.5 1.56 1.2 1.93V13c0 1.11.89 2 2 2h3v3.05c-.71.37,-1.2 1.1,-1.2 1.95 0 1.22.99 2.2 2.2 2.2 1.21 0 2.2,-.98 2.2,-2.2 0,-.85,-.49,-1.58,-1.2,-1.95V15h3c1.11 0 2,-.89 2,-2v-2h1V7h-4z"/> </vector> </inset> diff --git a/res/values/inspector_strings.xml b/res/values/inspector_strings.xml index b98392eac..8548823c6 100644 --- a/res/values/inspector_strings.xml +++ b/res/values/inspector_strings.xml @@ -15,10 +15,10 @@ --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <!--File properties dialog title. This dialog show fine grained details about a file. --> + <!-- File properties dialog title. This dialog show fine grained details about a file. --> <string name="inspector_title">Info</string> - <!--File properties dialog error shown when information about a file could not be loaded--> + <!-- File properties dialog error shown when information about a file could not be loaded--> <string name="inspector_load_error">File info could not be loaded</string> <!-- Title of inspector's debug information section. The contents of this section @@ -43,65 +43,74 @@ <!-- File properties dialog user message if the default app is unknown--> <string name="handler_app_unknown">Unknown</string> - <!--The height and width of a photo. Note that this is probably camera EXIF data.--> + <!-- The height and width of a photo. --> <string name="metadata_dimensions">Dimensions</string> - <!--The message that displays the width and height of a photo followed by a the - "megapixel rating" this corresponds to. Cameras are frequently rated by "megapixels". - In U.S. English this is denoted by a number followed by "MP". E.g. 12.2MP or 8MP. --> - <string name="metadata_dimensions_display"><xliff:g id="width" example="1280">%1$d</xliff:g> x <xliff:g id="height" example="1024">%2$d</xliff:g> - <xliff:g id="megapixels" example="12.2">%3$,.1f</xliff:g>MP</string> - <!--The location of where a photo was taken. (i.e. latitude, longitude) Note that this is probably camera EXIF data.--> + <!-- The width and height of a photo, followed by a the "megapixel rating". + Cameras are frequently rated by "megapixels". In U.S. English the "megapixels" rating + of a camera is denoted by a number followed by "MP". E.g. 12.2MP or 8MP. --> + <string name="metadata_dimensions_format"><xliff:g id="width" example="1280">%1$d</xliff:g> x <xliff:g id="height" example="1024">%2$d</xliff:g> - <xliff:g id="megapixels" example="12.2">%3$,.1f</xliff:g>MP</string> + <!-- The location of where a photo was taken. --> <string name="metadata_coordinates">Coordinates</string> - <!--The elevation the photo was taken. Note that this is probably camera EXIF data.--> + <!-- The location a photo was taken in the form of latitude/longitude coordinates. --> + <string name="metadata_coordinates_format"><xliff:g id="latitude" example="33.996">%1$,.3f</xliff:g>, <xliff:g id="longitude" example="-118.476">%2$,.3f</xliff:g></string> + <!-- The elevation a photo was taken. --> <string name="metadata_altitude">Altitude</string> - <!--The company that made the camera the photo was taken on. Note that this is probably camera EXIF data.--> - <string name="metadata_make">Make</string> - <!--The camera model that the photo was taken on. Note that this is probably camera EXIF data.--> - <string name="metadata_model">Model</string> - <!--The value of a photos aperture. Note that this is probably camera EXIF data.--> + <!-- The camera make and model. --> + <string name="metadata_camera">Camera</string> + <!-- The camera make and model. Where make is usually the company, and model is the individual camera model name. --> + <string name="metadata_camera_format"><xliff:g id="make" example="Sony">%1$s</xliff:g> <xliff:g id="model" example="Snazzy Snapper">%2$s</xliff:g></string> + <!-- The value of a photos aperture. Note that this is probably camera EXIF data.--> <string name="metadata_aperture">Aperture</string> - <!--The value of a photos shutter speed. Note that this is probably camera EXIF data.--> + <!-- 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.--> + <!-- 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.--> + <!-- When a photo was taken. Note that this is probably camera EXIF data.--> <string name="metadata_date_time">Taken on</string> - <!-- Message presenting EXIF aperture information in the tradition "f/2.0" format familiar to users. This format is basically an industry standard that shouldn't be translated, so it is marked as not translatable. --> <string name="metadata_aperture_format" translatable="false">f/<xliff:g id="aperture" example="2.0">%1$,.1f</xliff:g></string> + <!--The tag for the focal length of a camera. Note that this is probably camera EXIF data--> + <string name="metadata_focal_length">Focal length</string> + <!--The format for displaying the focal length of a camera. Note that this is probably camera EXIF data--> + <string name="metadata_focal_format"><xliff:g id="length" example="24">%1$.2f </xliff:g>mm</string> + <!--The tag for the ISO Speed of a camera. Note that this is probably camera EXIF data--> + <string name="metadata_iso_speed_ratings">ISO equivalent</string> + <!--The format for displaying ISO speed. Note that this is probably camera EXIF data--> + <string name="metadata_iso_format">ISO <xliff:g id="iso_speed" example="35">%1$d</xliff:g></string> - <!--String label for developer/debug file details, specifying which stream types are available. --> + <!-- String label for developer/debug file details, specifying which stream types are available. --> <string name="debug_stream_types">Stream types</string> - <!--String label for developer/debug file details, specifying the size of the file in bytes. --> + <!-- String label for developer/debug file details, specifying the size of the file in bytes. --> <string name="debug_raw_size">Raw size (bytes)</string> - <!--String label for developer/debug file details, specifying a file's uri/content address. --> + <!-- String label for developer/debug file details, specifying a file's uri/content address. --> <string name="debug_content_uri" translatable="false">Uri</string> - <!--String label for developer/debug file details, specifying document id. --> + <!-- String label for developer/debug file details, specifying document id. --> <string name="debug_document_id" translatable="false">Document id</string> - <!--String label for developer/debug file details, specifying mimetype. --> + <!-- String label for developer/debug file details, specifying mimetype. --> <string name="debug_raw_mimetype" translatable="false">Raw mimetype</string> - <!--String label for developer/debug file details, specifying that a file is an archive. --> + <!-- String label for developer/debug file details, specifying that a file is an archive. --> <string name="debug_is_archive" translatable="false">Is archive</string> - <!--String label for developer/debug file details, specifying that a file is a container (like a folder or an archive). --> + <!-- String label for developer/debug file details, specifying that a file is a container (like a folder or an archive). --> <string name="debug_is_container" translatable="false">Is container</string> - <!--String label for developer/debug file details, specifying that a file is partial (being downloaded). --> + <!-- String label for developer/debug file details, specifying that a file is partial (being downloaded). --> <string name="debug_is_partial" translatable="false">Is partial</string> - <!--String label for developer/debug file details, specifying that a file is virtual (has no byte representation). --> + <!-- String label for developer/debug file details, specifying that a file is virtual (has no byte representation). --> <string name="debug_is_virtual" translatable="false">Is virtual</string> - <!--String label for developer/debug file details, specifying that a file supports creating files within it. --> + <!-- String label for developer/debug file details, specifying that a file supports creating files within it. --> <string name="debug_supports_create" translatable="false">Supports create</string> - <!--String label for developer/debug file details, specifying that a file is deletable. --> + <!-- String label for developer/debug file details, specifying that a file is deletable. --> <string name="debug_supports_delete" translatable="false">Supports delete</string> - <!--String label for developer/debug file details, specifying that a file has associated metadata. --> + <!-- String label for developer/debug file details, specifying that a file has associated metadata. --> <string name="debug_supports_metadata" translatable="false">Supports metadata</string> - <!--String label for developer/debug file details, specifying that a file can be renamed. --> + <!-- String label for developer/debug file details, specifying that a file can be renamed. --> <string name="debug_supports_rename" translatable="false">Supports rename</string> - <!--String label for developer/debug file details, specifying that a file supports the "settings" feature. --> + <!-- String label for developer/debug file details, specifying that a file supports the "settings" feature. --> <string name="debug_supports_settings" translatable="false">Supports settings</string> - <!--String label for developer/debug file details, specifying that a file has a viewable thumbnail. --> + <!-- String label for developer/debug file details, specifying that a file has a viewable thumbnail. --> <string name="debug_supports_thumbnail" translatable="false">Supports thumbnail</string> - <!--String label for developer/debug file details, specifying that a file supports the "weblink" feature. --> + <!-- String label for developer/debug file details, specifying that a file supports the "weblink" feature. --> <string name="debug_supports_weblink" translatable="false">Supports weblink</string> - <!--String label for developer/debug file details, specifying that a file is writable. --> + <!-- String label for developer/debug file details, specifying that a file is writable. --> <string name="debug_supports_write" translatable="false">Supports write</string> </resources> diff --git a/src/com/android/documentsui/archives/Archive.java b/src/com/android/documentsui/archives/Archive.java index e3c81e37b..14d703c32 100644 --- a/src/com/android/documentsui/archives/Archive.java +++ b/src/com/android/documentsui/archives/Archive.java @@ -26,6 +26,7 @@ import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.os.storage.StorageManager; import android.provider.DocumentsContract; +import android.provider.MetadataReader; import android.provider.DocumentsContract.Document; import android.support.annotation.Nullable; import android.system.ErrnoException; @@ -281,7 +282,10 @@ public abstract class Archive implements Closeable { final String mimeType = getMimeTypeForEntry(entry); row.add(Document.COLUMN_MIME_TYPE, mimeType); - final int flags = mimeType.startsWith("image/") ? Document.FLAG_SUPPORTS_THUMBNAIL : 0; + int flags = mimeType.startsWith("image/") ? Document.FLAG_SUPPORTS_THUMBNAIL : 0; + if (MetadataReader.isSupportedMimeType(mimeType)) { + flags |= Document.FLAG_SUPPORTS_METADATA; + } row.add(Document.COLUMN_FLAGS, flags); } diff --git a/src/com/android/documentsui/archives/ArchivesProvider.java b/src/com/android/documentsui/archives/ArchivesProvider.java index 8f0e3993e..1db03b25c 100644 --- a/src/com/android/documentsui/archives/ArchivesProvider.java +++ b/src/com/android/documentsui/archives/ArchivesProvider.java @@ -17,38 +17,34 @@ package com.android.documentsui.archives; import android.content.ContentProviderClient; -import android.content.ContentResolver; -import android.content.Context; import android.content.res.AssetFileDescriptor; -import android.content.res.Configuration; -import android.database.ContentObserver; import android.database.Cursor; -import android.database.MatrixCursor.RowBuilder; import android.database.MatrixCursor; +import android.database.MatrixCursor.RowBuilder; import android.graphics.Point; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; +import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; -import android.provider.DocumentsContract; import android.provider.DocumentsProvider; +import android.provider.MetadataReader; import android.support.annotation.Nullable; import android.util.Log; import com.android.documentsui.R; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; -import java.io.Closeable; -import java.io.File; +import libcore.io.IoUtils; + import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.Objects; -import java.util.concurrent.locks.Lock; /** * Provides basic implementation for creating, extracting and accessing @@ -71,7 +67,7 @@ public class ArchivesProvider extends DocumentsProvider { }; @GuardedBy("mArchives") - private final Map<Key, Loader> mArchives = new HashMap<Key, Loader>(); + private final Map<Key, Loader> mArchives = new HashMap<>(); @Override public Bundle call(String method, String arg, Bundle extras) { @@ -153,6 +149,32 @@ public class ArchivesProvider extends DocumentsProvider { } @Override + public @Nullable Bundle getDocumentMetadata(String documentId) + throws FileNotFoundException { + + Archive archive = getLoaderOrThrow(documentId).get(); + String mimeType = archive.getDocumentType(documentId); + + if (!MetadataReader.isSupportedMimeType(mimeType)) { + return null; + } + + InputStream stream = null; + try { + stream = new ParcelFileDescriptor.AutoCloseInputStream( + openDocument(documentId, "r", null)); + Bundle metadata = new Bundle(); + MetadataReader.getMetadata(metadata, stream, mimeType, null); + return metadata; + } catch (IOException e) { + Log.e(TAG, "An error occurred retrieving the metadata", e); + return null; + } finally { + IoUtils.closeQuietly(stream); + } + } + + @Override public Cursor queryDocument(String documentId, @Nullable String[] projection) throws FileNotFoundException { final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId); diff --git a/src/com/android/documentsui/base/DocumentStack.java b/src/com/android/documentsui/base/DocumentStack.java index 9b0605c7e..090bcb10c 100644 --- a/src/com/android/documentsui/base/DocumentStack.java +++ b/src/com/android/documentsui/base/DocumentStack.java @@ -17,6 +17,7 @@ package com.android.documentsui.base; import static com.android.documentsui.base.Shared.DEBUG; +import static com.android.internal.util.Preconditions.checkArgument; import android.content.ContentResolver; import android.database.Cursor; @@ -123,13 +124,10 @@ public class DocumentStack implements Durable, Parcelable { } public void push(DocumentInfo info) { - boolean alreadyInStack = mList.contains(info); - assert (!alreadyInStack); - if (!alreadyInStack) { - if (DEBUG) Log.d(TAG, "Adding doc to stack: " + info); - mList.addLast(info); - mStackTouched = true; - } + checkArgument(!mList.contains(info)); + if (DEBUG) Log.d(TAG, "Adding doc to stack: " + info); + mList.addLast(info); + mStackTouched = true; } public DocumentInfo pop() { diff --git a/src/com/android/documentsui/base/Shared.java b/src/com/android/documentsui/base/Shared.java index dbe14012b..32a7d9dcc 100644 --- a/src/com/android/documentsui/base/Shared.java +++ b/src/com/android/documentsui/base/Shared.java @@ -39,8 +39,6 @@ import android.view.WindowManager; import com.android.documentsui.R; import com.android.documentsui.ui.MessageBuilder; -import java.io.PrintWriter; -import java.io.StringWriter; import java.text.Collator; import java.util.ArrayList; import java.util.List; @@ -62,6 +60,8 @@ public final class Shared { // These values track values declared in MediaDocumentsProvider. public static final String METADATA_KEY_AUDIO = "android.media.metadata.audio"; public static final String METADATA_KEY_VIDEO = "android.media.metadata.video"; + public static final String METADATA_VIDEO_LATITUDE = "android.media.metadata.video:latitude"; + public static final String METADATA_VIDEO_LONGITUTE = "android.media.metadata.video:longitude"; /** * Extra boolean flag for {@link #ACTION_PICK_COPY_DESTINATION}, which diff --git a/src/com/android/documentsui/dirlist/DragStartListener.java b/src/com/android/documentsui/dirlist/DragStartListener.java index 1651603ac..bf9e2bbd1 100644 --- a/src/com/android/documentsui/dirlist/DragStartListener.java +++ b/src/com/android/documentsui/dirlist/DragStartListener.java @@ -17,6 +17,7 @@ package com.android.documentsui.dirlist; import static com.android.documentsui.base.Shared.DEBUG; +import static com.android.internal.util.Preconditions.checkArgument; import android.net.Uri; import android.support.annotation.VisibleForTesting; @@ -98,7 +99,7 @@ interface DragStartListener { @Override public final boolean onMouseDragEvent(InputEvent event) { - assert(Events.isMouseDragEvent(event)); + checkArgument(Events.isMouseDragEvent(event)); return startDrag(mViewFinder.findView(event.getX(), event.getY()), event); } diff --git a/src/com/android/documentsui/inspector/DebugView.java b/src/com/android/documentsui/inspector/DebugView.java index a2c818ab0..ccae14aba 100644 --- a/src/com/android/documentsui/inspector/DebugView.java +++ b/src/com/android/documentsui/inspector/DebugView.java @@ -31,7 +31,10 @@ import com.android.documentsui.base.Lookup; import com.android.documentsui.inspector.InspectorController.DebugDisplay; import java.text.NumberFormat; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.concurrent.Executor; /** @@ -126,7 +129,9 @@ public class DebugView extends TableView implements DebugDisplay { String title = mContext.getResources().getString( R.string.inspector_debug_metadata_section); putTitle(String.format(title, type)); - for (String key : bundle.keySet()) { + List<String> keys = new ArrayList<>(bundle.keySet()); + Collections.sort(keys); + for (String key : keys) { put(key, String.valueOf(bundle.get(key))); } } diff --git a/src/com/android/documentsui/inspector/HeaderTextSelector.java b/src/com/android/documentsui/inspector/HeaderTextSelector.java new file mode 100644 index 000000000..5530fbe63 --- /dev/null +++ b/src/com/android/documentsui/inspector/HeaderTextSelector.java @@ -0,0 +1,89 @@ +/* + * 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 static com.android.internal.util.Preconditions.checkArgument; + +import android.text.Spannable; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.TextView; + +/** + * Selects all or part of a textview. + */ + public final class HeaderTextSelector implements ActionMode.Callback { + + private final TextView mText; + private final Selector mSelector; + + public HeaderTextSelector(TextView text, Selector selector) { + checkArgument(text != null); + checkArgument(selector != null); + mText = text; + mSelector = selector; + } + + // An action mode is created when the user selects text. This method is called where + // we force it to select only the filename in the header. Meaning the name of the file would + // be selected but the extension would not. + @Override + public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { + CharSequence value = mText.getText(); + if (value instanceof Spannable) { + mSelector.select((Spannable) value, 0, getLengthOfFilename(value)); + } + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode actionMode) {} + + /** + * Gets the length of the inspector header for selection. + * + * Example: + * filename.txt returns the end index needed to select "filename". + * + * @return the index of the last character in the filename + */ + private static int getLengthOfFilename(CharSequence text) { + String title = text.toString(); + if (title != null) { + int index = title.indexOf('.'); + if (index > 0) { + return index; + } + } + + return text.length(); + } + + public interface Selector { + void select(Spannable text, int start, int stop); + } +} diff --git a/src/com/android/documentsui/inspector/HeaderView.java b/src/com/android/documentsui/inspector/HeaderView.java index 3a68016d9..c411082ab 100644 --- a/src/com/android/documentsui/inspector/HeaderView.java +++ b/src/com/android/documentsui/inspector/HeaderView.java @@ -20,6 +20,8 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.drawable.Drawable; +import android.text.Selection; +import android.text.Spannable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -77,6 +79,12 @@ public final class HeaderView extends RelativeLayout implements HeaderDisplay { public void accept(DocumentInfo info, String displayName) { loadHeaderImage(info); mTitle.setText(displayName); + mTitle.setCustomSelectionActionModeCallback( + new HeaderTextSelector(mTitle, this::selectText)); + } + + private void selectText(Spannable text, int start, int stop) { + Selection.setSelection(text, start, stop); } private void loadHeaderImage(DocumentInfo doc) { diff --git a/src/com/android/documentsui/inspector/InspectorController.java b/src/com/android/documentsui/inspector/InspectorController.java index da018818f..c4db39498 100644 --- a/src/com/android/documentsui/inspector/InspectorController.java +++ b/src/com/android/documentsui/inspector/InspectorController.java @@ -207,10 +207,9 @@ public final class InspectorController { return; } - Bundle exif = metadata.getBundle(DocumentsContract.METADATA_EXIF); Runnable geoClickListener = null; - if (exif != null && MetadataUtils.hasExifGpsFields(exif)) { - double[] coords = MetadataUtils.getExifGpsCoords(exif); + if (MetadataUtils.hasGeoCoordinates(metadata)) { + float[] coords = MetadataUtils.getGeoCoordinates(metadata); final Intent intent = createGeoIntent(coords[0], coords[1], doc.displayName); if (hasHandler(intent)) { geoClickListener = () -> { @@ -252,9 +251,10 @@ public final class InspectorController { * * @see https://developer.android.com/guide/components/intents-common.html#Maps */ - private static Intent createGeoIntent(double latitude, double longitude, - @Nullable String label) { - String data = "geo:0,0?q=" + latitude + " " + longitude + "(" + Uri.encode(label) + ")"; + private static Intent createGeoIntent( + float latitude, float longitude, @Nullable String label) { + label = Uri.encode(label == null ? "" : label); + String data = "geo:0,0?q=" + latitude + " " + longitude + "(" + label + ")"; Uri uri = Uri.parse(data); return new Intent(Intent.ACTION_VIEW, uri); } diff --git a/src/com/android/documentsui/inspector/MediaView.java b/src/com/android/documentsui/inspector/MediaView.java index d62b78392..ba53b0efb 100644 --- a/src/com/android/documentsui/inspector/MediaView.java +++ b/src/com/android/documentsui/inspector/MediaView.java @@ -66,7 +66,7 @@ public class MediaView extends TableView implements MediaDisplay { Bundle video = metadata.getBundle(Shared.METADATA_KEY_VIDEO); if (video != null) { - showVideoData(this, mResources, doc, video); + showVideoData(this, mResources, doc, video, geoClickListener); } setVisible(!isEmpty()); @@ -74,10 +74,19 @@ public class MediaView extends TableView implements MediaDisplay { @VisibleForTesting public static void showVideoData( - TableDisplay table, Resources resources, DocumentInfo doc, Bundle tags) { + TableDisplay table, + Resources resources, + DocumentInfo doc, + Bundle tags, + @Nullable Runnable geoClickListener) { addDimensionsRow(table, resources, tags); + if (MetadataUtils.hasVideoCoordinates(tags)) { + float[] coords = MetadataUtils.getVideoCoords(tags); + showCoordiantes(table, resources, coords, geoClickListener); + } + if (tags.containsKey(MediaMetadata.METADATA_KEY_DURATION)) { int millis = tags.getInt(MediaMetadata.METADATA_KEY_DURATION); table.put(R.string.metadata_duration, DateUtils.formatElapsedTime(millis / 1000)); @@ -100,18 +109,8 @@ public class MediaView extends TableView implements MediaDisplay { } if (MetadataUtils.hasExifGpsFields(tags)) { - double[] coords = MetadataUtils.getExifGpsCoords(tags); - if (geoClickListener != null) { - table.put(R.string.metadata_coordinates, - String.valueOf(coords[0]) + ", " + String.valueOf(coords[1]), - view -> { - geoClickListener.run(); - } - ); - } else { - table.put(R.string.metadata_coordinates, - String.valueOf(coords[0]) + ", " + String.valueOf(coords[1])); - } + float[] coords = MetadataUtils.getExifGpsCoords(tags); + showCoordiantes(table, resources, coords, geoClickListener); } if (tags.containsKey(ExifInterface.TAG_GPS_ALTITUDE)) { @@ -119,14 +118,14 @@ public class MediaView extends TableView implements MediaDisplay { 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_MAKE) || tags.containsKey(ExifInterface.TAG_MODEL)) { + String make = tags.getString(ExifInterface.TAG_MAKE); + String model = tags.getString(ExifInterface.TAG_MODEL); + make = make != null ? make : ""; + model = model != null ? model : ""; + table.put( + R.string.metadata_camera, + resources.getString(R.string.metadata_camera_format, make, model)); } if (tags.containsKey(ExifInterface.TAG_APERTURE)) { @@ -139,6 +138,40 @@ public class MediaView extends TableView implements MediaDisplay { formatShutterSpeed(tags.getDouble(ExifInterface.TAG_SHUTTER_SPEED_VALUE))); table.put(R.string.metadata_shutter_speed, shutterSpeed); } + + if (tags.containsKey(ExifInterface.TAG_FOCAL_LENGTH)) { + double length = tags.getDouble(ExifInterface.TAG_FOCAL_LENGTH); + table.put(R.string.metadata_focal_length, + String.format(resources.getString(R.string.metadata_focal_format), length)); + } + + if (tags.containsKey(ExifInterface.TAG_ISO_SPEED_RATINGS)) { + int iso = tags.getInt(ExifInterface.TAG_ISO_SPEED_RATINGS); + table.put(R.string.metadata_iso_speed_ratings, + String.format(resources.getString(R.string.metadata_iso_format), iso)); + } + + } + + private static void showCoordiantes( + TableDisplay table, + Resources resources, + float[] coords, + @Nullable Runnable geoClickListener) { + + String value = resources.getString( + R.string.metadata_coordinates_format, coords[0], coords[1]); + if (geoClickListener != null) { + table.put( + R.string.metadata_coordinates, + value, + view -> { + geoClickListener.run(); + } + ); + } else { + table.put(R.string.metadata_coordinates, value); + } } /** @@ -172,7 +205,7 @@ public class MediaView extends TableView implements MediaDisplay { float megaPixels = height * width / 1000000f; table.put(R.string.metadata_dimensions, resources.getString( - R.string.metadata_dimensions_display, width, height, megaPixels)); + R.string.metadata_dimensions_format, width, height, megaPixels)); } } } diff --git a/src/com/android/documentsui/inspector/MetadataUtils.java b/src/com/android/documentsui/inspector/MetadataUtils.java index 8852bc1b7..b5c1034b5 100644 --- a/src/com/android/documentsui/inspector/MetadataUtils.java +++ b/src/com/android/documentsui/inspector/MetadataUtils.java @@ -15,35 +15,77 @@ */ package com.android.documentsui.inspector; +import static com.android.internal.util.Preconditions.checkNotNull; + import android.media.ExifInterface; import android.os.Bundle; +import android.provider.DocumentsContract; + +import com.android.documentsui.base.Shared; + +import javax.annotation.Nullable; final class MetadataUtils { private MetadataUtils() {} - static boolean hasExifGpsFields(Bundle exif) { - return (exif.containsKey(ExifInterface.TAG_GPS_LATITUDE) + static boolean hasGeoCoordinates(@Nullable Bundle metadata) { + if (metadata == null) { + return false; + } + return hasVideoCoordinates(metadata.getBundle(Shared.METADATA_KEY_VIDEO)) + || hasExifGpsFields(metadata.getBundle(DocumentsContract.METADATA_EXIF)); + } + + static boolean hasVideoCoordinates(@Nullable Bundle data) { + return data != null && (data.containsKey(Shared.METADATA_VIDEO_LATITUDE) + && data.containsKey(Shared.METADATA_VIDEO_LONGITUTE)); + } + + static boolean hasExifGpsFields(@Nullable Bundle exif) { + return exif != null && (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) { + static float[] getGeoCoordinates(Bundle metadata) { + assert hasGeoCoordinates(metadata); + checkNotNull(metadata); + + Bundle bundle = metadata.getBundle(DocumentsContract.METADATA_EXIF); + if (hasExifGpsFields(bundle)) { + return getExifGpsCoords(bundle); + } + + bundle = metadata.getBundle(Shared.METADATA_KEY_VIDEO); + if (hasVideoCoordinates(bundle)) { + return getVideoCoords(bundle); + } + + // This should never happen, because callers should always check w/ hasGeoCoordinates first. + throw new IllegalArgumentException("Invalid metadata bundle: " + metadata); + } + + static float[] getExifGpsCoords(Bundle exif) { + assert hasExifGpsFields(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 = 1000.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 new float[] { + ExifInterface.convertRationalLatLonToFloat(lat, latRef), + ExifInterface.convertRationalLatLonToFloat(lon, lonRef) + }; + } - return coordinates; + static float[] getVideoCoords(Bundle data) { + assert hasVideoCoordinates(data); + return new float[] { + data.getFloat(Shared.METADATA_VIDEO_LATITUDE), + data.getFloat(Shared.METADATA_VIDEO_LONGITUTE) + }; } } diff --git a/src/com/android/documentsui/services/FileOperation.java b/src/com/android/documentsui/services/FileOperation.java index 2ee12d56d..1f0e9b987 100644 --- a/src/com/android/documentsui/services/FileOperation.java +++ b/src/com/android/documentsui/services/FileOperation.java @@ -16,12 +16,13 @@ package com.android.documentsui.services; -import static com.android.documentsui.services.FileOperationService.OPERATION_COPY; import static com.android.documentsui.services.FileOperationService.OPERATION_COMPRESS; -import static com.android.documentsui.services.FileOperationService.OPERATION_EXTRACT; +import static com.android.documentsui.services.FileOperationService.OPERATION_COPY; import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE; +import static com.android.documentsui.services.FileOperationService.OPERATION_EXTRACT; import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE; import static com.android.documentsui.services.FileOperationService.OPERATION_UNKNOWN; +import static com.android.internal.util.Preconditions.checkArgument; import android.content.Context; import android.net.Uri; @@ -57,8 +58,8 @@ public abstract class FileOperation implements Parcelable { @VisibleForTesting FileOperation(@OpType int opType, UrisSupplier srcs, DocumentStack destination) { - assert(opType != OPERATION_UNKNOWN); - assert(srcs.getItemCount() > 0); + checkArgument(opType != OPERATION_UNKNOWN); + checkArgument(srcs.getItemCount() > 0); mOpType = opType; mSrcs = srcs; @@ -133,6 +134,7 @@ public abstract class FileOperation implements Parcelable { return builder.toString(); } + @Override CopyJob createJob(Context service, Job.Listener listener, String id, Features features) { return new CopyJob( service, listener, id, getDestination(), getSrc(), getMessenger(), features); @@ -173,6 +175,7 @@ public abstract class FileOperation implements Parcelable { return builder.toString(); } + @Override CopyJob createJob(Context service, Job.Listener listener, String id, Features features) { return new CompressJob(service, listener, id, getDestination(), getSrc(), getMessenger(), features); @@ -214,6 +217,7 @@ public abstract class FileOperation implements Parcelable { } // TODO: Replace CopyJob with ExtractJob. + @Override CopyJob createJob(Context service, Job.Listener listener, String id, Features features) { return new CopyJob( service, listener, id, getDestination(), getSrc(), getMessenger(), features); diff --git a/tests/common/com/android/documentsui/bots/InspectorBot.java b/tests/common/com/android/documentsui/bots/InspectorBot.java index 55f904464..448a7b746 100644 --- a/tests/common/com/android/documentsui/bots/InspectorBot.java +++ b/tests/common/com/android/documentsui/bots/InspectorBot.java @@ -15,19 +15,24 @@ */ package com.android.documentsui.bots; -import android.annotation.StringRes; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + import android.app.Activity; import android.content.Context; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiSelector; +import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; - -import com.android.documentsui.inspector.DetailsView; import com.android.documentsui.R; +import com.android.documentsui.inspector.DetailsView; +import com.android.documentsui.inspector.KeyValueRow; +import com.android.documentsui.inspector.TableView; + +import java.util.HashMap; +import java.util.Map; public class InspectorBot extends Bots.BaseBot { @@ -42,23 +47,45 @@ public class InspectorBot extends Bots.BaseBot { assertEquals(expected, text); } - public void assertRowPresent(@StringRes String key, String value, Activity activity) + public void assertRowPresent(String key, Activity activity) throws Exception { - assertTrue(isRowPresent(key, value, activity)); + assertTrue(isRowPresent(key, activity)); } - private boolean isRowPresent(@StringRes String key, String value, Activity activity) + public void assertRowEquals(String key, String value, Activity activity) throws Exception { - DetailsView detailsView = (DetailsView) activity.findViewById(R.id.inspector_details_view); - int children = detailsView.getChildCount(); + assertTrue(isRowEquals(key, value, activity)); + } + + private static Map<String, String> getTableRows(TableView table) + throws Exception { + Map<String, String> rows = new HashMap<>(); + int children = table.getChildCount(); for (int i = 0; i < children; i++) { - LinearLayout child = (LinearLayout) detailsView.getChildAt(i); - TextView title = (TextView) child.getChildAt(0); - if (title.getText().equals(key)) { - TextView info = (TextView) child.getChildAt(1); - return info.getText().equals(value); + View view = table.getChildAt(i); + if (view instanceof KeyValueRow) { + LinearLayout row = (LinearLayout) table.getChildAt(i); + TextView key = (TextView) row.getChildAt(0); + TextView value = (TextView) row.getChildAt(1); + rows.put( + String.valueOf(key.getText()), + String.valueOf(value.getText())); } } - return false; + return rows; + } + + private boolean isRowPresent(String key, Activity activity) + throws Exception { + DetailsView details = (DetailsView) activity.findViewById(R.id.inspector_details_view); + Map<String, String> rows = getTableRows(details); + return rows.containsKey(key); + } + + private boolean isRowEquals(String key, String value, Activity activity) + throws Exception { + DetailsView details = (DetailsView) activity.findViewById(R.id.inspector_details_view); + Map<String, String> rows = getTableRows(details); + return rows.containsKey(key) && value.equals(rows.get(key)); } } diff --git a/tests/functional/com/android/documentsui/InspectorUiTest.java b/tests/functional/com/android/documentsui/InspectorUiTest.java index 55b832a98..a228de2da 100644 --- a/tests/functional/com/android/documentsui/InspectorUiTest.java +++ b/tests/functional/com/android/documentsui/InspectorUiTest.java @@ -58,13 +58,17 @@ public class InspectorUiTest extends ActivityTest<InspectorActivity> { bots.inspector.assertTitle("test.txt"); } - public void testDisplayFileType() throws Exception { + public void testFolderDetails() throws Exception { if (!features.isInspectorEnabled()) { return; } - bots.inspector.assertRowPresent( + bots.inspector.assertRowEquals( getActivity().getString(R.string.sort_dimension_file_type), "Folder", getActivity()); + bots.inspector.assertRowEquals( + getActivity().getString(R.string.directory_items), + "4", + getActivity()); } } diff --git a/tests/res/raw/images.zip b/tests/res/raw/images.zip Binary files differnew file mode 100644 index 000000000..b3013e9ec --- /dev/null +++ b/tests/res/raw/images.zip diff --git a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java index 54248e020..807c3fafd 100644 --- a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java @@ -17,7 +17,7 @@ package com.android.documentsui; import static junit.framework.Assert.assertTrue; - +import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import android.content.Intent; @@ -179,15 +179,13 @@ public class AbstractActionHandlerTest { } @Test - public void testOpensDocument_AssertionErrorIfAlreadyInStack() throws Exception { + public void testOpensDocument_ExceptionIfAlreadyInStack() throws Exception { mEnv.populateStack(); - boolean threw = false; try { mEnv.state.stack.push(TestEnv.FOLDER_0); - } catch (AssertionError e) { - threw = true; + fail("Should have thrown IllegalArgumentException."); + } catch (IllegalArgumentException expected) { } - assertTrue(threw); } @Test diff --git a/tests/unit/com/android/documentsui/archives/ArchivesProviderTest.java b/tests/unit/com/android/documentsui/archives/ArchivesProviderTest.java index 7b2504ab1..cc40e56e8 100644 --- a/tests/unit/com/android/documentsui/archives/ArchivesProviderTest.java +++ b/tests/unit/com/android/documentsui/archives/ArchivesProviderTest.java @@ -28,11 +28,11 @@ import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; +import android.media.ExifInterface; import android.net.Uri; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import android.os.SystemClock; import android.provider.DocumentsContract; import android.support.test.InstrumentationRegistry; import android.support.test.filters.MediumTest; @@ -264,4 +264,35 @@ public class ArchivesProviderTest { client.release(); } + + @Test + public void testGetDocumentMetadata() throws InterruptedException, RemoteException { + final Uri sourceUri = DocumentsContract.buildDocumentUri( + ResourcesProvider.AUTHORITY, "images.zip"); + final Uri archiveUri = ArchivesProvider.buildUriForArchive(sourceUri, + ParcelFileDescriptor.MODE_READ_ONLY); + + final ContentResolver resolver = mContext.getContentResolver(); + final ContentProviderClient client = + resolver.acquireUnstableContentProviderClient(archiveUri); + + ArchivesProvider.acquireArchive(client, archiveUri); + + Uri archivedImageUri = Uri.parse( + "content://com.android.documentsui.archives/document/content%3A%2F%2F" + + "com.android.documentsui.archives.resourcesprovider%2F" + + "document%2Fimages.zip%23268435456%23%2Ffreddy.jpg"); + + Bundle metadata = DocumentsContract.getDocumentMetadata(client, archivedImageUri); + assertNotNull(metadata); + Bundle exif = metadata.getBundle(DocumentsContract.METADATA_EXIF); + assertNotNull(exif); + + assertEquals(3036, exif.getInt(ExifInterface.TAG_IMAGE_WIDTH)); + assertEquals(4048, exif.getInt(ExifInterface.TAG_IMAGE_LENGTH)); + assertEquals("Pixel", exif.getString(ExifInterface.TAG_MODEL)); + + ArchivesProvider.releaseArchive(client, archiveUri); + client.release(); + } } diff --git a/tests/unit/com/android/documentsui/archives/ResourcesProvider.java b/tests/unit/com/android/documentsui/archives/ResourcesProvider.java index 988b4957e..b9ddb92b1 100644 --- a/tests/unit/com/android/documentsui/archives/ResourcesProvider.java +++ b/tests/unit/com/android/documentsui/archives/ResourcesProvider.java @@ -56,9 +56,10 @@ public class ResourcesProvider extends DocumentsProvider { private static final Map<String, Integer> RESOURCES = new HashMap<>(); static { RESOURCES.put("archive.zip", R.raw.archive); + RESOURCES.put("broken.zip", R.raw.broken); RESOURCES.put("empty_dirs.zip", R.raw.empty_dirs); + RESOURCES.put("images.zip", R.raw.images); RESOURCES.put("no_dirs.zip", R.raw.no_dirs); - RESOURCES.put("broken.zip", R.raw.broken); } private ExecutorService mExecutor = null; diff --git a/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java b/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java index 635e249d5..6cfdd9567 100644 --- a/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java +++ b/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java @@ -18,7 +18,7 @@ package com.android.documentsui.dirlist; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static junit.framework.TestCase.fail; +import static junit.framework.Assert.fail; import android.provider.DocumentsContract; import android.support.test.filters.SmallTest; @@ -26,18 +26,17 @@ import android.support.test.runner.AndroidJUnit4; import android.view.MotionEvent; import android.view.View; -import com.android.documentsui.MenuManager; import com.android.documentsui.MenuManager.SelectionDetails; import com.android.documentsui.base.DocumentInfo; +import com.android.documentsui.base.Events.InputEvent; import com.android.documentsui.base.Providers; import com.android.documentsui.base.State; import com.android.documentsui.dirlist.DragStartListener.ActiveListener; -import com.android.documentsui.base.Events.InputEvent; -import com.android.documentsui.selection.SelectionManager; import com.android.documentsui.selection.Selection; +import com.android.documentsui.selection.SelectionManager; +import com.android.documentsui.testing.SelectionManagers; import com.android.documentsui.testing.TestDragAndDropManager; import com.android.documentsui.testing.TestEvent; -import com.android.documentsui.testing.SelectionManagers; import com.android.documentsui.testing.TestSelectionDetails; import com.android.documentsui.testing.Views; @@ -86,7 +85,7 @@ public class DragStartListenerTest { }, // docInfo Converter (Selection selection) -> { - return new ArrayList<DocumentInfo>(); + return new ArrayList<>(); }, mManager); @@ -123,7 +122,8 @@ public class DragStartListenerTest { @Test public void testThrows_OnNonPrimaryMove() { - assertThrows(mEvent.pressButton(MotionEvent.BUTTON_PRIMARY).build()); + mEvent.releaseButton(MotionEvent.BUTTON_PRIMARY); + assertThrows(mEvent.pressButton(MotionEvent.BUTTON_SECONDARY).build()); } @Test @@ -187,8 +187,8 @@ public class DragStartListenerTest { private void assertThrows(InputEvent e) { try { - assertFalse(mListener.onMouseDragEvent(e)); + mListener.onMouseDragEvent(e); fail(); - } catch (AssertionError expected) {} + } catch (IllegalArgumentException expected) {} } } diff --git a/tests/unit/com/android/documentsui/inspector/HeaderTextSelectorTest.java b/tests/unit/com/android/documentsui/inspector/HeaderTextSelectorTest.java new file mode 100644 index 000000000..58b533bb7 --- /dev/null +++ b/tests/unit/com/android/documentsui/inspector/HeaderTextSelectorTest.java @@ -0,0 +1,136 @@ +/* + * 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 static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import android.test.suitebuilder.annotation.SmallTest; +import android.text.Spannable; +import android.text.SpannableString; +import android.widget.TextView; +import com.android.documentsui.inspector.HeaderTextSelector.Selector; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class HeaderTextSelectorTest { + + private Context mContext; + private TestTextView mTestTextView; + private TestSelector mTestSelector; + private HeaderTextSelector mSelector; + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + mTestTextView = new TestTextView(mContext); + mTestSelector = new TestSelector(); + mSelector = new HeaderTextSelector(mTestTextView, mTestSelector); + } + + @Test + public void testSimpleFileName() throws Exception { + CharSequence fileName = "filename"; + String extension = ".txt"; + String testSequence = fileName + extension; + + mTestTextView.setText(testSequence); + mSelector.onCreateActionMode(null, null); + mTestSelector.assertCalled(); + + CharSequence selectedSequence = + testSequence.subSequence(mTestSelector.mStart, mTestSelector.mStop); + assertEquals(selectedSequence, fileName); + } + + + @Test + public void testLotsOfPeriodsInFileName() throws Exception { + CharSequence fileName = "filename"; + String extension = ".jpg.zip.pdf.txt"; + String testSequence = fileName + extension; + + mTestTextView.setText(testSequence); + mSelector.onCreateActionMode(null, null); + mTestSelector.assertCalled(); + + CharSequence selectedSequence = + testSequence.subSequence(mTestSelector.mStart, mTestSelector.mStop); + assertEquals(selectedSequence, fileName); + } + + @Test + public void testNoPeriodsInFileName() throws Exception { + String testSequence = "filename"; + + mTestTextView.setText(testSequence); + mSelector.onCreateActionMode(null, null); + mTestSelector.assertCalled(); + + CharSequence selectedSequence = + testSequence.subSequence(mTestSelector.mStart, mTestSelector.mStop); + assertEquals(selectedSequence, testSequence); + } + + private static class TestTextView extends TextView { + + public TestTextView(Context context) { + super(context); + } + + private String textViewString; + + public void setText(String text) { + textViewString = text; + } + + @Override + public CharSequence getText() { + return new SpannableString(textViewString); + } + } + + private static class TestSelector implements Selector { + + private boolean mCalled; + private int mStart; + private int mStop; + + public TestSelector() { + mCalled = false; + mStart = -1; + mStop = -1; + } + + @Override + public void select(Spannable text, int start, int stop) { + mCalled = true; + mStart = start; + mStop = stop; + } + + public void assertCalled() { + assertTrue(mCalled); + } + } +}
\ No newline at end of file diff --git a/tests/unit/com/android/documentsui/inspector/MediaViewTest.java b/tests/unit/com/android/documentsui/inspector/MediaViewTest.java index bdf55edb6..7901213a6 100644 --- a/tests/unit/com/android/documentsui/inspector/MediaViewTest.java +++ b/tests/unit/com/android/documentsui/inspector/MediaViewTest.java @@ -42,7 +42,11 @@ public class MediaViewTest { @Before public void setUp() { mResources = TestResources.create(); - mResources.strings.put(R.string.metadata_dimensions_display, "%d x %d, %.1fMP"); + // TODO: We should just be using the real underlying resources. + mResources.strings.put(R.string.metadata_dimensions_format, "%d x %d, %.1fMP"); + mResources.strings.put(R.string.metadata_aperture_format, "f/%.1f"); + mResources.strings.put(R.string.metadata_coordinates_format, "%.3f, %.3f"); + mResources.strings.put(R.string.metadata_camera_format, "%s %s"); mTable = new TestTable(); mMetadata = new Bundle(); TestMetadata.populateExifData(mMetadata); @@ -55,18 +59,20 @@ public class MediaViewTest { */ @Test public void testShowExifData() throws Exception { - mResources.strings.put(R.string.metadata_aperture_format, "f/%.1f"); + mResources.strings.put(R.string.metadata_focal_format, "%.2f mm"); + mResources.strings.put(R.string.metadata_iso_format, "ISO %d"); Bundle exif = mMetadata.getBundle(DocumentsContract.METADATA_EXIF); MediaView.showExifData(mTable, mResources, TestEnv.FILE_JPG, exif, null); mTable.assertHasRow(R.string.metadata_dimensions, "3840 x 2160, 8.3MP"); mTable.assertHasRow(R.string.metadata_date_time, "Jan 01, 1970, 12:16 AM"); - mTable.assertHasRow(R.string.metadata_coordinates, "33.996, -118.475"); + mTable.assertHasRow(R.string.metadata_coordinates, "33.996, -118.475"); mTable.assertHasRow(R.string.metadata_altitude, "1244.0"); - mTable.assertHasRow(R.string.metadata_make, "Google"); - mTable.assertHasRow(R.string.metadata_model, "Pixel"); + mTable.assertHasRow(R.string.metadata_camera, "Google Pixel"); mTable.assertHasRow(R.string.metadata_shutter_speed, "1/100"); mTable.assertHasRow(R.string.metadata_aperture, "f/2.0"); + mTable.assertHasRow(R.string.metadata_iso_speed_ratings, "ISO 120"); + mTable.assertHasRow(R.string.metadata_focal_length, "4.27 mm"); } /** @@ -106,7 +112,7 @@ public class MediaViewTest { @Test public void testShowVideoData() throws Exception { Bundle data = mMetadata.getBundle(Shared.METADATA_KEY_VIDEO); - MediaView.showVideoData(mTable, mResources, TestEnv.FILE_MP4, data); + MediaView.showVideoData(mTable, mResources, TestEnv.FILE_MP4, data, null); mTable.assertHasRow(R.string.metadata_duration, "01:12"); mTable.assertHasRow(R.string.metadata_dimensions, "1920 x 1080, 2.1MP"); @@ -120,7 +126,7 @@ public class MediaViewTest { public void testShowVideoData_HourPlusDuration() throws Exception { Bundle data = mMetadata.getBundle(Shared.METADATA_KEY_VIDEO); data.putInt(MediaMetadata.METADATA_KEY_DURATION, 21660000); - MediaView.showVideoData(mTable, mResources, TestEnv.FILE_MP4, data); + MediaView.showVideoData(mTable, mResources, TestEnv.FILE_MP4, data, null); mTable.assertHasRow(R.string.metadata_duration, "6:01:00"); } diff --git a/tests/unit/com/android/documentsui/inspector/TestMetadata.java b/tests/unit/com/android/documentsui/inspector/TestMetadata.java index bfb9c1b68..af7404963 100644 --- a/tests/unit/com/android/documentsui/inspector/TestMetadata.java +++ b/tests/unit/com/android/documentsui/inspector/TestMetadata.java @@ -41,6 +41,8 @@ final class TestMetadata { data.putString(ExifInterface.TAG_MODEL, "Pixel"); data.putDouble(ExifInterface.TAG_SHUTTER_SPEED_VALUE, 6.643); data.putDouble(ExifInterface.TAG_APERTURE, 2.0); + data.putInt(ExifInterface.TAG_ISO_SPEED_RATINGS, 120); + data.putDouble(ExifInterface.TAG_FOCAL_LENGTH, 4.27); } static void populateVideoData(Bundle container) { diff --git a/tests/unit/com/android/documentsui/services/FileOperationServiceTest.java b/tests/unit/com/android/documentsui/services/FileOperationServiceTest.java index 7f75aa5c5..a210c0ee1 100644 --- a/tests/unit/com/android/documentsui/services/FileOperationServiceTest.java +++ b/tests/unit/com/android/documentsui/services/FileOperationServiceTest.java @@ -21,8 +21,8 @@ import static com.android.documentsui.services.FileOperationService.OPERATION_DE import static com.android.documentsui.services.FileOperations.createBaseIntent; import static com.android.documentsui.services.FileOperations.createJobId; import static com.google.android.collect.Lists.newArrayList; +import static org.junit.Assert.fail; -import android.app.Notification; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -131,8 +131,9 @@ public class FileOperationServiceTest extends ServiceTestCase<FileOperationServi public void testRunsCopyJobs_AfterExceptionInJobCreation() throws Exception { try { startService(createCopyIntent(new ArrayList<>(), BETA_DOC)); - } catch(AssertionError e) { - // Expected AssertionError + fail("Should have throw exception."); + } catch(IllegalArgumentException expected) { + // We're sending a naughty empty list that should result in an IllegalArgumentException. } startService(createCopyIntent(newArrayList(GAMMA_DOC), DELTA_DOC)); |