I am trying to implement DocumentsProvider as a demo for my book. While the provider appears in the consumer sample application, as soon as I click on it in the Access Access Framework user interface, I get the following stack trace:
09-15 18:40:46.290 1765-1829/com.android.documentsui E/AndroidRuntime๏น FATAL EXCEPTION: ProviderExecutor: com.commonsware.android.documents.provider Process: com.android.documentsui, PID: 1765 java.lang.RuntimeException: An error occured while executing doInBackground() at android.os.AsyncTask$3.done(AsyncTask.java:300) at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355) at java.util.concurrent.FutureTask.setException(FutureTask.java:222) at java.util.concurrent.FutureTask.run(FutureTask.java:242) at com.android.documentsui.ProviderExecutor.run(ProviderExecutor.java:107) Caused by: java.lang.UnsupportedOperationException: Unsupported Uri content://com.android.documentsui.recents/state/com.commonsware.android.documents.provider/thisIsMyRoot/ at com.android.documentsui.RecentsProvider.query(RecentsProvider.java:192) at android.content.ContentProvider.query(ContentProvider.java:857) at android.content.ContentProvider$Transport.query(ContentProvider.java:200) at android.content.ContentResolver.query(ContentResolver.java:461) at android.content.ContentResolver.query(ContentResolver.java:404) at com.android.documentsui.DirectoryLoader.loadInBackground(DirectoryLoader.java:124) at com.android.documentsui.DirectoryLoader.loadInBackground(DirectoryLoader.java:65) at android.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:312) at android.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:69) at android.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:57) at android.os.AsyncTask$2.call(AsyncTask.java:288) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at com.android.documentsui.ProviderExecutor.run(ProviderExecutor.java:107)
content://com.commonsware.android.documents.provider/thisIsMyRoot/ is supposedly Uri generated for my document root, based on the implementation of queryRoots() . But I have no idea what content://com.android.documentsui.recents/state/com.commonsware.android.documents.provider/thisIsMyRoot/ or what should I do to prevent this error.
Here is the implementation of DocumentsProvider designed to serve files from assets/ :
package com.commonsware.android.documents.provider; import android.content.res.AssetManager; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsProvider; import android.util.Log; import android.webkit.MimeTypeMap; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; public class DemoDocumentProvider extends DocumentsProvider { private static final String[] SUPPORTED_ROOT_PROJECTION=new String[] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_ICON }; private static final String[] SUPPORTED_DOCUMENT_PROJECTION= new String[] { Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_FLAGS}; private static final String ROOT_ID="thisIsMyRoot"; private static final String ROOT_DOCUMENT_ID="thisCannotBeEmpty"; private AssetManager assets; @Override public boolean onCreate() { assets=getContext().getAssets(); return(true); } @Override public Cursor queryRoots(String[] projection) throws FileNotFoundException { String[] netProjection= netProjection(projection, SUPPORTED_ROOT_PROJECTION); MatrixCursor result=new MatrixCursor(netProjection); MatrixCursor.RowBuilder row=result.newRow(); row.add(Root.COLUMN_ROOT_ID, ROOT_ID); row.add(Root.COLUMN_ICON, R.drawable.ic_launcher); row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY); row.add(Root.COLUMN_TITLE, getContext().getString(R.string.root)); row.add(Root.COLUMN_DOCUMENT_ID, ROOT_DOCUMENT_ID); return(result); } @Override public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException { String[] netProjection= netProjection(projection, SUPPORTED_DOCUMENT_PROJECTION); MatrixCursor result=new MatrixCursor(netProjection); parentDocumentId=fixUpDocumentId(parentDocumentId); try { String[] children=assets.list(parentDocumentId); for (String child : children) { addDocumentRow(result, child, parentDocumentId+child); } } catch (IOException e) { Log.e(getClass().getSimpleName(), "Exception reading asset dir", e); } return(result); } @Override public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException { String[] netProjection= netProjection(projection, SUPPORTED_DOCUMENT_PROJECTION); MatrixCursor result=new MatrixCursor(netProjection); documentId=fixUpDocumentId(documentId); try { addDocumentRow(result, Uri.parse(documentId).getLastPathSegment(), documentId); } catch (IOException e) { Log.e(getClass().getSimpleName(), "Exception reading asset dir", e); } return(result); } @Override public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal) throws FileNotFoundException { ParcelFileDescriptor[] pipe=null; try { pipe=ParcelFileDescriptor.createPipe(); AssetManager assets=getContext().getResources().getAssets(); new TransferThread(assets.open(documentId), new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1])).start(); } catch (IOException e) { Log.e(getClass().getSimpleName(), "Exception opening pipe", e); throw new FileNotFoundException("Could not open pipe for: " + documentId); } return(pipe[0]); } private void addDocumentRow(MatrixCursor result, String child, String assetPath) throws IOException { MatrixCursor.RowBuilder row=result.newRow(); row.add(Document.COLUMN_DOCUMENT_ID, assetPath); if (isDirectory(assetPath)) { row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); } else { row.add(Document.COLUMN_MIME_TYPE, MimeTypeMap.getFileExtensionFromUrl(assetPath)); row.add(Document.COLUMN_SIZE, lastModified(assetPath)); } row.add(Document.COLUMN_DISPLAY_NAME, child); row.add(Document.COLUMN_FLAGS, 0); } private boolean isDirectory(String assetPath) throws IOException { return(assets.list(assetPath).length>1); } private long lastModified(String assetPath) throws IOException { return(assets.openFd(assetPath).getLength()); } private String fixUpDocumentId(String documentId) { if (ROOT_DOCUMENT_ID.equals(documentId)) { return(""); } return(documentId); } private static String[] netProjection(String[] requested, String[] supported) { if (requested==null) { return(supported); } ArrayList<String> result=new ArrayList<String>(); for (String request : requested) { for (String support : supported) { if (request.equals(support)) { result.add(request); break; } } } return(result.toArray(new String[0])); } static class TransferThread extends Thread { InputStream in; OutputStream out; TransferThread(InputStream in, OutputStream out) { this.in=in; this.out=out; } @Override public void run() { byte[] buf=new byte[1024]; int len; try { while ((len=in.read(buf)) >= 0) { out.write(buf, 0, len); } in.close(); out.flush(); out.close(); } catch (IOException e) { Log.e(getClass().getSimpleName(), "Exception transferring file", e); } } } }
So my question is: where am I going wrong?