Merge "Cleaning up mRestoredSelection after restoring selection." into arc-apps
diff --git a/res/layout-sw720dp-land/column_headers.xml b/res/layout-sw720dp-land/column_headers.xml
index 7c4adeb..d5bcb76 100644
--- a/res/layout-sw720dp-land/column_headers.xml
+++ b/res/layout-sw720dp-land/column_headers.xml
@@ -52,7 +52,7 @@
android:id="@android:id/title"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_weight="0.5"
+ android:layout_weight="0.375"
android:layout_marginEnd="12dp"
android:focusable="true"
android:gravity="center_vertical"
@@ -77,6 +77,20 @@
</com.android.documentsui.sorting.HeaderCell>
<com.android.documentsui.sorting.HeaderCell
+ android:id="@+id/file_type"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="0.125"
+ android:layout_marginEnd="12dp"
+ android:focusable="true"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:animateLayoutChanges="true">
+
+ <include layout="@layout/shared_cell_content" />
+ </com.android.documentsui.sorting.HeaderCell>
+
+ <com.android.documentsui.sorting.HeaderCell
android:id="@+id/size"
android:layout_width="0dp"
android:layout_height="match_parent"
diff --git a/res/layout-sw720dp-land/item_doc_list.xml b/res/layout-sw720dp-land/item_doc_list.xml
index ca43827..e097d23 100644
--- a/res/layout-sw720dp-land/item_doc_list.xml
+++ b/res/layout-sw720dp-land/item_doc_list.xml
@@ -81,7 +81,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
- android:layout_weight="0.5"
+ android:layout_weight="0.375"
android:ellipsize="middle"
android:singleLine="true"
android:textAlignment="viewStart"
@@ -101,6 +101,18 @@
android:textColor="?android:attr/textColorSecondary" />
<TextView
+ android:id="@+id/file_type"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="12dp"
+ android:layout_weight="0.125"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@android:style/TextAppearance.Material.Body1"
+ android:textColor="?android:attr/textColorSecondary" />
+
+ <TextView
android:id="@+id/size"
android:layout_width="0dp"
android:layout_height="wrap_content"
diff --git a/res/layout/item_doc_list.xml b/res/layout/item_doc_list.xml
index 9269b28..f6212f0 100644
--- a/res/layout/item_doc_list.xml
+++ b/res/layout/item_doc_list.xml
@@ -114,6 +114,17 @@
android:textColor="@color/item_details" />
<TextView
+ android:id="@+id/file_type"
+ android:layout_width="90dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@android:style/TextAppearance.Material.Caption"
+ android:textColor="@color/item_details" />
+
+ <TextView
android:id="@android:id/summary"
android:layout_width="0dp"
android:layout_height="wrap_content"
diff --git a/res/values/mimes.xml b/res/values/mimes.xml
new file mode 100644
index 0000000..38c0276
--- /dev/null
+++ b/res/values/mimes.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Generic file type with an extention shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="generic_extention_file_type"><xliff:g id="extension" example="APE">%1$s</xliff:g> file</string>
+ <!-- Generic file type without an extention shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="generic_file_type">File</string>
+ <!-- Generic image file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="image_file_type">Image</string>
+ <!-- Image file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="image_extension_file_type"><xliff:g id="fileType" example="JPG">%1$s</xliff:g> image</string>
+ <!-- Generic audio file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="audio_file_type">Audio</string>
+ <!-- Audio file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="audio_extension_file_type"><xliff:g id="fileType" example="MP3">%1$s</xliff:g> audio</string>
+ <!-- Generic video file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="video_file_type">Video</string>
+ <!-- Video file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="video_extension_file_type"><xliff:g id="fileType" example="AVI">%1$s</xliff:g> video</string>
+ <!-- Archive file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="archive_file_type"><xliff:g id="fileType" example="Zip">%1$s</xliff:g> archive</string>
+ <!-- Android application file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="apk_file_type">Android application</string>
+ <!-- Plain text file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="txt_file_type">Plain text</string>
+ <!-- HTML file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="html_file_type">HTML document</string>
+ <!-- PDF file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="pdf_file_type">PDF document</string>
+ <!-- Word document file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="word_file_type">Word document</string>
+ <!-- PowerPoint presentation file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="ppt_file_type">PowerPoint presentation</string>
+ <!-- Excel spreadsheet file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="excel_file_type">Excel spreadsheet</string>
+ <!-- Google document file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="gdoc_file_type">Google document</string>
+ <!-- Google spreadsheet file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="gsheet_file_type">Google spreadsheet</string>
+ <!-- Google presentation file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="gslides_file_type">Google presentation</string>
+ <!-- Google drawing file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="gdraw_file_type">Google drawing</string>
+ <!-- Google table file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="gtable_file_type">Google table</string>
+ <!-- Google form file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="gform_file_type">Google form</string>
+ <!-- Google map file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="gmap_file_type">Google map</string>
+ <!-- Google site file type shown in type column in list view. [CHAR LIMIT=24] -->
+ <string name="gsite_file_type">Google site</string>
+</resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 731dfa3..67215d8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -108,10 +108,12 @@
<string name="sort_dimension_name">Name</string>
<!-- Table header for metadata of downloaded files, such as download source and progress. [CHAR_LIMIT=24] -->
<string name="sort_dimension_summary">Summary</string>
- <!-- Table header for last modified time. [CHAR_LIMIT=18] -->
- <string name="sort_dimension_date">Modified</string>
+ <!-- Table header for file type. [CHAR_LIMIT=12] -->
+ <string name="sort_dimension_file_type">Type</string>
<!-- Table header for file size. [CHAR_LIMIT=12] -->
<string name="sort_dimension_size">Size</string>
+ <!-- Table header for last modified time. [CHAR_LIMIT=18] -->
+ <string name="sort_dimension_date">Modified</string>
<!-- content description to describe ascending sorting used with upward arrow in table header. -->
<string name="sort_direction_ascending">Ascending</string>
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index b2bccd3..fa474bb 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -533,8 +533,12 @@
if (DEBUG) Log.d(TAG, "Creating new loader recents.");
return new RecentsLoader(
- context, mProviders, mState, mInjector.features, mExecutors);
-
+ context,
+ mProviders,
+ mState,
+ mInjector.features,
+ mExecutors,
+ mInjector.fileTypeLookup);
} else {
Uri contentsUri = mSearchMgr.isSearching()
@@ -561,6 +565,7 @@
mState.stack.peek(),
contentsUri,
mState.sortModel,
+ mInjector.fileTypeLookup,
mDirectoryReloadLock,
mSearchMgr.isSearching());
}
diff --git a/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java
index 7b50cc6..30a447c 100644
--- a/src/com/android/documentsui/DirectoryLoader.java
+++ b/src/com/android/documentsui/DirectoryLoader.java
@@ -40,6 +40,7 @@
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Features;
import com.android.documentsui.base.FilteringCursorWrapper;
+import com.android.documentsui.base.Lookup;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.roots.RootCursorWrapper;
import com.android.documentsui.sorting.SortModel;
@@ -56,6 +57,7 @@
private final RootInfo mRoot;
private final Uri mUri;
private final SortModel mModel;
+ private final Lookup<String, String> mFileTypeLookup;
private final boolean mSearchMode;
private DocumentInfo mDoc;
@@ -65,21 +67,23 @@
private Features mFeatures;
public DirectoryLoader(
- Features freatures,
+ Features features,
Context context,
RootInfo root,
DocumentInfo doc,
Uri uri,
SortModel model,
+ Lookup<String, String> fileTypeLookup,
DirectoryReloadLock lock,
boolean inSearchMode) {
super(context, ProviderExecutor.forAuthority(root.authority));
- mFeatures = freatures;
+ mFeatures = features;
mRoot = root;
mUri = uri;
mModel = model;
mDoc = doc;
+ mFileTypeLookup = fileTypeLookup;
mSearchMode = inSearchMode;
mObserver = new LockingContentObserver(lock, this::onContentChanged);
}
@@ -142,7 +146,7 @@
&& cursor.getExtras().containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
if (VERBOSE) Log.d(TAG, "Skipping sort of pre-sorted cursor. Booya!");
} else {
- cursor = mModel.sortCursor(cursor);
+ cursor = mModel.sortCursor(cursor, mFileTypeLookup);
}
result.cursor = cursor;
} catch (Exception e) {
diff --git a/src/com/android/documentsui/DocumentsApplication.java b/src/com/android/documentsui/DocumentsApplication.java
index e0b2559..4c9c65f 100644
--- a/src/com/android/documentsui/DocumentsApplication.java
+++ b/src/com/android/documentsui/DocumentsApplication.java
@@ -28,6 +28,7 @@
import android.os.RemoteException;
import android.text.format.DateUtils;
+import com.android.documentsui.base.Lookup;
import com.android.documentsui.clipping.ClipStorage;
import com.android.documentsui.clipping.ClipStore;
import com.android.documentsui.clipping.DocumentClipper;
@@ -41,6 +42,7 @@
private ClipStorage mClipStore;
private DocumentClipper mClipper;
private DragAndDropManager mDragAndDropManager;
+ private Lookup<String, String> mFileTypeLookup;
public static ProvidersCache getProvidersCache(Context context) {
return ((DocumentsApplication) context.getApplicationContext()).mProviders;
@@ -74,6 +76,10 @@
return ((DocumentsApplication) context.getApplicationContext()).mDragAndDropManager;
}
+ public static Lookup<String, String> getFileTypeLookup(Context context) {
+ return ((DocumentsApplication) context.getApplicationContext()).mFileTypeLookup;
+ }
+
@Override
public void onCreate() {
super.onCreate();
@@ -93,6 +99,8 @@
mDragAndDropManager = DragAndDropManager.create(this, mClipper);
+ mFileTypeLookup = new FileTypeMap(this);
+
final IntentFilter packageFilter = new IntentFilter();
packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
diff --git a/src/com/android/documentsui/FileTypeMap.java b/src/com/android/documentsui/FileTypeMap.java
new file mode 100644
index 0000000..29f7e4c
--- /dev/null
+++ b/src/com/android/documentsui/FileTypeMap.java
@@ -0,0 +1,162 @@
+/*
+ * 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;
+
+import android.annotation.StringRes;
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.documentsui.base.Lookup;
+import com.android.documentsui.base.MimeTypes;
+
+import libcore.net.MimeUtils;
+
+import java.util.HashMap;
+
+/**
+ * A map from mime type to user friendly type string.
+ */
+public class FileTypeMap implements Lookup<String, String> {
+
+ private static final String TAG = "FileTypeMap";
+
+ private final Resources mRes;
+
+ private final SparseArray<Integer> mMediaTypeStringMap = new SparseArray<>();
+
+ private final HashMap<String, Integer> mFileTypeMap = new HashMap<>();
+ private final HashMap<String, String> mArchiveTypeMap = new HashMap<>();
+ private final HashMap<String, Integer> mSpecialMediaMimeType = new HashMap<>();
+
+ FileTypeMap(Context context) {
+ mRes = context.getResources();
+
+ // Mapping from generic media type string to extension media type string
+ mMediaTypeStringMap.put(R.string.video_file_type, R.string.video_extension_file_type);
+ mMediaTypeStringMap.put(R.string.audio_file_type, R.string.audio_extension_file_type);
+ mMediaTypeStringMap.put(R.string.image_file_type, R.string.image_extension_file_type);
+
+ // Common file types
+ mFileTypeMap.put(MimeTypes.APK_TYPE, R.string.apk_file_type);
+ mFileTypeMap.put("text/plain", R.string.txt_file_type);
+ mFileTypeMap.put("text/html", R.string.html_file_type);
+ mFileTypeMap.put("application/xhtml+xml", R.string.html_file_type);
+ mFileTypeMap.put("application/pdf", R.string.pdf_file_type);
+
+ // MS file types
+ mFileTypeMap.put("application/msword", R.string.word_file_type);
+ mFileTypeMap.put(
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ R.string.word_file_type);
+ mFileTypeMap.put("application/vnd.ms-powerpoint", R.string.ppt_file_type);
+ mFileTypeMap.put(
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+ R.string.ppt_file_type);
+ mFileTypeMap.put("application/vnd.ms-excel", R.string.excel_file_type);
+ mFileTypeMap.put(
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ R.string.excel_file_type);
+
+ // Google doc types
+ mFileTypeMap.put("application/vnd.google-apps.document", R.string.gdoc_file_type);
+ mFileTypeMap.put("application/vnd.google-apps.spreadsheet", R.string.gsheet_file_type);
+ mFileTypeMap.put("application/vnd.google-apps.presentation", R.string.gslides_file_type);
+ mFileTypeMap.put("application/vnd.google-apps.drawing", R.string.gdraw_file_type);
+ mFileTypeMap.put("application/vnd.google-apps.fusiontable", R.string.gtable_file_type);
+ mFileTypeMap.put("application/vnd.google-apps.form", R.string.gform_file_type);
+ mFileTypeMap.put("application/vnd.google-apps.map", R.string.gmap_file_type);
+ mFileTypeMap.put("application/vnd.google-apps.sites", R.string.gsite_file_type);
+
+ // Archive types
+ mArchiveTypeMap.put("application/rar", "RAR");
+ mArchiveTypeMap.put("application/zip", "Zip");
+ mArchiveTypeMap.put("application/x-tar", "Tar");
+ mArchiveTypeMap.put("application/gzip", "Gzip");
+ mArchiveTypeMap.put("application/x-7z-compressed", "7z");
+ mArchiveTypeMap.put("application/x-rar-compressed", "RAR");
+
+ // Special media mime types
+ mSpecialMediaMimeType.put("application/ogg", R.string.audio_file_type);
+ mSpecialMediaMimeType.put("application/x-flac", R.string.audio_file_type);
+ }
+
+ @Override
+ public String lookup(String mimeType) {
+ if (mFileTypeMap.containsKey(mimeType)) {
+ return getPredefinedFileTypeString(mimeType);
+ }
+
+ if (mArchiveTypeMap.containsKey(mimeType)) {
+ return buildArchiveTypeString(mimeType);
+ }
+
+ if (mSpecialMediaMimeType.containsKey(mimeType)) {
+ int genericType = mSpecialMediaMimeType.get(mimeType);
+ return getFileTypeString(mimeType, mMediaTypeStringMap.get(genericType), genericType);
+ }
+
+ final String[] type = MimeTypes.splitMimeType(mimeType);
+ if (type == null) {
+ Log.w(TAG, "Unexpected mime type " + mimeType);
+ return getGenericFileTypeString();
+ }
+
+ switch (type[0]) {
+ case MimeTypes.IMAGE_PREFIX:
+ return getFileTypeString(
+ mimeType, R.string.image_extension_file_type, R.string.image_file_type);
+ case MimeTypes.AUDIO_PREFIX:
+ return getFileTypeString(
+ mimeType, R.string.audio_extension_file_type, R.string.audio_file_type);
+ case MimeTypes.VIDEO_PREFIX:
+ return getFileTypeString(
+ mimeType, R.string.video_extension_file_type, R.string.video_file_type);
+ default:
+ return getFileTypeString(
+ mimeType, R.string.generic_extention_file_type, R.string.generic_file_type);
+ }
+ }
+
+ private String buildArchiveTypeString(String mimeType) {
+ final String archiveType = mArchiveTypeMap.get(mimeType);
+
+ assert(!TextUtils.isEmpty(archiveType));
+
+ final String format = mRes.getString(R.string.archive_file_type);
+ return String.format(format, archiveType);
+ }
+
+ private String getPredefinedFileTypeString(String mimeType) {
+ return mRes.getString(mFileTypeMap.get(mimeType));
+ }
+
+ private String getFileTypeString(
+ String mimeType, @StringRes int formatStringId, @StringRes int defaultStringId) {
+ final String extension = MimeUtils.guessExtensionFromMimeType(mimeType);
+
+ return TextUtils.isEmpty(extension)
+ ? mRes.getString(defaultStringId)
+ : String.format(mRes.getString(formatStringId), extension.toUpperCase());
+ }
+
+ private String getGenericFileTypeString() {
+ return mRes.getString(R.string.generic_file_type);
+ }
+}
diff --git a/src/com/android/documentsui/Injector.java b/src/com/android/documentsui/Injector.java
index 5ac897d..dbc0475 100644
--- a/src/com/android/documentsui/Injector.java
+++ b/src/com/android/documentsui/Injector.java
@@ -26,6 +26,7 @@
import com.android.documentsui.MenuManager.SelectionDetails;
import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.Features;
+import com.android.documentsui.base.Lookup;
import com.android.documentsui.dirlist.DocumentsAdapter;
import com.android.documentsui.base.DebugHelper;
import com.android.documentsui.prefs.ScopedPreferences;
@@ -48,6 +49,7 @@
public final ActivityConfig config;
public final ScopedPreferences prefs;
public final MessageBuilder messages;
+ public final Lookup<String, String> fileTypeLookup;
public MenuManager menuManager;
public DialogController dialogs;
@@ -76,8 +78,9 @@
ActivityConfig config,
ScopedPreferences prefs,
MessageBuilder messages,
- DialogController dialogs) {
- this(features, config, prefs, messages, dialogs, new Model(features));
+ DialogController dialogs,
+ Lookup<String, String> fileTypeLookup) {
+ this(features, config, prefs, messages, dialogs, fileTypeLookup, new Model(features));
}
@VisibleForTesting
@@ -87,6 +90,7 @@
ScopedPreferences prefs,
MessageBuilder messages,
DialogController dialogs,
+ Lookup<String, String> fileTypeLookup,
Model model) {
this.features = features;
@@ -94,6 +98,7 @@
this.prefs = prefs;
this.messages = messages;
this.dialogs = dialogs;
+ this.fileTypeLookup = fileTypeLookup;
this.mModel = model;
this.debugHelper = new DebugHelper(this);
}
diff --git a/src/com/android/documentsui/RecentsLoader.java b/src/com/android/documentsui/RecentsLoader.java
index 43209ec..38a3cfa 100644
--- a/src/com/android/documentsui/RecentsLoader.java
+++ b/src/com/android/documentsui/RecentsLoader.java
@@ -88,6 +88,7 @@
private final State mState;
private final Features mFeatures;
private final Lookup<String, Executor> mExecutors;
+ private final Lookup<String, String> mFileTypeMap;
@GuardedBy("mTasks")
/** A authority -> RecentsTask map */
@@ -99,13 +100,14 @@
private DirectoryResult mResult;
public RecentsLoader(Context context, ProvidersAccess providers, State state, Features features,
- Lookup<String, Executor> executors) {
+ Lookup<String, Executor> executors, Lookup<String, String> fileTypeMap) {
super(context);
mProviders = providers;
mState = state;
mFeatures = features;
mExecutors = executors;
+ mFileTypeMap = fileTypeMap;
// Keep clients around on high-RAM devices, since we'd be spinning them
// up moments later to fetch thumbnails anyway.
@@ -204,7 +206,7 @@
}
final Cursor notMovableMasked = new NotMovableMaskCursor(merged);
- final Cursor sorted = mState.sortModel.sortCursor(notMovableMasked);
+ final Cursor sorted = mState.sortModel.sortCursor(notMovableMasked, mFileTypeMap);
// Tell the UI if this is an in-progress result. When loading is complete, another update is
// sent with EXTRA_LOADING set to false.
diff --git a/src/com/android/documentsui/base/MimeTypes.java b/src/com/android/documentsui/base/MimeTypes.java
index 0f61e67..44b61ca 100644
--- a/src/com/android/documentsui/base/MimeTypes.java
+++ b/src/com/android/documentsui/base/MimeTypes.java
@@ -24,16 +24,31 @@
private MimeTypes() {}
- private static final String APK_TYPE = "application/vnd.android.package-archive";
+ public static final String APK_TYPE = "application/vnd.android.package-archive";
+
+ public static final String IMAGE_PREFIX = "image";
+ public static final String AUDIO_PREFIX = "audio";
+ public static final String VIDEO_PREFIX = "video";
+
/**
* MIME types that are visual in nature. For example, they should always be
* shown as thumbnails in list mode.
*/
public static final String[] VISUAL_MIMES = new String[] { "image/*", "video/*" };
+ public static @Nullable String[] splitMimeType(String mimeType) {
+ final String[] groups = mimeType.split("/");
+
+ if (groups.length != 2 || groups[0].isEmpty() || groups[1].isEmpty()) {
+ return null;
+ }
+
+ return groups;
+ }
+
public static String findCommonMimeType(List<String> mimeTypes) {
- String[] commonType = mimeTypes.get(0).split("/");
- if (commonType.length != 2) {
+ String[] commonType = splitMimeType(mimeTypes.get(0));
+ if (commonType == null) {
return "*/*";
}
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 4399a95..ff9edc2 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -84,6 +84,7 @@
import com.android.documentsui.base.Events.InputEvent;
import com.android.documentsui.base.Events.MotionInputEvent;
import com.android.documentsui.base.Features;
+import com.android.documentsui.base.Lookup;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.base.State;
@@ -307,7 +308,9 @@
mIconHelper = new IconHelper(mActivity, MODE_GRID);
mAdapter = new DirectoryAddonsAdapter(
- mAdapterEnv, new ModelBackedDocumentsAdapter(mAdapterEnv, mIconHelper));
+ mAdapterEnv,
+ new ModelBackedDocumentsAdapter(mAdapterEnv, mIconHelper, mInjector.fileTypeLookup)
+ );
mRecView.setAdapter(mAdapter);
diff --git a/src/com/android/documentsui/dirlist/ListDocumentHolder.java b/src/com/android/documentsui/dirlist/ListDocumentHolder.java
index d4d13a1..42be8f9 100644
--- a/src/com/android/documentsui/dirlist/ListDocumentHolder.java
+++ b/src/com/android/documentsui/dirlist/ListDocumentHolder.java
@@ -18,6 +18,7 @@
import static com.android.documentsui.base.DocumentInfo.getCursorString;
+import android.annotation.Nullable;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Rect;
@@ -31,25 +32,30 @@
import com.android.documentsui.R;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Events.InputEvent;
+import com.android.documentsui.base.Lookup;
import com.android.documentsui.base.Shared;
import com.android.documentsui.roots.RootCursorWrapper;
final class ListDocumentHolder extends DocumentHolder {
private final TextView mTitle;
- private final LinearLayout mDetails; // Container of date/size/summary
+ private final @Nullable LinearLayout mDetails; // Container of date/size/summary
private final TextView mDate;
private final TextView mSize;
+ private final TextView mType;
private final TextView mSummary;
private final ImageView mIconMime;
private final ImageView mIconThumb;
private final ImageView mIconCheck;
- private final IconHelper mIconHelper;
private final View mIconLayout;
+
+ private final IconHelper mIconHelper;
+ private final Lookup<String, String> mFileTypeLookup;
// This is used in as a convenience in our bind method.
private final DocumentInfo mDoc;
- public ListDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper) {
+ public ListDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper,
+ Lookup<String, String> fileTypeLookup) {
super(context, parent, R.layout.item_doc_list);
mIconLayout = itemView.findViewById(android.R.id.icon);
@@ -60,10 +66,12 @@
mSummary = (TextView) itemView.findViewById(android.R.id.summary);
mSize = (TextView) itemView.findViewById(R.id.size);
mDate = (TextView) itemView.findViewById(R.id.date);
+ mType = (TextView) itemView.findViewById(R.id.file_type);
// Warning: mDetails view doesn't exists in layout-sw720dp-land layout
mDetails = (LinearLayout) itemView.findViewById(R.id.line2);
mIconHelper = iconHelper;
+ mFileTypeLookup = fileTypeLookup;
mDoc = new DocumentInfo();
}
@@ -142,7 +150,6 @@
* Bind this view to the given document for display.
* @param cursor Pointing to the item to be bound.
* @param modelId The model ID of the item.
- * @param state Current display state.
*/
@Override
public void bind(Cursor cursor, String modelId) {
@@ -190,8 +197,10 @@
mSize.setVisibility(View.VISIBLE);
mSize.setText(Formatter.formatFileSize(mContext, mDoc.size));
} else {
- mSize.setVisibility(View.GONE);
+ mSize.setVisibility(View.INVISIBLE);
}
+
+ mType.setText(mFileTypeLookup.lookup(mDoc.mimeType));
}
// mDetails view doesn't exists in layout-sw720dp-land layout
diff --git a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
index be1091f..921917c 100644
--- a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
+++ b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
@@ -28,6 +28,7 @@
import com.android.documentsui.Model;
import com.android.documentsui.base.EventListener;
+import com.android.documentsui.base.Lookup;
import com.android.documentsui.base.State;
import com.android.documentsui.Model.Update;
@@ -45,6 +46,7 @@
// isn't an ideal pattern (more transitive dependency stuff) but good enough for now.
private final Environment mEnv;
private final IconHelper mIconHelper; // a transitive dependency of the holders.
+ private final Lookup<String, String> mFileTypeLookup;
/**
* An ordered list of model IDs. This is the data structure that determines what shows up in
@@ -53,9 +55,11 @@
private List<String> mModelIds = new ArrayList<>();
private EventListener<Model.Update> mModelUpdateListener;
- public ModelBackedDocumentsAdapter(Environment env, IconHelper iconHelper) {
+ public ModelBackedDocumentsAdapter(
+ Environment env, IconHelper iconHelper, Lookup<String, String> fileTypeLookup) {
mEnv = env;
mIconHelper = iconHelper;
+ mFileTypeLookup = fileTypeLookup;
mModelUpdateListener = new EventListener<Model.Update>() {
@Override
@@ -92,7 +96,8 @@
}
break;
case MODE_LIST:
- holder = new ListDocumentHolder(mEnv.getContext(), parent, mIconHelper);
+ holder = new ListDocumentHolder(
+ mEnv.getContext(), parent, mIconHelper, mFileTypeLookup);
break;
default:
throw new IllegalStateException("Unsupported layout mode.");
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index 125f8c3..aeef645 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -84,7 +84,8 @@
new Config(),
ScopedPreferences.create(this, PREFERENCES_SCOPE),
messages,
- DialogController.create(features, this, messages));
+ DialogController.create(features, this, messages),
+ DocumentsApplication.getFileTypeLookup(this));
super.onCreate(icicle);
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index a49c58c..a2a5053 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -34,6 +34,7 @@
import com.android.documentsui.ActionModeController;
import com.android.documentsui.BaseActivity;
+import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.FocusManager;
import com.android.documentsui.Injector;
import com.android.documentsui.MenuManager.DirectoryDetails;
@@ -80,7 +81,8 @@
new Config(),
ScopedPreferences.create(this, PREFERENCES_SCOPE),
new MessageBuilder(this),
- DialogController.create(features, this, null));
+ DialogController.create(features, this, null),
+ DocumentsApplication.getFileTypeLookup(this));
super.onCreate(icicle);
diff --git a/src/com/android/documentsui/sorting/SortModel.java b/src/com/android/documentsui/sorting/SortModel.java
index 05833f7..65b7fc1 100644
--- a/src/com/android/documentsui/sorting/SortModel.java
+++ b/src/com/android/documentsui/sorting/SortModel.java
@@ -26,11 +26,13 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.DocumentsContract.Document;
+import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import com.android.documentsui.R;
+import com.android.documentsui.base.Lookup;
import com.android.documentsui.sorting.SortDimension.SortDirection;
import java.lang.annotation.Retention;
@@ -48,8 +50,9 @@
SORT_DIMENSION_ID_UNKNOWN,
SORT_DIMENSION_ID_TITLE,
SORT_DIMENSION_ID_SUMMARY,
- SORT_DIMENSION_ID_DATE,
- SORT_DIMENSION_ID_SIZE
+ SORT_DIMENSION_ID_SIZE,
+ SORT_DIMENSION_ID_FILE_TYPE,
+ SORT_DIMENSION_ID_DATE
})
@Retention(RetentionPolicy.SOURCE)
public @interface SortDimensionId {}
@@ -57,6 +60,7 @@
public static final int SORT_DIMENSION_ID_TITLE = android.R.id.title;
public static final int SORT_DIMENSION_ID_SUMMARY = android.R.id.summary;
public static final int SORT_DIMENSION_ID_SIZE = R.id.size;
+ public static final int SORT_DIMENSION_ID_FILE_TYPE = R.id.file_type;
public static final int SORT_DIMENSION_ID_DATE = R.id.date;
@IntDef(flag = true, value = {
@@ -96,7 +100,8 @@
private boolean mIsUserSpecified = false;
private @Nullable SortDimension mSortedDimension;
- public SortModel(Collection<SortDimension> columns) {
+ @VisibleForTesting
+ SortModel(Collection<SortDimension> columns) {
mDimensions = new SparseArray<>(columns.size());
for (SortDimension column : columns) {
@@ -221,9 +226,9 @@
notifyListeners(UPDATE_TYPE_VISIBILITY);
}
- public Cursor sortCursor(Cursor cursor) {
+ public Cursor sortCursor(Cursor cursor, Lookup<String, String> fileTypesMap) {
if (mSortedDimension != null) {
- return new SortingCursorWrapper(cursor, mSortedDimension);
+ return new SortingCursorWrapper(cursor, mSortedDimension, fileTypesMap);
} else {
return cursor;
}
@@ -468,6 +473,16 @@
.build()
);
+ // Type column
+ dimensions.add(builder
+ .withId(SORT_DIMENSION_ID_FILE_TYPE)
+ .withLabelId(R.string.sort_dimension_file_type)
+ .withDataType(SortDimension.DATA_TYPE_STRING)
+ .withSortCapability(SortDimension.SORT_CAPABILITY_BOTH_DIRECTION)
+ .withDefaultSortDirection(SortDimension.SORT_DIRECTION_ASCENDING)
+ .withVisibility(View.VISIBLE)
+ .build());
+
// Date column
dimensions.add(builder
.withId(SORT_DIMENSION_ID_DATE)
diff --git a/src/com/android/documentsui/sorting/SortingCursorWrapper.java b/src/com/android/documentsui/sorting/SortingCursorWrapper.java
index 691c1d7..ce50513 100644
--- a/src/com/android/documentsui/sorting/SortingCursorWrapper.java
+++ b/src/com/android/documentsui/sorting/SortingCursorWrapper.java
@@ -24,6 +24,7 @@
import android.os.Bundle;
import android.provider.DocumentsContract.Document;
+import com.android.documentsui.base.Lookup;
import com.android.documentsui.base.Shared;
import com.android.documentsui.sorting.SortModel.SortDimensionId;
@@ -36,20 +37,22 @@
private final int[] mPosition;
- public SortingCursorWrapper(Cursor cursor, SortDimension dimension) {
+ public SortingCursorWrapper(
+ Cursor cursor, SortDimension dimension, Lookup<String, String> fileTypeLookup) {
mCursor = cursor;
final int count = cursor.getCount();
mPosition = new int[count];
boolean[] isDirs = new boolean[count];
- String[] displayNames = null;
+ String[] stringValues = null;
long[] longValues = null;
String[] ids = null;
final @SortDimensionId int id = dimension.getId();
switch (id) {
case SortModel.SORT_DIMENSION_ID_TITLE:
- displayNames = new String[count];
+ case SortModel.SORT_DIMENSION_ID_FILE_TYPE:
+ stringValues = new String[count];
break;
case SortModel.SORT_DIMENSION_ID_DATE:
case SortModel.SORT_DIMENSION_ID_SIZE:
@@ -70,7 +73,10 @@
case SortModel.SORT_DIMENSION_ID_TITLE:
final String displayName = getCursorString(
mCursor, Document.COLUMN_DISPLAY_NAME);
- displayNames[i] = displayName;
+ stringValues[i] = displayName;
+ break;
+ case SortModel.SORT_DIMENSION_ID_FILE_TYPE:
+ stringValues[i] = fileTypeLookup.lookup(mimeType);
break;
case SortModel.SORT_DIMENSION_ID_DATE:
longValues[i] = getLastModified(mCursor);
@@ -86,7 +92,8 @@
switch (id) {
case SortModel.SORT_DIMENSION_ID_TITLE:
- binarySort(displayNames, isDirs, mPosition, dimension.getSortDirection());
+ case SortModel.SORT_DIMENSION_ID_FILE_TYPE:
+ binarySort(stringValues, isDirs, mPosition, dimension.getSortDirection());
break;
case SortModel.SORT_DIMENSION_ID_DATE:
case SortModel.SORT_DIMENSION_ID_SIZE:
diff --git a/src/com/android/documentsui/sorting/TableHeaderController.java b/src/com/android/documentsui/sorting/TableHeaderController.java
index da82a6e..72aec53 100644
--- a/src/com/android/documentsui/sorting/TableHeaderController.java
+++ b/src/com/android/documentsui/sorting/TableHeaderController.java
@@ -32,6 +32,7 @@
private final HeaderCell mTitleCell;
private final HeaderCell mSummaryCell;
private final HeaderCell mSizeCell;
+ private final HeaderCell mFileTypeCell;
private final HeaderCell mDateCell;
// We assign this here porque each method reference creates a new object
@@ -50,6 +51,7 @@
mTitleCell = (HeaderCell) tableHeader.findViewById(android.R.id.title);
mSummaryCell = (HeaderCell) tableHeader.findViewById(android.R.id.summary);
mSizeCell = (HeaderCell) tableHeader.findViewById(R.id.size);
+ mFileTypeCell = (HeaderCell) tableHeader.findViewById(R.id.file_type);
mDateCell = (HeaderCell) tableHeader.findViewById(R.id.date);
onModelUpdate(mModel, SortModel.UPDATE_TYPE_UNSPECIFIED);
@@ -61,6 +63,7 @@
bindCell(mTitleCell, SortModel.SORT_DIMENSION_ID_TITLE);
bindCell(mSummaryCell, SortModel.SORT_DIMENSION_ID_SUMMARY);
bindCell(mSizeCell, SortModel.SORT_DIMENSION_ID_SIZE);
+ bindCell(mFileTypeCell, SortModel.SORT_DIMENSION_ID_FILE_TYPE);
bindCell(mDateCell, SortModel.SORT_DIMENSION_ID_DATE);
}
diff --git a/tests/common/com/android/documentsui/DocumentsProviderHelper.java b/tests/common/com/android/documentsui/DocumentsProviderHelper.java
index 3045feb..f56e1be 100644
--- a/tests/common/com/android/documentsui/DocumentsProviderHelper.java
+++ b/tests/common/com/android/documentsui/DocumentsProviderHelper.java
@@ -163,7 +163,7 @@
}
public void assertChildCount(String parentId, int expected) throws Exception {
- List<DocumentInfo> children = listChildren(parentId);
+ List<DocumentInfo> children = listChildren(parentId, -1);
assertEquals("Incorrect file count after copy", expected, children.size());
}
@@ -264,10 +264,19 @@
}
public List<DocumentInfo> listChildren(String documentId) throws Exception {
+ return listChildren(documentId, 100);
+ }
+
+ public List<DocumentInfo> listChildren(Uri parentUri, int maxCount) throws Exception {
+ String id = DocumentsContract.getDocumentId(parentUri);
+ return listChildren(id, maxCount);
+ }
+
+ public List<DocumentInfo> listChildren(String documentId, int maxCount) throws Exception {
Uri uri = buildChildDocumentsUri(mAuthority, documentId);
List<DocumentInfo> children = new ArrayList<>();
try (Cursor cursor = mClient.query(uri, null, null, null, null, null)) {
- Cursor wrapper = new RootCursorWrapper(mAuthority, "totally-fake", cursor, 100);
+ Cursor wrapper = new RootCursorWrapper(mAuthority, "totally-fake", cursor, maxCount);
while (wrapper.moveToNext()) {
children.add(DocumentInfo.fromDirectoryCursor(wrapper));
}
@@ -314,4 +323,8 @@
extra.putLong(DocumentsContract.EXTRA_LOADING, duration);
mClient.call("setLoadingDuration", null, extra);
}
+
+ public void configure(String args, Bundle configuration) throws RemoteException {
+ mClient.call("configure", args, configuration);
+ }
}
diff --git a/tests/common/com/android/documentsui/StubProvider.java b/tests/common/com/android/documentsui/StubProvider.java
index 9b6dbd4..0468bb4 100644
--- a/tests/common/com/android/documentsui/StubProvider.java
+++ b/tests/common/com/android/documentsui/StubProvider.java
@@ -65,6 +65,8 @@
public static final String EXTRA_STREAM_TYPES
= "com.android.documentsui.stubprovider.STREAM_TYPES";
public static final String EXTRA_CONTENT = "com.android.documentsui.stubprovider.CONTENT";
+ public static final String EXTRA_ENABLE_ROOT_NOTIFICATION
+ = "com.android.documentsui.stubprovider.ROOT_NOTIFICATION";
public static final String EXTRA_FLAGS = "com.android.documentsui.stubprovider.FLAGS";
public static final String EXTRA_PARENT_ID = "com.android.documentsui.stubprovider.PARENT";
@@ -91,6 +93,7 @@
private SharedPreferences mPrefs;
private Set<String> mSimulateReadErrorIds = new HashSet<>();
private long mLoadingDuration = 0;
+ private boolean mRootNotification = true;
@Override
public void attachInfo(Context context, ProviderInfo info) {
@@ -630,16 +633,19 @@
private void configure(String arg, Bundle extras) {
Log.d(TAG, "Configure " + arg);
String rootName = extras.getString(EXTRA_ROOT, ROOT_0_ID);
- long rootSize = extras.getLong(EXTRA_SIZE, 1) * 1024 * 1024;
+ long rootSize = extras.getLong(EXTRA_SIZE, 100) * 1024 * 1024;
setSize(rootName, rootSize);
+ mRootNotification = extras.getBoolean(EXTRA_ENABLE_ROOT_NOTIFICATION, true);
}
private void notifyParentChanged(String parentId) {
getContext().getContentResolver().notifyChange(
DocumentsContract.buildChildDocumentsUri(mAuthority, parentId), null, false);
- // Notify also about possible change in remaining space on the root.
- getContext().getContentResolver().notifyChange(DocumentsContract.buildRootsUri(mAuthority),
- null, false);
+ if (mRootNotification) {
+ // Notify also about possible change in remaining space on the root.
+ getContext().getContentResolver().notifyChange(
+ DocumentsContract.buildRootsUri(mAuthority), null, false);
+ }
}
private void includeDocument(MatrixCursor result, StubDocument document) {
diff --git a/tests/common/com/android/documentsui/bots/UiBot.java b/tests/common/com/android/documentsui/bots/UiBot.java
index 82f4822..93c53e3 100644
--- a/tests/common/com/android/documentsui/bots/UiBot.java
+++ b/tests/common/com/android/documentsui/bots/UiBot.java
@@ -32,7 +32,6 @@
import android.content.Context;
import android.support.test.espresso.Espresso;
-import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.action.ViewActions;
import android.support.test.espresso.matcher.BoundedMatcher;
import android.support.test.espresso.matcher.ViewMatchers;
diff --git a/tests/common/com/android/documentsui/testing/TestEnv.java b/tests/common/com/android/documentsui/testing/TestEnv.java
index 8d1b8a0..487585d 100644
--- a/tests/common/com/android/documentsui/testing/TestEnv.java
+++ b/tests/common/com/android/documentsui/testing/TestEnv.java
@@ -88,6 +88,7 @@
null, //ScopedPreferences are not required for tests
null, //a MessageBuilder is not required for tests
dialogs,
+ new TestFileTypeLookup(),
model);
injector.selectionMgr = selectionMgr;
injector.focusManager = new FocusManager(features, selectionMgr, null, null, 0);
diff --git a/tests/common/com/android/documentsui/testing/TestFileTypeLookup.java b/tests/common/com/android/documentsui/testing/TestFileTypeLookup.java
new file mode 100644
index 0000000..98d3d01
--- /dev/null
+++ b/tests/common/com/android/documentsui/testing/TestFileTypeLookup.java
@@ -0,0 +1,35 @@
+/*
+ * 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.testing;
+
+import com.android.documentsui.base.Lookup;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class TestFileTypeLookup implements Lookup<String, String> {
+
+ public static final String DEFAULT_TYPE = "default_type";
+ public final Map<String, String> fileTypes = new HashMap<>();
+
+ @Override
+ public String lookup(String mimeType) {
+ final String type = fileTypes.get(mimeType);
+
+ return type == null ? DEFAULT_TYPE : type;
+ }
+}
diff --git a/tests/functional/com/android/documentsui/ActivityTest.java b/tests/functional/com/android/documentsui/ActivityTest.java
index 1f97b8b..2c31c7b 100644
--- a/tests/functional/com/android/documentsui/ActivityTest.java
+++ b/tests/functional/com/android/documentsui/ActivityTest.java
@@ -22,6 +22,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.os.Bundle;
import android.os.RemoteException;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
@@ -127,6 +128,9 @@
// so if a drawer is on top of a file we want to select, it will actually click the drawer.
// Thus to start a clean state, we always try to close first.
bots.roots.closeDrawer();
+
+ // Configure the provider back to default.
+ mDocsHelper.configure(null, Bundle.EMPTY);
}
@Override
diff --git a/tests/functional/com/android/documentsui/FileManagementUiTest.java b/tests/functional/com/android/documentsui/FileManagementUiTest.java
index 567429e..5157ec3 100644
--- a/tests/functional/com/android/documentsui/FileManagementUiTest.java
+++ b/tests/functional/com/android/documentsui/FileManagementUiTest.java
@@ -20,12 +20,19 @@
import static com.android.documentsui.StubProvider.ROOT_1_ID;
import android.net.Uri;
+import android.os.Bundle;
import android.os.RemoteException;
import android.support.test.filters.LargeTest;
import android.support.test.filters.Suppress;
import android.view.KeyEvent;
+import com.android.documentsui.base.DocumentInfo;
+import com.android.documentsui.base.Shared;
import com.android.documentsui.files.FilesActivity;
+import com.android.documentsui.sorting.SortDimension;
+import com.android.documentsui.sorting.SortModel;
+
+import java.util.List;
@LargeTest
public class FileManagementUiTest extends ActivityTest<FilesActivity> {
@@ -122,4 +129,72 @@
bots.directory.waitForDocument("file1.png");
}
+
+ public void testCopyLargeAmountOfFiles() throws Exception {
+ // Suppress root notification. We're gonna create tons of files and it will soon crash
+ // DocsUI because too many root refreshes are queued in an executor.
+ Bundle conf = new Bundle();
+ conf.putBoolean(StubProvider.EXTRA_ENABLE_ROOT_NOTIFICATION, false);
+ mDocsHelper.configure(null, conf);
+
+ final Uri test = mDocsHelper.createFolder(rootDir0, "test");
+ final Uri target = mDocsHelper.createFolder(rootDir0, "target");
+ String nameOfLastFile = "";
+ for (int i = 0; i <= Shared.MAX_DOCS_IN_INTENT; ++i) {
+ final String name = i + ".txt";
+ final Uri doc =
+ mDocsHelper.createDocument(test, "text/plain", name);
+ mDocsHelper.writeDocument(doc, Integer.toString(i).getBytes());
+ nameOfLastFile = nameOfLastFile.compareTo(name) < 0 ? name : nameOfLastFile;
+ }
+
+ bots.roots.openRoot(ROOT_0_ID);
+ bots.directory.openDocument("test");
+ bots.sortHeader.sortBy(
+ SortModel.SORT_DIMENSION_ID_TITLE, SortDimension.SORT_DIRECTION_ASCENDING);
+ bots.directory.waitForDocument("0.txt");
+ bots.keyboard.pressKey(
+ KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON);
+ bots.keyboard.pressKey(
+ KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON);
+
+ bots.roots.openRoot(ROOT_0_ID);
+ bots.directory.openDocument("target");
+ bots.directory.pasteFilesFromClipboard();
+
+ // Use these 2 events as a signal that many files have already been copied. Only considering
+ // Android devices a more reliable way is to wait until notification goes away, but ARC++
+ // uses Chrome OS notifications so it isn't even an option.
+ bots.directory.waitForDocument("0.txt");
+ bots.directory.waitForDocument(nameOfLastFile);
+
+ final int expectedCount = Shared.MAX_DOCS_IN_INTENT + 1;
+ List<DocumentInfo> children = mDocsHelper.listChildren(target, -1);
+ if (children.size() == expectedCount) {
+ return;
+ }
+
+ // Files weren't copied fast enough, so gonna do some polling until they all arrive or copy
+ // seems stalled.
+ while (true) {
+ Thread.sleep(200);
+ List<DocumentInfo> newChildren = mDocsHelper.listChildren(target, -1);
+ if (newChildren.size() == expectedCount) {
+ return;
+ }
+
+ if (newChildren.size() > expectedCount) {
+ // Should never happen
+ fail("Something wrong with this test case. Copied file count "
+ + newChildren.size() + " exceeds expected number " + expectedCount);
+ }
+
+ if (newChildren.size() <= children.size()) {
+ fail("Only copied " + children.size()
+ + " files, expected to copy " + expectedCount + " files.");
+ }
+
+ children = newChildren;
+ }
+ }
}
diff --git a/tests/unit/com/android/documentsui/FileTypeMapTest.java b/tests/unit/com/android/documentsui/FileTypeMapTest.java
new file mode 100644
index 0000000..13f2137
--- /dev/null
+++ b/tests/unit/com/android/documentsui/FileTypeMapTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.annotation.StringRes;
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class FileTypeMapTest {
+
+ private Resources mRes;
+ private FileTypeMap mMap;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getTargetContext();
+ mRes = context.getResources();
+ mMap = new FileTypeMap(context);
+ }
+
+ @Test
+ public void testPlainTextType() {
+ String expected = mRes.getString(R.string.txt_file_type);
+ assertEquals(expected, mMap.lookup("text/plain"));
+ }
+
+ @Test
+ public void testPortableDocumentFormatType() {
+ String expected = mRes.getString(R.string.pdf_file_type);
+ assertEquals(expected, mMap.lookup("application/pdf"));
+ }
+
+ @Test
+ public void testMsWordType() {
+ String expected = mRes.getString(R.string.word_file_type);
+ assertEquals(expected, mMap.lookup("application/msword"));
+ }
+
+ @Test
+ public void testGoogleDocType() {
+ String expected = mRes.getString(R.string.gdoc_file_type);
+ assertEquals(expected, mMap.lookup("application/vnd.google-apps.document"));
+ }
+
+ @Test
+ public void testZipType() {
+ String expected = getExtensionType(R.string.archive_file_type, "Zip");
+ assertEquals(expected, mMap.lookup("application/zip"));
+ }
+
+ @Test
+ public void testMp3Type() {
+ String expected = getExtensionType(R.string.audio_extension_file_type, "MP3");
+ assertEquals(expected, mMap.lookup("audio/mpeg"));
+ }
+
+ @Test
+ public void testMkvType() {
+ String expected = getExtensionType(R.string.video_extension_file_type, "AVI");
+ assertEquals(expected, mMap.lookup("video/avi"));
+ }
+
+ @Test
+ public void testJpgType() {
+ String expected = getExtensionType(R.string.image_extension_file_type, "JPG");
+ assertEquals(expected, mMap.lookup("image/jpeg"));
+ }
+
+ @Test
+ public void testOggType() {
+ String expected = getExtensionType(R.string.audio_extension_file_type, "OGG");
+ assertEquals(expected, mMap.lookup("application/ogg"));
+ }
+
+ @Test
+ public void testFlacType() {
+ String expected = getExtensionType(R.string.audio_extension_file_type, "FLAC");
+ assertEquals(expected, mMap.lookup("application/x-flac"));
+ }
+
+ private String getExtensionType(@StringRes int formatStringId, String extension) {
+ String format = mRes.getString(formatStringId);
+ return String.format(format, extension);
+ }
+}
diff --git a/tests/unit/com/android/documentsui/RecentsLoaderTests.java b/tests/unit/com/android/documentsui/RecentsLoaderTests.java
index 80993ba..657432f 100644
--- a/tests/unit/com/android/documentsui/RecentsLoaderTests.java
+++ b/tests/unit/com/android/documentsui/RecentsLoaderTests.java
@@ -29,6 +29,7 @@
import com.android.documentsui.testing.ActivityManagers;
import com.android.documentsui.testing.TestEnv;
import com.android.documentsui.testing.TestFeatures;
+import com.android.documentsui.testing.TestFileTypeLookup;
import com.android.documentsui.testing.TestImmediateExecutor;
import com.android.documentsui.testing.TestProvidersAccess;
@@ -54,7 +55,7 @@
mEnv.state.acceptMimes = new String[] { "*/*" };
mLoader = new RecentsLoader(mActivity, mEnv.providers, mEnv.state, mEnv.features,
- TestImmediateExecutor.createLookup());
+ TestImmediateExecutor.createLookup(), new TestFileTypeLookup());
}
@Test
diff --git a/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java b/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java
index 4ce5e21..68169bc 100644
--- a/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java
@@ -31,6 +31,7 @@
import com.android.documentsui.base.State;
import com.android.documentsui.testing.TestActionHandler;
import com.android.documentsui.testing.TestEnv;
+import com.android.documentsui.testing.TestFileTypeLookup;
@MediumTest
public class DirectoryAddonsAdapterTest extends AndroidTestCase {
@@ -53,7 +54,7 @@
mAdapter = new DirectoryAddonsAdapter(
env,
new ModelBackedDocumentsAdapter(
- env, new IconHelper(testContext, State.MODE_GRID)));
+ env, new IconHelper(testContext, State.MODE_GRID), new TestFileTypeLookup()));
mEnv.model.addUpdateListener(mAdapter.getModelUpdateListener());
}
diff --git a/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java b/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
index 18c5ef9..88ea13d 100644
--- a/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
@@ -28,6 +28,7 @@
import com.android.documentsui.base.State;
import com.android.documentsui.testing.TestActionHandler;
import com.android.documentsui.testing.TestEnv;
+import com.android.documentsui.testing.TestFileTypeLookup;
@MediumTest
public class ModelBackedDocumentsAdapterTest extends AndroidTestCase {
@@ -47,7 +48,7 @@
DocumentsAdapter.Environment env = new TestEnvironment(testContext);
mAdapter = new ModelBackedDocumentsAdapter(
- env, new IconHelper(testContext, State.MODE_GRID));
+ env, new IconHelper(testContext, State.MODE_GRID), new TestFileTypeLookup());
mAdapter.getModelUpdateListener().accept(Model.Update.UPDATE);
}
diff --git a/tests/unit/com/android/documentsui/sorting/SortingCursorWrapperTest.java b/tests/unit/com/android/documentsui/sorting/SortingCursorWrapperTest.java
index eb1c54f..6705c4d 100644
--- a/tests/unit/com/android/documentsui/sorting/SortingCursorWrapperTest.java
+++ b/tests/unit/com/android/documentsui/sorting/SortingCursorWrapperTest.java
@@ -26,6 +26,7 @@
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
+import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import com.android.documentsui.base.DocumentInfo;
@@ -33,6 +34,7 @@
import com.android.documentsui.roots.RootCursorWrapper;
import com.android.documentsui.sorting.SortModel.SortDimensionId;
import com.android.documentsui.testing.SortModels;
+import com.android.documentsui.testing.TestFileTypeLookup;
import org.junit.Before;
import org.junit.Test;
@@ -46,6 +48,7 @@
import java.util.Set;
@RunWith(AndroidJUnit4.class)
+@SmallTest
public class SortingCursorWrapperTest {
private static final int ITEM_COUNT = 10;
private static final String AUTHORITY = "test_authority";
@@ -73,12 +76,40 @@
"%$%VD"
};
+ private static final String[] MIMES = new String[] {
+ "application/zip",
+ "video/3gp",
+ "image/png",
+ "text/plain",
+ "application/msword",
+ "text/html",
+ "application/pdf",
+ "image/png",
+ "audio/flac",
+ "audio/mp3"
+ };
+
+ private static final String[] TYPES = new String[] {
+ "Zip archive",
+ "3GP video",
+ "PNG image",
+ "Plain text",
+ "Word document",
+ "HTML document",
+ "PDF document",
+ "PNG image",
+ "FLAC audio",
+ "MP3 audio"
+ };
+
+ private TestFileTypeLookup fileTypeLookup;
private SortModel sortModel;
private Cursor cursor;
@Before
public void setUp() {
sortModel = SortModels.createTestSortModel();
+ fileTypeLookup = new TestFileTypeLookup();
Random rand = new Random();
MatrixCursor c = new MatrixCursor(COLUMNS);
@@ -91,6 +122,7 @@
// to actually do something.
row.add(Document.COLUMN_DISPLAY_NAME, NAMES[i]);
row.add(Document.COLUMN_SIZE, rand.nextInt());
+ row.add(Document.COLUMN_MIME_TYPE, MIMES[i]);
}
cursor = c;
@@ -368,6 +400,76 @@
}
@Test
+ public void testSort_type_ascending() {
+ populateTypeMap();
+
+ sortModel.sortByUser(
+ SortModel.SORT_DIMENSION_ID_FILE_TYPE, SortDimension.SORT_DIRECTION_ASCENDING);
+
+ final Cursor cursor = createSortingCursorWrapper();
+
+ assertEquals(ITEM_COUNT, cursor.getCount());
+ final BitSet seen = new BitSet(ITEM_COUNT);
+ List<String> types = new ArrayList<>(ITEM_COUNT);
+ for (int i = 0; i < ITEM_COUNT; ++i) {
+ cursor.moveToPosition(i);
+ final String mime =
+ DocumentInfo.getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+ final String type = fileTypeLookup.lookup(mime);
+ types.add(type);
+
+ seen.set(DocumentInfo.getCursorInt(cursor, Document.COLUMN_DOCUMENT_ID));
+ }
+
+ // Check all items were accounted for
+ assertEquals(ITEM_COUNT, seen.cardinality());
+ for (int i = 0; i < ITEM_COUNT - 1; ++i) {
+ final String lhs = types.get(i);
+ final String rhs = types.get(i + 1);
+ assertTrue(lhs + " is not smaller than " + rhs,
+ Shared.compareToIgnoreCaseNullable(lhs, rhs) <= 0);
+ }
+ }
+
+ @Test
+ public void testSort_type_descending() {
+ populateTypeMap();
+
+ sortModel.sortByUser(
+ SortModel.SORT_DIMENSION_ID_FILE_TYPE, SortDimension.SORT_DIRECTION_DESCENDING);
+
+ final Cursor cursor = createSortingCursorWrapper();
+
+ assertEquals(ITEM_COUNT, cursor.getCount());
+ final BitSet seen = new BitSet(ITEM_COUNT);
+ List<String> types = new ArrayList<>(ITEM_COUNT);
+ for (int i = 0; i < ITEM_COUNT; ++i) {
+ cursor.moveToPosition(i);
+ final String mime =
+ DocumentInfo.getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+ final String type = fileTypeLookup.lookup(mime);
+ types.add(type);
+
+ seen.set(DocumentInfo.getCursorInt(cursor, Document.COLUMN_DOCUMENT_ID));
+ }
+
+ // Check all items were accounted for
+ assertEquals(ITEM_COUNT, seen.cardinality());
+ for (int i = 0; i < ITEM_COUNT - 1; ++i) {
+ final String lhs = types.get(i);
+ final String rhs = types.get(i + 1);
+ assertTrue(lhs + " is not smaller than " + rhs,
+ Shared.compareToIgnoreCaseNullable(lhs, rhs) >= 0);
+ }
+ }
+
+ private void populateTypeMap() {
+ for (int i = 0; i < ITEM_COUNT; ++i) {
+ fileTypeLookup.fileTypes.put(MIMES[i], TYPES[i]);
+ }
+ }
+
+ @Test
public void testReturnsWrappedExtras() {
MatrixCursor c = new MatrixCursor(COLUMNS);
Bundle extras = new Bundle();
@@ -394,6 +496,6 @@
private Cursor createSortingCursorWrapper(Cursor c) {
final @SortDimensionId int id = sortModel.getSortedDimensionId();
- return new SortingCursorWrapper(c, sortModel.getDimensionById(id));
+ return new SortingCursorWrapper(c, sortModel.getDimensionById(id), fileTypeLookup);
}
}