Add a type column to show user-friendly type info.
Also add a special map from mime type to user friendly strings.
Test: Auto tests & smoke tests.
Bug: 34844878
Change-Id: I773f2bae524b7dffc6eeda1a1e92330b5ad64473
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 e10cdba..fda6b77 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);
}