summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--res/drawable/ic_usb_shortcut.xml8
-rw-r--r--res/values/inspector_strings.xml79
-rw-r--r--src/com/android/documentsui/archives/Archive.java6
-rw-r--r--src/com/android/documentsui/archives/ArchivesProvider.java44
-rw-r--r--src/com/android/documentsui/base/DocumentStack.java12
-rw-r--r--src/com/android/documentsui/base/Shared.java4
-rw-r--r--src/com/android/documentsui/dirlist/DragStartListener.java3
-rw-r--r--src/com/android/documentsui/inspector/DebugView.java7
-rw-r--r--src/com/android/documentsui/inspector/HeaderTextSelector.java89
-rw-r--r--src/com/android/documentsui/inspector/HeaderView.java8
-rw-r--r--src/com/android/documentsui/inspector/InspectorController.java12
-rw-r--r--src/com/android/documentsui/inspector/MediaView.java79
-rw-r--r--src/com/android/documentsui/inspector/MetadataUtils.java66
-rw-r--r--src/com/android/documentsui/services/FileOperation.java12
-rw-r--r--tests/common/com/android/documentsui/bots/InspectorBot.java59
-rw-r--r--tests/functional/com/android/documentsui/InspectorUiTest.java8
-rw-r--r--tests/res/raw/images.zipbin0 -> 2850081 bytes
-rw-r--r--tests/unit/com/android/documentsui/AbstractActionHandlerTest.java10
-rw-r--r--tests/unit/com/android/documentsui/archives/ArchivesProviderTest.java33
-rw-r--r--tests/unit/com/android/documentsui/archives/ResourcesProvider.java3
-rw-r--r--tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java18
-rw-r--r--tests/unit/com/android/documentsui/inspector/HeaderTextSelectorTest.java136
-rw-r--r--tests/unit/com/android/documentsui/inspector/MediaViewTest.java20
-rw-r--r--tests/unit/com/android/documentsui/inspector/TestMetadata.java2
-rw-r--r--tests/unit/com/android/documentsui/services/FileOperationServiceTest.java7
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
new file mode 100644
index 000000000..b3013e9ec
--- /dev/null
+++ b/tests/res/raw/images.zip
Binary files differ
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));