Use Cursors directly when binding documents.

Instead of creating a DocumentInfo for every list item, bind the
adapter against Cursor directly.

Create new SortingCursorWrapper which performs sorting at query time
and keeps a O(1) mapping from sorted to unsorted positions in the
underlying Cursor.

Suppress extra loader passes that had been kicked off.  Use unstable
provider when querying to guard against broken providers.

Bug: 10567506, 10510851
Change-Id: I535814da6b17c38de04a1175e0afcc78c6b966ce
This commit is contained in:
Jeff Sharkey
2013-08-31 21:27:44 -07:00
parent ae9b51bfa3
commit 5dfb345df7
6 changed files with 511 additions and 190 deletions

View File

@@ -20,9 +20,9 @@ import static com.android.documentsui.DocumentsActivity.TAG;
import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANAGE;
import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_GRID;
import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_LIST;
import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_DISPLAY_NAME;
import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_LAST_MODIFIED;
import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_SIZE;
import static com.android.documentsui.model.DocumentInfo.getCursorInt;
import static com.android.documentsui.model.DocumentInfo.getCursorLong;
import static com.android.documentsui.model.DocumentInfo.getCursorString;
import android.app.Fragment;
import android.app.FragmentManager;
@@ -32,12 +32,14 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.text.format.DateUtils;
import android.text.format.Formatter;
import android.text.format.Time;
@@ -66,7 +68,6 @@ import com.android.internal.util.Predicate;
import com.google.android.collect.Lists;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@@ -85,7 +86,6 @@ public class DirectoryFragment extends Fragment {
public static final int TYPE_NORMAL = 1;
public static final int TYPE_SEARCH = 2;
public static final int TYPE_RECENT_OPEN = 3;
private int mType = TYPE_NORMAL;
@@ -99,6 +99,8 @@ public class DirectoryFragment extends Fragment {
private static AtomicInteger sLoaderId = new AtomicInteger(4000);
private int mLastSortOrder = -1;
private final int mLoaderId = sLoaderId.incrementAndGet();
public static void showNormal(FragmentManager fm, Uri uri) {
@@ -111,8 +113,9 @@ public class DirectoryFragment extends Fragment {
show(fm, TYPE_SEARCH, searchUri);
}
@Deprecated
public static void showRecentsOpen(FragmentManager fm) {
show(fm, TYPE_RECENT_OPEN, null);
// TODO: new recents behavior
}
private static void show(FragmentManager fm, int type, Uri uri) {
@@ -137,7 +140,6 @@ public class DirectoryFragment extends Fragment {
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final Context context = inflater.getContext();
final View view = inflater.inflate(R.layout.fragment_directory, container, false);
mEmptyView = view.findViewById(android.R.id.empty);
@@ -150,80 +152,65 @@ public class DirectoryFragment extends Fragment {
mGridView.setOnItemClickListener(mItemListener);
mGridView.setMultiChoiceModeListener(mMultiListener);
mAdapter = new DocumentsAdapter();
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final Context context = getActivity();
final Uri uri = getArguments().getParcelable(EXTRA_URI);
mAdapter = new DocumentsAdapter(uri.getAuthority());
mType = getArguments().getInt(EXTRA_TYPE);
mCallbacks = new LoaderCallbacks<DirectoryResult>() {
@Override
public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
final DisplayState state = getDisplayState(DirectoryFragment.this);
mFilter = new MimePredicate(state.acceptMimes);
Uri contentsUri;
if (mType == TYPE_NORMAL) {
contentsUri = DocumentsContract.buildChildDocumentsUri(
uri.getAuthority(), DocumentsContract.getDocumentId(uri));
} else if (mType == TYPE_RECENT_OPEN) {
contentsUri = RecentsProvider.buildRecentOpen();
} else {
contentsUri = uri;
}
final Comparator<DocumentInfo> sortOrder;
if (state.sortOrder == SORT_ORDER_LAST_MODIFIED || mType == TYPE_RECENT_OPEN) {
sortOrder = new DocumentInfo.LastModifiedComparator();
} else if (state.sortOrder == SORT_ORDER_DISPLAY_NAME) {
sortOrder = new DocumentInfo.DisplayNameComparator();
} else if (state.sortOrder == SORT_ORDER_SIZE) {
sortOrder = new DocumentInfo.SizeComparator();
} else {
throw new IllegalArgumentException("Unknown sort order " + state.sortOrder);
}
return new DirectoryLoader(context, contentsUri, mType, null, sortOrder);
return new DirectoryLoader(context, contentsUri, state.sortOrder);
}
@Override
public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
mAdapter.swapDocuments(result.contents);
mAdapter.swapCursor(result.cursor);
}
@Override
public void onLoaderReset(Loader<DirectoryResult> loader) {
mAdapter.swapDocuments(null);
mAdapter.swapCursor(null);
}
};
updateDisplayState();
return view;
}
@Override
public void onStart() {
super.onStart();
getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks);
}
@Override
public void onStop() {
super.onStop();
getLoaderManager().destroyLoader(mLoaderId);
}
public void updateDisplayState() {
final DisplayState state = getDisplayState(this);
// TODO: avoid kicking loader when nothing changed
getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks);
if (mLastSortOrder != state.sortOrder) {
getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
mLastSortOrder = state.sortOrder;
}
mListView.smoothScrollToPosition(0);
mGridView.smoothScrollToPosition(0);
mListView.setVisibility(state.mode == MODE_LIST ? View.VISIBLE : View.GONE);
mGridView.setVisibility(state.mode == MODE_GRID ? View.VISIBLE : View.GONE);
mFilter = new MimePredicate(state.acceptMimes);
final int choiceMode;
if (state.allowMultiple) {
choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
@@ -258,7 +245,9 @@ public class DirectoryFragment extends Fragment {
private OnItemClickListener mItemListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final DocumentInfo doc = mAdapter.getItem(position);
final Cursor cursor = mAdapter.getItem(position);
final Uri uri = getArguments().getParcelable(EXTRA_URI);
final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(uri, cursor);
if (mFilter.apply(doc)) {
((DocumentsActivity) getActivity()).onDocumentPicked(doc);
}
@@ -295,7 +284,9 @@ public class DirectoryFragment extends Fragment {
final int size = checked.size();
for (int i = 0; i < size; i++) {
if (checked.valueAt(i)) {
final DocumentInfo doc = mAdapter.getItem(checked.keyAt(i));
final Cursor cursor = mAdapter.getItem(checked.keyAt(i));
final Uri uri = getArguments().getParcelable(EXTRA_URI);
final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(uri, cursor);
docs.add(doc);
}
}
@@ -328,8 +319,9 @@ public class DirectoryFragment extends Fragment {
ActionMode mode, int position, long id, boolean checked) {
if (checked) {
// Directories cannot be checked
final DocumentInfo doc = mAdapter.getItem(position);
if (doc.isDirectory()) {
final Cursor cursor = mAdapter.getItem(position);
final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
mCurrentView.setItemChecked(position, false);
}
}
@@ -396,15 +388,18 @@ public class DirectoryFragment extends Fragment {
}
private class DocumentsAdapter extends BaseAdapter {
private List<DocumentInfo> mDocuments;
private final String mAuthority;
public DocumentsAdapter() {
private Cursor mCursor;
public DocumentsAdapter(String authority) {
mAuthority = authority;
}
public void swapDocuments(List<DocumentInfo> documents) {
mDocuments = documents;
public void swapCursor(Cursor cursor) {
mCursor = cursor;
if (mDocuments != null && mDocuments.isEmpty()) {
if (isEmpty()) {
mEmptyView.setVisibility(View.VISIBLE);
} else {
mEmptyView.setVisibility(View.GONE);
@@ -433,7 +428,16 @@ public class DirectoryFragment extends Fragment {
}
}
final DocumentInfo doc = getItem(position);
final Cursor cursor = getItem(position);
final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
final String docDisplayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
final long docLastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
final int docIcon = getCursorInt(cursor, Document.COLUMN_ICON);
final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY);
final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
@@ -448,32 +452,31 @@ public class DirectoryFragment extends Fragment {
oldTask.cancel(false);
}
if (doc.isThumbnailSupported()) {
final Bitmap cachedResult = thumbs.get(doc.uri);
if ((docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0) {
final Uri uri = DocumentsContract.buildDocumentUri(mAuthority, docId);
final Bitmap cachedResult = thumbs.get(uri);
if (cachedResult != null) {
icon.setImageBitmap(cachedResult);
} else {
final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize);
icon.setImageBitmap(null);
icon.setTag(task);
task.execute(doc.uri);
task.execute(uri);
}
} else if (docIcon != 0) {
icon.setImageDrawable(DocumentInfo.loadIcon(context, mAuthority, docIcon));
} else {
icon.setImageDrawable(RootsCache.resolveDocumentIcon(context, doc.mimeType));
icon.setImageDrawable(RootsCache.resolveDocumentIcon(context, docMimeType));
}
title.setText(doc.displayName);
title.setText(docDisplayName);
if (mType == TYPE_NORMAL || mType == TYPE_SEARCH) {
icon1.setVisibility(View.GONE);
if (doc.summary != null) {
summary.setText(doc.summary);
summary.setVisibility(View.VISIBLE);
} else {
summary.setVisibility(View.INVISIBLE);
}
} else if (mType == TYPE_RECENT_OPEN) {
// TODO: resolve storage root
icon1.setVisibility(View.GONE);
if (docSummary != null) {
summary.setText(docSummary);
summary.setVisibility(View.VISIBLE);
} else {
summary.setVisibility(View.INVISIBLE);
}
if (summaryGrid != null) {
@@ -481,18 +484,18 @@ public class DirectoryFragment extends Fragment {
(summary.getVisibility() == View.VISIBLE) ? View.VISIBLE : View.GONE);
}
if (doc.lastModified == -1) {
if (docLastModified == -1) {
date.setText(null);
} else {
date.setText(formatTime(context, doc.lastModified));
date.setText(formatTime(context, docLastModified));
}
if (state.showSize) {
size.setVisibility(View.VISIBLE);
if (doc.isDirectory() || doc.size == -1) {
if (Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
size.setText(null);
} else {
size.setText(Formatter.formatFileSize(context, doc.size));
size.setText(Formatter.formatFileSize(context, docSize));
}
} else {
size.setVisibility(View.GONE);
@@ -503,17 +506,20 @@ public class DirectoryFragment extends Fragment {
@Override
public int getCount() {
return mDocuments != null ? mDocuments.size() : 0;
return mCursor != null ? mCursor.getCount() : 0;
}
@Override
public DocumentInfo getItem(int position) {
return mDocuments.get(position);
public Cursor getItem(int position) {
if (mCursor != null) {
mCursor.moveToPosition(position);
}
return mCursor;
}
@Override
public long getItemId(int position) {
return getItem(position).uri.hashCode();
return position;
}
}

View File

@@ -16,98 +16,147 @@
package com.android.documentsui;
import static com.android.documentsui.DirectoryFragment.TYPE_NORMAL;
import static com.android.documentsui.DirectoryFragment.TYPE_RECENT_OPEN;
import static com.android.documentsui.DirectoryFragment.TYPE_SEARCH;
import static com.android.documentsui.DocumentsActivity.TAG;
import android.content.ContentResolver;
import android.content.AsyncTaskLoader;
import android.content.ContentProviderClient;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.CancellationSignal;
import android.util.Log;
import android.os.OperationCanceledException;
import android.provider.DocumentsContract.Document;
import com.android.documentsui.model.DocumentInfo;
import com.android.internal.util.Predicate;
import com.google.android.collect.Lists;
import com.android.documentsui.DocumentsActivity.DisplayState;
import libcore.io.IoUtils;
import java.io.FileNotFoundException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class DirectoryResult implements AutoCloseable {
ContentProviderClient client;
Cursor cursor;
List<DocumentInfo> contents = Lists.newArrayList();
Exception e;
Exception exception;
@Override
public void close() throws Exception {
public void close() {
IoUtils.closeQuietly(cursor);
ContentProviderClient.closeQuietly(client);
cursor = null;
client = null;
}
}
public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {
public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
private final int mType;
private Predicate<DocumentInfo> mFilter;
private Comparator<DocumentInfo> mSortOrder;
private final Uri mUri;
private final int mSortOrder;
public DirectoryLoader(Context context, Uri uri, int type, Predicate<DocumentInfo> filter,
Comparator<DocumentInfo> sortOrder) {
super(context, uri);
mType = type;
mFilter = filter;
private CancellationSignal mSignal;
private DirectoryResult mResult;
public DirectoryLoader(Context context, Uri uri, int sortOrder) {
super(context);
mUri = uri;
mSortOrder = sortOrder;
}
@Override
public DirectoryResult loadInBackground(Uri uri, CancellationSignal signal) {
public final DirectoryResult loadInBackground() {
synchronized (this) {
if (isLoadInBackgroundCanceled()) {
throw new OperationCanceledException();
}
mSignal = new CancellationSignal();
}
final DirectoryResult result = new DirectoryResult();
try {
loadInBackgroundInternal(result, uri, signal);
result.client = getContext()
.getContentResolver().acquireUnstableContentProviderClient(mUri.getAuthority());
final Cursor cursor = result.client.query(
mUri, null, null, null, getQuerySortOrder(), mSignal);
result.cursor = new SortingCursorWrapper(cursor, mSortOrder);
result.cursor.registerContentObserver(mObserver);
} catch (Exception e) {
result.e = e;
result.exception = e;
ContentProviderClient.closeQuietly(result.client);
} finally {
synchronized (this) {
mSignal = null;
}
}
return result;
}
private void loadInBackgroundInternal(
DirectoryResult result, Uri uri, CancellationSignal signal) throws RuntimeException {
// TODO: switch to using unstable CPC
final ContentResolver resolver = getContext().getContentResolver();
final Cursor cursor = resolver.query(uri, null, null, null, null, signal);
result.cursor = cursor;
result.cursor.registerContentObserver(mObserver);
@Override
public void cancelLoadInBackground() {
super.cancelLoadInBackground();
while (cursor.moveToNext()) {
DocumentInfo doc = null;
switch (mType) {
case TYPE_NORMAL:
case TYPE_SEARCH:
doc = DocumentInfo.fromDirectoryCursor(uri, cursor);
break;
case TYPE_RECENT_OPEN:
try {
doc = DocumentInfo.fromRecentOpenCursor(resolver, cursor);
} catch (FileNotFoundException e) {
Log.w(TAG, "Failed to find recent: " + e);
}
break;
default:
throw new IllegalArgumentException("Unknown type");
}
if (doc != null && (mFilter == null || mFilter.apply(doc))) {
result.contents.add(doc);
synchronized (this) {
if (mSignal != null) {
mSignal.cancel();
}
}
}
if (mSortOrder != null) {
Collections.sort(result.contents, mSortOrder);
@Override
public void deliverResult(DirectoryResult result) {
if (isReset()) {
IoUtils.closeQuietly(result);
return;
}
DirectoryResult oldResult = mResult;
mResult = result;
if (isStarted()) {
super.deliverResult(result);
}
if (oldResult != null && oldResult != result) {
IoUtils.closeQuietly(oldResult);
}
}
@Override
protected void onStartLoading() {
if (mResult != null) {
deliverResult(mResult);
}
if (takeContentChanged() || mResult == null) {
forceLoad();
}
}
@Override
protected void onStopLoading() {
cancelLoad();
}
@Override
public void onCanceled(DirectoryResult result) {
IoUtils.closeQuietly(result);
}
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
IoUtils.closeQuietly(mResult);
mResult = null;
getContext().getContentResolver().unregisterContentObserver(mObserver);
}
private String getQuerySortOrder() {
switch (mSortOrder) {
case DisplayState.SORT_ORDER_DISPLAY_NAME:
return Document.COLUMN_DISPLAY_NAME + " ASC";
case DisplayState.SORT_ORDER_LAST_MODIFIED:
return Document.COLUMN_LAST_MODIFIED + " DESC";
case DisplayState.SORT_ORDER_SIZE:
return Document.COLUMN_SIZE + " DESC";
default:
return null;
}
}
}

View File

@@ -61,6 +61,7 @@ public class RecentsProvider extends ContentProvider {
public static final String COL_PACKAGE_NAME = "package_name";
public static final String COL_TIMESTAMP = "timestamp";
@Deprecated
public static Uri buildRecentOpen() {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY).appendPath("recent_open").build();

View File

@@ -0,0 +1,256 @@
/*
* Copyright (C) 2013 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.database.AbstractCursor;
import android.database.Cursor;
import android.provider.DocumentsContract.Document;
import com.android.documentsui.DocumentsActivity.DisplayState;
/**
* Cursor wrapper that presents a sorted view of the underlying cursor. Handles
* common {@link Document} sorting modes, such as ordering directories first.
*/
public class SortingCursorWrapper extends AbstractCursor {
private final Cursor mCursor;
private final int[] mPosition;
private final String[] mValueString;
private final long[] mValueLong;
public SortingCursorWrapper(Cursor cursor, int sortOrder) {
mCursor = cursor;
final int count = cursor.getCount();
mPosition = new int[count];
switch (sortOrder) {
case DisplayState.SORT_ORDER_DISPLAY_NAME:
mValueString = new String[count];
mValueLong = null;
break;
case DisplayState.SORT_ORDER_LAST_MODIFIED:
case DisplayState.SORT_ORDER_SIZE:
mValueString = null;
mValueLong = new long[count];
break;
default:
throw new IllegalArgumentException();
}
final int mimeTypeIndex = cursor.getColumnIndex(Document.COLUMN_MIME_TYPE);
final int displayNameIndex = cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME);
final int lastModifiedIndex = cursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED);
final int sizeIndex = cursor.getColumnIndex(Document.COLUMN_SIZE);
cursor.moveToPosition(-1);
for (int i = 0; i < count; i++) {
cursor.moveToNext();
mPosition[i] = i;
switch (sortOrder) {
case DisplayState.SORT_ORDER_DISPLAY_NAME:
final String mimeType = cursor.getString(mimeTypeIndex);
final String displayName = cursor.getString(displayNameIndex);
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
mValueString[i] = '\001' + displayName;
} else {
mValueString[i] = displayName;
}
break;
case DisplayState.SORT_ORDER_LAST_MODIFIED:
mValueLong[i] = cursor.getLong(lastModifiedIndex);
break;
case DisplayState.SORT_ORDER_SIZE:
mValueLong[i] = cursor.getLong(sizeIndex);
break;
}
}
switch (sortOrder) {
case DisplayState.SORT_ORDER_DISPLAY_NAME:
synchronized (SortingCursorWrapper.class) {
binarySort(mPosition, mValueString);
}
break;
case DisplayState.SORT_ORDER_LAST_MODIFIED:
case DisplayState.SORT_ORDER_SIZE:
binarySort(mPosition, mValueLong);
break;
}
}
@Override
public void close() {
super.close();
mCursor.close();
}
@Override
public boolean onMove(int oldPosition, int newPosition) {
return mCursor.moveToPosition(mPosition[newPosition]);
}
@Override
public String[] getColumnNames() {
return mCursor.getColumnNames();
}
@Override
public int getCount() {
return mCursor.getCount();
}
@Override
public double getDouble(int column) {
return mCursor.getDouble(column);
}
@Override
public float getFloat(int column) {
return mCursor.getFloat(column);
}
@Override
public int getInt(int column) {
return mCursor.getInt(column);
}
@Override
public long getLong(int column) {
return mCursor.getLong(column);
}
@Override
public short getShort(int column) {
return mCursor.getShort(column);
}
@Override
public String getString(int column) {
return mCursor.getString(column);
}
@Override
public int getType(int column) {
return mCursor.getType(column);
}
@Override
public boolean isNull(int column) {
return mCursor.isNull(column);
}
/**
* Borrowed from TimSort.binarySort(), but modified to sort two column
* dataset.
*/
private static void binarySort(int[] position, String[] value) {
final int count = position.length;
for (int start = 1; start < count; start++) {
final int pivotPosition = position[start];
final String pivotValue = value[start];
int left = 0;
int right = start;
while (left < right) {
int mid = (left + right) >>> 1;
final String lhs = pivotValue;
final String rhs = value[mid];
final int compare;
if (lhs == null) {
compare = -1;
} else if (rhs == null) {
compare = 1;
} else {
compare = lhs.compareToIgnoreCase(rhs);
}
if (compare < 0) {
right = mid;
} else {
left = mid + 1;
}
}
int n = start - left;
switch (n) {
case 2:
position[left + 2] = position[left + 1];
value[left + 2] = value[left + 1];
case 1:
position[left + 1] = position[left];
value[left + 1] = value[left];
break;
default:
System.arraycopy(position, left, position, left + 1, n);
System.arraycopy(value, left, value, left + 1, n);
}
position[left] = pivotPosition;
value[left] = pivotValue;
}
}
/**
* Borrowed from TimSort.binarySort(), but modified to sort two column
* dataset.
*/
private static void binarySort(int[] position, long[] value) {
final int count = position.length;
for (int start = 1; start < count; start++) {
final int pivotPosition = position[start];
final long pivotValue = value[start];
int left = 0;
int right = start;
while (left < right) {
int mid = (left + right) >>> 1;
final long lhs = pivotValue;
final long rhs = value[mid];
final int compare = Long.compare(lhs, rhs);
if (compare > 0) {
right = mid;
} else {
left = mid + 1;
}
}
int n = start - left;
switch (n) {
case 2:
position[left + 2] = position[left + 1];
value[left + 2] = value[left + 1];
case 1:
position[left + 1] = position[left];
value[left + 1] = value[left];
break;
default:
System.arraycopy(position, left, position, left + 1, n);
System.arraycopy(value, left, value, left + 1, n);
}
position[left] = pivotPosition;
value[left] = pivotValue;
}
}
}

View File

@@ -17,7 +17,11 @@
package com.android.documentsui.model;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
@@ -33,38 +37,28 @@ import java.util.Comparator;
* Representation of a {@link Document}.
*/
public class DocumentInfo {
public final Uri uri;
public final String mimeType;
public final String displayName;
public final long lastModified;
public final int flags;
public final String summary;
public final long size;
private DocumentInfo(Uri uri, String mimeType, String displayName, long lastModified, int flags,
String summary, long size) {
this.uri = uri;
this.mimeType = mimeType;
this.displayName = displayName;
this.lastModified = lastModified;
this.flags = flags;
this.summary = summary;
this.size = size;
}
public Uri uri;
public String mimeType;
public String displayName;
public long lastModified;
public int flags;
public String summary;
public long size;
public int icon;
public static DocumentInfo fromDirectoryCursor(Uri parent, Cursor cursor) {
final DocumentInfo doc = new DocumentInfo();
final String authority = parent.getAuthority();
final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
final Uri uri = DocumentsContract.buildDocumentUri(authority, docId);
final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
final long size = getCursorLong(cursor, Document.COLUMN_SIZE);
return new DocumentInfo(uri, mimeType, displayName, lastModified, flags, summary, size);
doc.uri = DocumentsContract.buildDocumentUri(authority, docId);
doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
doc.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
doc.flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
doc.size = getCursorLong(cursor, Document.COLUMN_SIZE);
doc.icon = getCursorInt(cursor, Document.COLUMN_ICON);
return doc;
}
@Deprecated
@@ -79,14 +73,18 @@ public class DocumentInfo {
if (!cursor.moveToFirst()) {
throw new FileNotFoundException("Missing details for " + uri);
}
final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS)
& Document.FLAG_SUPPORTS_THUMBNAIL;
final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
final long size = getCursorLong(cursor, Document.COLUMN_SIZE);
return new DocumentInfo(uri, mimeType, displayName, lastModified, flags, summary, size);
final DocumentInfo doc = new DocumentInfo();
doc.uri = uri;
doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
doc.lastModified = lastModified;
doc.flags = getCursorInt(cursor, Document.COLUMN_FLAGS)
& Document.FLAG_SUPPORTS_THUMBNAIL;
doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
doc.size = getCursorLong(cursor, Document.COLUMN_SIZE);
doc.icon = getCursorInt(cursor, Document.COLUMN_ICON);
return doc;
} catch (Throwable t) {
throw asFileNotFoundException(t);
} finally {
@@ -101,14 +99,16 @@ public class DocumentInfo {
if (!cursor.moveToFirst()) {
throw new FileNotFoundException("Missing details for " + uri);
}
final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
final long size = getCursorLong(cursor, Document.COLUMN_SIZE);
return new DocumentInfo(uri, mimeType, displayName, lastModified, flags, summary, size);
final DocumentInfo doc = new DocumentInfo();
doc.uri = uri;
doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
doc.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
doc.flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
doc.size = getCursorLong(cursor, Document.COLUMN_SIZE);
doc.icon = getCursorInt(cursor, Document.COLUMN_ICON);
return doc;
} catch (Throwable t) {
throw asFileNotFoundException(t);
} finally {
@@ -145,6 +145,25 @@ public class DocumentInfo {
return (flags & Document.FLAG_SUPPORTS_DELETE) != 0;
}
public Drawable loadIcon(Context context) {
return loadIcon(context, uri.getAuthority(), icon);
}
public static Drawable loadIcon(Context context, String authority, int icon) {
if (icon != 0) {
if (authority != null) {
final PackageManager pm = context.getPackageManager();
final ProviderInfo info = pm.resolveContentProvider(authority, 0);
if (info != null) {
return pm.getDrawable(info.packageName, icon, info.applicationInfo);
}
} else {
return context.getResources().getDrawable(icon);
}
}
return null;
}
public static String getCursorString(Cursor cursor, String columnName) {
final int index = cursor.getColumnIndex(columnName);
return (index != -1) ? cursor.getString(index) : null;
@@ -170,6 +189,7 @@ public class DocumentInfo {
return (index != -1) ? cursor.getInt(index) : 0;
}
@Deprecated
public static class DisplayNameComparator implements Comparator<DocumentInfo> {
@Override
public int compare(DocumentInfo lhs, DocumentInfo rhs) {
@@ -184,6 +204,7 @@ public class DocumentInfo {
}
}
@Deprecated
public static class LastModifiedComparator implements Comparator<DocumentInfo> {
@Override
public int compare(DocumentInfo lhs, DocumentInfo rhs) {
@@ -191,6 +212,7 @@ public class DocumentInfo {
}
}
@Deprecated
public static class SizeComparator implements Comparator<DocumentInfo> {
@Override
public int compare(DocumentInfo lhs, DocumentInfo rhs) {

View File

@@ -21,8 +21,6 @@ import static com.android.documentsui.model.DocumentInfo.getCursorLong;
import static com.android.documentsui.model.DocumentInfo.getCursorString;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.provider.DocumentsContract.Root;
@@ -56,17 +54,6 @@ public class RootInfo {
}
public Drawable loadIcon(Context context) {
if (icon != 0) {
if (authority != null) {
final PackageManager pm = context.getPackageManager();
final ProviderInfo info = pm.resolveContentProvider(authority, 0);
if (info != null) {
return pm.getDrawable(info.packageName, icon, info.applicationInfo);
}
} else {
return context.getResources().getDrawable(icon);
}
}
return null;
return DocumentInfo.loadIcon(context, authority, icon);
}
}