package org.meshbay.client.drive import android.content.Context import android.net.Uri import android.provider.DocumentsContract import android.provider.DocumentsContract.Document import java.io.InputStream /** * The files of the folders the person chose, read through the Storage Access * Framework: each folder was granted through the system's own picker, which * is all the access this needs (no storage permission, which Play keeps for a * few kinds of application). */ class DriveSource(private val context: Context) { /** Every file under `tree`, its sub-folders included; hidden ones (`.x`) and their contents skipped. */ fun files(tree: Uri): List { val out = ArrayList() val root = DocumentsContract.getTreeDocumentId(tree) val pending = ArrayDeque(listOf(root to "")) val cols = arrayOf(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_MIME_TYPE, Document.COLUMN_SIZE, Document.COLUMN_LAST_MODIFIED) while (pending.isNotEmpty()) { val (parent, sub) = pending.removeFirst() val children = DocumentsContract.buildChildDocumentsUriUsingTree(tree, parent) context.contentResolver.query(children, cols, null, null, null)?.use { c -> while (c.moveToNext()) { val id = c.getString(0) ?: continue val name = c.getString(1) ?: continue if (name.startsWith('.')) continue val mime = c.getString(2) ?: "" if (mime == Document.MIME_TYPE_DIR) { pending.addLast(id to (if (sub.isEmpty()) name else "$sub/$name")) } else { out += DriveFile(tree.toString(), id, sub, name, if (c.isNull(3)) 0 else c.getLong(3), if (c.isNull(4)) 0 else c.getLong(4), mime) } } } } return out } fun open(f: DriveFile): InputStream? = context.contentResolver.openInputStream( DocumentsContract.buildDocumentUriUsingTree(Uri.parse(f.tree), f.docId)) /** The chosen folder's own name, as the picker showed it. */ fun nameOf(tree: Uri): String? = try { val doc = DocumentsContract.buildDocumentUriUsingTree(tree, DocumentsContract.getTreeDocumentId(tree)) context.contentResolver.query(doc, arrayOf(Document.COLUMN_DISPLAY_NAME), null, null, null) ?.use { c -> if (c.moveToFirst()) c.getString(0) else null } } catch (e: Exception) { null } }