Request more documents when EXTRA_HAS_MORE.
Implement EXTRA_HAS_MORE and EXTRA_REQUEST_MORE contract with document providers. Providers can include EXTRA_HAS_MORE when additional data is available with additional cost, such as a network request. Listen to content changes based on returned cursor instead of original Uri. Include a test backend to exercise. UX still under development. Bug: 10350207 Change-Id: Iaa8954df55a1a1c0aa96eb8a4fd288e12c2fbb01
This commit is contained in:
@@ -42,4 +42,12 @@
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:visibility="gone" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/more"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:text="@string/more"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -60,4 +60,7 @@
|
||||
<string name="toast_no_application">Can\'t open file</string>
|
||||
<string name="toast_failed_delete">Unable to delete some documents</string>
|
||||
|
||||
<string name="more">More</string>
|
||||
<string name="loading">Loading\u2026</string>
|
||||
|
||||
</resources>
|
||||
|
||||
19
packages/DocumentsUI/res/xml/document_provider.xml
Normal file
19
packages/DocumentsUI/res/xml/document_provider.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<documents-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:customRoots="true">
|
||||
</documents-provider>
|
||||
@@ -32,6 +32,7 @@ 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;
|
||||
@@ -54,6 +55,7 @@ import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.GridView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
@@ -79,6 +81,7 @@ public class DirectoryFragment extends Fragment {
|
||||
private View mEmptyView;
|
||||
private ListView mListView;
|
||||
private GridView mGridView;
|
||||
private Button mMoreView;
|
||||
|
||||
private AbsListView mCurrentView;
|
||||
|
||||
@@ -93,7 +96,7 @@ public class DirectoryFragment extends Fragment {
|
||||
private Point mThumbSize;
|
||||
|
||||
private DocumentsAdapter mAdapter;
|
||||
private LoaderCallbacks<List<Document>> mCallbacks;
|
||||
private LoaderCallbacks<DirectoryResult> mCallbacks;
|
||||
|
||||
private static final String EXTRA_TYPE = "type";
|
||||
private static final String EXTRA_URI = "uri";
|
||||
@@ -150,14 +153,16 @@ public class DirectoryFragment extends Fragment {
|
||||
mGridView.setOnItemClickListener(mItemListener);
|
||||
mGridView.setMultiChoiceModeListener(mMultiListener);
|
||||
|
||||
mMoreView = (Button) view.findViewById(R.id.more);
|
||||
|
||||
mAdapter = new DocumentsAdapter();
|
||||
|
||||
final Uri uri = getArguments().getParcelable(EXTRA_URI);
|
||||
mType = getArguments().getInt(EXTRA_TYPE);
|
||||
|
||||
mCallbacks = new LoaderCallbacks<List<Document>>() {
|
||||
mCallbacks = new LoaderCallbacks<DirectoryResult>() {
|
||||
@Override
|
||||
public Loader<List<Document>> onCreateLoader(int id, Bundle args) {
|
||||
public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
|
||||
final DisplayState state = getDisplayState(DirectoryFragment.this);
|
||||
mFilter = new MimePredicate(state.acceptMimes);
|
||||
|
||||
@@ -189,12 +194,34 @@ public class DirectoryFragment extends Fragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<List<Document>> loader, List<Document> data) {
|
||||
mAdapter.swapDocuments(data);
|
||||
public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
|
||||
mAdapter.swapDocuments(result.contents);
|
||||
|
||||
final Cursor cursor = result.cursor;
|
||||
if (cursor != null && cursor.getExtras()
|
||||
.getBoolean(DocumentsContract.EXTRA_HAS_MORE, false)) {
|
||||
mMoreView.setText(R.string.more);
|
||||
mMoreView.setVisibility(View.VISIBLE);
|
||||
mMoreView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mMoreView.setText(R.string.loading);
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putBoolean(DocumentsContract.EXTRA_REQUEST_MORE, true);
|
||||
try {
|
||||
cursor.respond(bundle);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to respond: " + e);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mMoreView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<List<Document>> loader) {
|
||||
public void onLoaderReset(Loader<DirectoryResult> loader) {
|
||||
mAdapter.swapDocuments(null);
|
||||
}
|
||||
};
|
||||
@@ -407,7 +434,7 @@ public class DirectoryFragment extends Fragment {
|
||||
public void swapDocuments(List<Document> documents) {
|
||||
mDocuments = documents;
|
||||
|
||||
if (documents != null && documents.isEmpty()) {
|
||||
if (mDocuments != null && mDocuments.isEmpty()) {
|
||||
mEmptyView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mEmptyView.setVisibility(View.GONE);
|
||||
|
||||
@@ -36,29 +36,27 @@ import com.google.android.collect.Lists;
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class DirectoryLoader extends UriDerivativeLoader<List<Document>> {
|
||||
class DirectoryResult implements AutoCloseable {
|
||||
Cursor cursor;
|
||||
List<Document> contents = Lists.newArrayList();
|
||||
Exception e;
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
IoUtils.closeQuietly(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {
|
||||
|
||||
private final int mType;
|
||||
private Predicate<Document> mFilter;
|
||||
private Comparator<Document> mSortOrder;
|
||||
|
||||
/**
|
||||
* Stub result that represents an internal error.
|
||||
*/
|
||||
public static class ExceptionResult extends LinkedList<Document> {
|
||||
public final Exception e;
|
||||
|
||||
public ExceptionResult(Exception e) {
|
||||
this.e = e;
|
||||
}
|
||||
}
|
||||
|
||||
public DirectoryLoader(Context context, Uri uri, int type, Predicate<Document> filter,
|
||||
Comparator<Document> sortOrder) {
|
||||
super(context, uri);
|
||||
@@ -68,53 +66,49 @@ public class DirectoryLoader extends UriDerivativeLoader<List<Document>> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Document> loadInBackground(Uri uri, CancellationSignal signal) {
|
||||
public DirectoryResult loadInBackground(Uri uri, CancellationSignal signal) {
|
||||
final DirectoryResult result = new DirectoryResult();
|
||||
try {
|
||||
return loadInBackgroundInternal(uri, signal);
|
||||
loadInBackgroundInternal(result, uri, signal);
|
||||
} catch (Exception e) {
|
||||
return new ExceptionResult(e);
|
||||
result.e = e;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Document> loadInBackgroundInternal(Uri uri, CancellationSignal signal) {
|
||||
final ArrayList<Document> result = Lists.newArrayList();
|
||||
|
||||
// TODO: subscribe to the notify uri from query
|
||||
|
||||
private void loadInBackgroundInternal(
|
||||
DirectoryResult result, Uri uri, CancellationSignal signal) {
|
||||
final ContentResolver resolver = getContext().getContentResolver();
|
||||
final Cursor cursor = resolver.query(uri, null, null, null, getQuerySortOrder(), signal);
|
||||
try {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
Document doc = null;
|
||||
switch (mType) {
|
||||
case TYPE_NORMAL:
|
||||
case TYPE_SEARCH:
|
||||
doc = Document.fromDirectoryCursor(uri, cursor);
|
||||
break;
|
||||
case TYPE_RECENT_OPEN:
|
||||
try {
|
||||
doc = Document.fromRecentOpenCursor(resolver, cursor);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.w(TAG, "Failed to find recent: " + e);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown type");
|
||||
}
|
||||
result.cursor = cursor;
|
||||
result.cursor.registerContentObserver(mObserver);
|
||||
|
||||
if (doc != null && (mFilter == null || mFilter.apply(doc))) {
|
||||
result.add(doc);
|
||||
}
|
||||
while (cursor.moveToNext()) {
|
||||
Document doc = null;
|
||||
switch (mType) {
|
||||
case TYPE_NORMAL:
|
||||
case TYPE_SEARCH:
|
||||
doc = Document.fromDirectoryCursor(uri, cursor);
|
||||
break;
|
||||
case TYPE_RECENT_OPEN:
|
||||
try {
|
||||
doc = Document.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);
|
||||
}
|
||||
} finally {
|
||||
IoUtils.closeQuietly(cursor);
|
||||
}
|
||||
|
||||
if (mSortOrder != null) {
|
||||
Collections.sort(result, mSortOrder);
|
||||
Collections.sort(result.contents, mSortOrder);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getQuerySortOrder() {
|
||||
|
||||
@@ -124,7 +124,7 @@ public class RecentsCreateFragment extends Fragment {
|
||||
}
|
||||
};
|
||||
|
||||
public static class RecentsCreateLoader extends UriDerivativeLoader<List<DocumentStack>> {
|
||||
public static class RecentsCreateLoader extends UriDerivativeLoader<Uri, List<DocumentStack>> {
|
||||
public RecentsCreateLoader(Context context) {
|
||||
super(context, RecentsProvider.buildRecentCreate());
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.android.documentsui;
|
||||
import android.content.AsyncTaskLoader;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.OperationCanceledException;
|
||||
|
||||
@@ -28,17 +27,16 @@ import android.os.OperationCanceledException;
|
||||
* changes while started, manages {@link CancellationSignal}, and caches
|
||||
* returned results.
|
||||
*/
|
||||
public abstract class UriDerivativeLoader<T> extends AsyncTaskLoader<T> {
|
||||
private final ForceLoadContentObserver mObserver;
|
||||
private boolean mObserving;
|
||||
public abstract class UriDerivativeLoader<P, R> extends AsyncTaskLoader<R> {
|
||||
final ForceLoadContentObserver mObserver;
|
||||
|
||||
private final Uri mUri;
|
||||
private final P mParam;
|
||||
|
||||
private T mResult;
|
||||
private R mResult;
|
||||
private CancellationSignal mCancellationSignal;
|
||||
|
||||
@Override
|
||||
public final T loadInBackground() {
|
||||
public final R loadInBackground() {
|
||||
synchronized (this) {
|
||||
if (isLoadInBackgroundCanceled()) {
|
||||
throw new OperationCanceledException();
|
||||
@@ -46,7 +44,7 @@ public abstract class UriDerivativeLoader<T> extends AsyncTaskLoader<T> {
|
||||
mCancellationSignal = new CancellationSignal();
|
||||
}
|
||||
try {
|
||||
return loadInBackground(mUri, mCancellationSignal);
|
||||
return loadInBackground(mParam, mCancellationSignal);
|
||||
} finally {
|
||||
synchronized (this) {
|
||||
mCancellationSignal = null;
|
||||
@@ -54,7 +52,7 @@ public abstract class UriDerivativeLoader<T> extends AsyncTaskLoader<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public abstract T loadInBackground(Uri uri, CancellationSignal signal);
|
||||
public abstract R loadInBackground(P param, CancellationSignal signal);
|
||||
|
||||
@Override
|
||||
public void cancelLoadInBackground() {
|
||||
@@ -68,12 +66,12 @@ public abstract class UriDerivativeLoader<T> extends AsyncTaskLoader<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(T result) {
|
||||
public void deliverResult(R result) {
|
||||
if (isReset()) {
|
||||
closeQuietly(result);
|
||||
return;
|
||||
}
|
||||
T oldResult = mResult;
|
||||
R oldResult = mResult;
|
||||
mResult = result;
|
||||
|
||||
if (isStarted()) {
|
||||
@@ -85,18 +83,14 @@ public abstract class UriDerivativeLoader<T> extends AsyncTaskLoader<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public UriDerivativeLoader(Context context, Uri uri) {
|
||||
public UriDerivativeLoader(Context context, P param) {
|
||||
super(context);
|
||||
mObserver = new ForceLoadContentObserver();
|
||||
mUri = uri;
|
||||
mParam = param;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (!mObserving) {
|
||||
getContext().getContentResolver().registerContentObserver(mUri, false, mObserver);
|
||||
mObserving = true;
|
||||
}
|
||||
if (mResult != null) {
|
||||
deliverResult(mResult);
|
||||
}
|
||||
@@ -111,7 +105,7 @@ public abstract class UriDerivativeLoader<T> extends AsyncTaskLoader<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(T result) {
|
||||
public void onCanceled(R result) {
|
||||
closeQuietly(result);
|
||||
}
|
||||
|
||||
@@ -125,13 +119,10 @@ public abstract class UriDerivativeLoader<T> extends AsyncTaskLoader<T> {
|
||||
closeQuietly(mResult);
|
||||
mResult = null;
|
||||
|
||||
if (mObserving) {
|
||||
getContext().getContentResolver().unregisterContentObserver(mObserver);
|
||||
mObserving = false;
|
||||
}
|
||||
getContext().getContentResolver().unregisterContentObserver(mObserver);
|
||||
}
|
||||
|
||||
private void closeQuietly(T result) {
|
||||
private void closeQuietly(R result) {
|
||||
if (result instanceof AutoCloseable) {
|
||||
try {
|
||||
((AutoCloseable) result).close();
|
||||
|
||||
@@ -15,5 +15,18 @@
|
||||
android:name="android.content.DOCUMENT_PROVIDER"
|
||||
android:resource="@xml/document_provider" />
|
||||
</provider>
|
||||
|
||||
<!-- TODO: remove when we have real providers -->
|
||||
<provider
|
||||
android:name=".CloudTestDocumentsProvider"
|
||||
android:authorities="com.android.externalstorage.cloudtest"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="true"
|
||||
android:enabled="false"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||
<meta-data
|
||||
android:name="android.content.DOCUMENT_PROVIDER"
|
||||
android:resource="@xml/document_provider" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* 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.externalstorage;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MatrixCursor.RowBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.DocumentColumns;
|
||||
import android.provider.DocumentsContract.Documents;
|
||||
import android.provider.DocumentsContract.RootColumns;
|
||||
import android.provider.DocumentsContract.Roots;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.collect.Lists;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.List;
|
||||
|
||||
public class CloudTestDocumentsProvider extends ContentProvider {
|
||||
private static final String TAG = "CloudTest";
|
||||
|
||||
private static final String AUTHORITY = "com.android.externalstorage.cloudtest";
|
||||
|
||||
private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
|
||||
private static final int URI_ROOTS = 1;
|
||||
private static final int URI_ROOTS_ID = 2;
|
||||
private static final int URI_DOCS_ID = 3;
|
||||
private static final int URI_DOCS_ID_CONTENTS = 4;
|
||||
private static final int URI_DOCS_ID_SEARCH = 5;
|
||||
|
||||
static {
|
||||
sMatcher.addURI(AUTHORITY, "roots", URI_ROOTS);
|
||||
sMatcher.addURI(AUTHORITY, "roots/*", URI_ROOTS_ID);
|
||||
sMatcher.addURI(AUTHORITY, "roots/*/docs/*", URI_DOCS_ID);
|
||||
sMatcher.addURI(AUTHORITY, "roots/*/docs/*/contents", URI_DOCS_ID_CONTENTS);
|
||||
sMatcher.addURI(AUTHORITY, "roots/*/docs/*/search", URI_DOCS_ID_SEARCH);
|
||||
}
|
||||
|
||||
private static final String[] ALL_ROOTS_COLUMNS = new String[] {
|
||||
RootColumns.ROOT_ID, RootColumns.ROOT_TYPE, RootColumns.ICON, RootColumns.TITLE,
|
||||
RootColumns.SUMMARY, RootColumns.AVAILABLE_BYTES
|
||||
};
|
||||
|
||||
private static final String[] ALL_DOCUMENTS_COLUMNS = new String[] {
|
||||
DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE,
|
||||
DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS
|
||||
};
|
||||
|
||||
private List<String> mKnownDocs = Lists.newArrayList("meow.png", "kittens.pdf");
|
||||
|
||||
private int mPage;
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
switch (sMatcher.match(uri)) {
|
||||
case URI_ROOTS: {
|
||||
final MatrixCursor result = new MatrixCursor(
|
||||
projection != null ? projection : ALL_ROOTS_COLUMNS);
|
||||
includeDefaultRoot(result);
|
||||
return result;
|
||||
}
|
||||
case URI_ROOTS_ID: {
|
||||
final MatrixCursor result = new MatrixCursor(
|
||||
projection != null ? projection : ALL_ROOTS_COLUMNS);
|
||||
includeDefaultRoot(result);
|
||||
return result;
|
||||
}
|
||||
case URI_DOCS_ID: {
|
||||
final String docId = DocumentsContract.getDocId(uri);
|
||||
final MatrixCursor result = new MatrixCursor(
|
||||
projection != null ? projection : ALL_DOCUMENTS_COLUMNS);
|
||||
includeDoc(result, docId);
|
||||
return result;
|
||||
}
|
||||
case URI_DOCS_ID_CONTENTS: {
|
||||
final CloudCursor result = new CloudCursor(
|
||||
projection != null ? projection : ALL_DOCUMENTS_COLUMNS, uri);
|
||||
for (String docId : mKnownDocs) {
|
||||
includeDoc(result, docId);
|
||||
}
|
||||
if (mPage < 3) {
|
||||
result.setHasMore();
|
||||
}
|
||||
result.setNotificationUri(getContext().getContentResolver(), uri);
|
||||
return result;
|
||||
}
|
||||
default: {
|
||||
throw new UnsupportedOperationException("Unsupported Uri " + uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void includeDefaultRoot(MatrixCursor result) {
|
||||
final RowBuilder row = result.newRow();
|
||||
row.offer(RootColumns.ROOT_ID, "testroot");
|
||||
row.offer(RootColumns.ROOT_TYPE, Roots.ROOT_TYPE_SERVICE);
|
||||
row.offer(RootColumns.TITLE, "_TestTitle");
|
||||
row.offer(RootColumns.SUMMARY, "_TestSummary");
|
||||
}
|
||||
|
||||
private void includeDoc(MatrixCursor result, String docId) {
|
||||
int flags = 0;
|
||||
|
||||
final String mimeType;
|
||||
if (Documents.DOC_ID_ROOT.equals(docId)) {
|
||||
mimeType = Documents.MIME_TYPE_DIR;
|
||||
} else {
|
||||
mimeType = "application/octet-stream";
|
||||
}
|
||||
|
||||
final RowBuilder row = result.newRow();
|
||||
row.offer(DocumentColumns.DOC_ID, docId);
|
||||
row.offer(DocumentColumns.DISPLAY_NAME, docId);
|
||||
row.offer(DocumentColumns.MIME_TYPE, mimeType);
|
||||
row.offer(DocumentColumns.LAST_MODIFIED, System.currentTimeMillis());
|
||||
row.offer(DocumentColumns.FLAGS, flags);
|
||||
}
|
||||
|
||||
private class CloudCursor extends MatrixCursor {
|
||||
private final Uri mUri;
|
||||
private Bundle mExtras = new Bundle();
|
||||
|
||||
public CloudCursor(String[] columnNames, Uri uri) {
|
||||
super(columnNames);
|
||||
mUri = uri;
|
||||
}
|
||||
|
||||
public void setHasMore() {
|
||||
mExtras.putBoolean(DocumentsContract.EXTRA_HAS_MORE, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getExtras() {
|
||||
Log.d(TAG, "getExtras() " + mExtras);
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle respond(Bundle extras) {
|
||||
extras.size();
|
||||
Log.d(TAG, "respond() " + extras);
|
||||
if (extras.getBoolean(DocumentsContract.EXTRA_REQUEST_MORE, false)) {
|
||||
new CloudTask().execute(mUri);
|
||||
}
|
||||
return Bundle.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
private class CloudTask extends AsyncTask<Uri, Void, Void> {
|
||||
@Override
|
||||
protected Void doInBackground(Uri... uris) {
|
||||
final Uri uri = uris[0];
|
||||
|
||||
SystemClock.sleep(1000);
|
||||
|
||||
// Grab some files from the cloud
|
||||
for (int i = 0; i < 5; i++) {
|
||||
mKnownDocs.add("cloud-page" + mPage + "-file" + i);
|
||||
}
|
||||
mPage++;
|
||||
|
||||
Log.d(TAG, "Loaded more; notifying " + uri);
|
||||
getContext().getContentResolver().notifyChange(uri, null, false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private interface TypeQuery {
|
||||
final String[] PROJECTION = {
|
||||
DocumentColumns.MIME_TYPE };
|
||||
|
||||
final int MIME_TYPE = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
switch (sMatcher.match(uri)) {
|
||||
case URI_ROOTS: {
|
||||
return Roots.MIME_TYPE_DIR;
|
||||
}
|
||||
case URI_ROOTS_ID: {
|
||||
return Roots.MIME_TYPE_ITEM;
|
||||
}
|
||||
case URI_DOCS_ID: {
|
||||
final Cursor cursor = query(uri, TypeQuery.PROJECTION, null, null, null);
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
return cursor.getString(TypeQuery.MIME_TYPE);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
IoUtils.closeQuietly(cursor);
|
||||
}
|
||||
}
|
||||
default: {
|
||||
throw new UnsupportedOperationException("Unsupported Uri " + uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
throw new UnsupportedOperationException("Unsupported Uri " + uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
throw new UnsupportedOperationException("Unsupported Uri " + uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException("Unsupported Uri " + uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException("Unsupported Uri " + uri);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user