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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user