I'm writing a wrapper for SAF wrapper for Dropbox since everyone (including Google) is too lazy to implement this "very rich" (ie: awful) API. I've got my root in the picker, but I thought queryChildren
should be called first. However, queryChildren is never called and it goes straight to
queryDocument`.
override fun queryRoots(projection: Array<out String>?): Cursor {
// TODO: Likely need to be more strict about projection (ie: map to supported)
val result = MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION)
val row = result.newRow()
row.add(DocumentsContract.Root.COLUMN_ROOT_ID, "com.anthonymandra.cloudprovider.dropbox")
row.add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_dropbox_gray)
row.add(DocumentsContract.Root.COLUMN_TITLE, "Dropbox")
row.add(DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_SUPPORTS_CREATE) // TODO:
row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, ROOT_DOCUMENT_ID)
return result
}
override fun queryChildDocuments(
parentDocumentId: String?,
projection: Array<out String>?,
sortOrder: String?
): Cursor {
// TODO: Likely need to be more strict about projection (ie: map to supported)
val result = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
val dropboxPath = if (parentDocumentId == ROOT_DOCUMENT_ID) "" else parentDocumentId
try {
val client = DropboxClientFactory.client
var childFolders = client.files().listFolder(dropboxPath)
while (true) {
for (metadata in childFolders.entries) {
addDocumentRow(result, metadata)
}
if (!childFolders.hasMore) {
break
}
childFolders = client.files().listFolderContinue(childFolders.cursor)
}
} catch(e: IllegalStateException) { // Test if we can attempt auth thru the provider
context?.let {
Auth.startOAuth2Authentication(it, appKey) // TODO: appKey
}
}
return result
}
override fun queryDocument(documentId: String?, projection: Array<out String>?): Cursor {
// TODO: Likely need to be more strict about projection (ie: map to supported)
val result = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
try {
val client = DropboxClientFactory.client
val metadata = client.files().getMetadata(documentId)
addDocumentRow(result, metadata)
} catch(e: IllegalStateException) { // Test if we can attempt auth thru the provider
context?.let {
Auth.startOAuth2Authentication(it, appKey) // TODO: appKey
}
}
return result
}
Error:
java.lang.IllegalArgumentException: String 'path' does not match pattern
at com.dropbox.core.v2.files.GetMetadataArg.<init>(GetMetadataArg.java:58)
at com.dropbox.core.v2.files.GetMetadataArg.<init>(GetMetadataArg.java:80)
at com.dropbox.core.v2.files.DbxUserFilesRequests.getMetadata(DbxUserFilesRequests.java:1285)
at com.anthonymandra.cloudprovider.dropbox.DropboxProvider.queryDocument(DropboxProvider.kt:98)
at android.provider.DocumentsProvider.query(DocumentsProvider.java:797)
at android.content.ContentProvider$Transport.query(ContentProvider.java:240)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:102)
at android.os.Binder.execTransact(Binder.java:731)
path
is ROOT_DOCUMENT_ID
which I'm expecting to go to queryChildDocuments
first.
What am I missing here?
The documentation for implementing a DocumentsProvider
is... limited. In particular, there is no documented guarantee of the order of calls. As such, a DocumentsProvider
really should be implemented to make as few assumptions as possible about the order of those calls.
For example, I would not assume that queryRoots()
is called first. It probably will be first, if the first use of the DocumentsProvider
for this process happens to be the Storage Access Framework UI. However, given that clients can (with care) persist a document or document tree Uri
, you might wind up being called with something else first in your process, if the first thing happens to be a client using a persisted Uri
.
And, in your specific case, I would not assume that queryChildDocuments()
occurs before or after queryDocument()
.