diff options
| author | Joel Klinghed <the_jk@spawned.biz> | 2024-11-09 17:58:09 +0100 |
|---|---|---|
| committer | Joel Klinghed <the_jk@spawned.biz> | 2024-11-09 17:58:09 +0100 |
| commit | d274ffe5ec33c285034bdae64b532f2a470b9407 (patch) | |
| tree | 9cc54dff13ce7b2bfcb471c66d3d12f55f4ea417 /libs/documents/src/test | |
| parent | f5aa9ba724fa8bfc2aba3532f5427398329b386a (diff) | |
documents: Create DocumentTreeTestHelper
For future tests
Diffstat (limited to 'libs/documents/src/test')
| -rw-r--r-- | libs/documents/src/test/java/org/the_jk/cleversync/documents/DocumentTreeTest.kt | 429 | ||||
| -rw-r--r-- | libs/documents/src/test/java/org/the_jk/cleversync/documents/DocumentTreeTestHelper.kt | 440 |
2 files changed, 443 insertions, 426 deletions
diff --git a/libs/documents/src/test/java/org/the_jk/cleversync/documents/DocumentTreeTest.kt b/libs/documents/src/test/java/org/the_jk/cleversync/documents/DocumentTreeTest.kt index f116f0e..ad6abfa 100644 --- a/libs/documents/src/test/java/org/the_jk/cleversync/documents/DocumentTreeTest.kt +++ b/libs/documents/src/test/java/org/the_jk/cleversync/documents/DocumentTreeTest.kt @@ -1,34 +1,14 @@ package org.the_jk.cleversync.documents -import android.Manifest.permission.MANAGE_DOCUMENTS -import android.content.Context -import android.content.pm.ProviderInfo -import android.database.Cursor -import android.database.MatrixCursor -import android.os.Build -import android.os.CancellationSignal -import android.os.ParcelFileDescriptor -import android.provider.DocumentsContract -import android.provider.DocumentsProvider -import android.webkit.MimeTypeMap -import androidx.test.core.app.ApplicationProvider -import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith -import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner -import org.robolectric.android.controller.ContentProviderController import org.robolectric.annotation.Config import org.robolectric.shadows.ShadowLooper import org.the_jk.cleversync.TreeAbstractTest -import java.io.File -import java.io.FileInputStream -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.IOException import java.util.concurrent.TimeUnit @Config(manifest=Config.NONE) @@ -37,36 +17,16 @@ class DocumentTreeTest : TreeAbstractTest() { @get:Rule val folder = TemporaryFolder() - private lateinit var controller: ContentProviderController<FakeProvider> + private val helper = DocumentTreeTestHelper() @Before fun setUp() { - val context = ApplicationProvider.getApplicationContext<Context>() - val contentResolver = context.contentResolver - val providerInfo = ProviderInfo() - providerInfo.authority = "org.the_jk.cleversync.documents.test" - providerInfo.grantUriPermissions = true - providerInfo.exported = true - providerInfo.readPermission = MANAGE_DOCUMENTS - providerInfo.writePermission = MANAGE_DOCUMENTS - controller = Robolectric - .buildContentProvider(FakeProvider::class.java) - - controller.get().setBaseDir(folder.root) - controller.create(providerInfo) - - val treeUri = DocumentsContract.buildTreeDocumentUri(providerInfo.authority, ROOT) - assertThat(DocumentsContract.isTreeUri(treeUri)).isTrue() - - tree = DocumentTreeFactory.modifiableTree( - contentResolver, - treeUri, - ) + tree = helper.openTree(folder) } @After fun tearDown() { - controller.shutdown() + helper.closeTree(tree) } override fun idle() { @@ -74,387 +34,4 @@ class DocumentTreeTest : TreeAbstractTest() { } override fun supportSymlinks() = false - - private class FakeProvider : DocumentsProvider() { - private lateinit var baseDir: File - - fun setBaseDir(file: File) { - baseDir = file - } - - override fun onCreate(): Boolean { - return true - } - - override fun queryRoots(projection: Array<String>?): Cursor { - val result = MatrixCursor(resolveRootProjection(projection)) - val row = result.newRow() - row.add(DocumentsContract.Root.COLUMN_ROOT_ID, ROOT) - row.add(DocumentsContract.Root.COLUMN_SUMMARY, "") - row.add(DocumentsContract.Root.COLUMN_FLAGS, - DocumentsContract.Root.FLAG_SUPPORTS_CREATE or - DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD) - row.add(DocumentsContract.Root.COLUMN_TITLE, "fake") - row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocIdForFile(baseDir)) - row.add(DocumentsContract.Root.COLUMN_MIME_TYPES, getChildMimeTypes()) - row.add(DocumentsContract.Root.COLUMN_AVAILABLE_BYTES, baseDir.freeSpace) - row.add(DocumentsContract.Root.COLUMN_ICON, null) - return result - } - - override fun queryDocument( - documentId: String?, - projection: Array<String>? - ): Cursor { - val result = MatrixCursor(resolveDocumentProjection(projection)) - includeFile(result, documentId, null) - return result - } - - override fun queryChildDocuments( - parentDocumentId: String, - projection: Array<String>?, - sortOrder: String? - ): Cursor { - val result = MatrixCursor(resolveDocumentProjection(projection)) - val parent = getFileForDocId(parentDocumentId) - parent.listFiles()?.forEach { file -> - includeFile(result, null, file) - } - return result - } - - override fun openDocument( - documentId: String, - mode: String?, - signal: CancellationSignal? - ): ParcelFileDescriptor { - val file = getFileForDocId(documentId) - val accessMode = ParcelFileDescriptor.parseMode(mode) - return ParcelFileDescriptor.open(file, accessMode) - } - - override fun isChildDocument( - parentDocumentId: String, - documentId: String - ): Boolean { - try { - val parentFile = getFileForDocId(parentDocumentId) - var childFile: File? = getFileForDocId(documentId) - // childFile can be any level of descendant of parentFile - // to return true. - do { - if (isChildFile(parentFile, childFile!!)) return true - childFile = childFile.parentFile - } while (childFile != null) - return false - } catch (_: FileNotFoundException) { - } - return false - } - - override fun createDocument( - parentDocumentId: String, - mimeType: String?, - displayName: String, - ): String { - val parent = getFileForDocId(parentDocumentId) - val file = File(parent.path, displayName) - try { - var documentWasCreated = false - if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) { - if (file.mkdir()) { - if (file.setWritable(true) && file.setWritable(true) && file.setExecutable(true)) { - documentWasCreated = true - } - } - } else { - if (file.createNewFile()) { - if (file.setWritable(true) && file.setReadable(true)) { - documentWasCreated = true - } - } - } - - if (!documentWasCreated) { - throw IOException("Failed to create document with name " + - displayName +" and documentId " + parentDocumentId) - } - } catch (e: IOException) { - throw IOException("Failed to create document with name " + - displayName +" and documentId " + parentDocumentId, e) - } - return getDocIdForFile(file) - } - - override fun renameDocument( - documentId: String, - displayName: String? - ): String { - if (displayName == null) { - throw IOException("Failed to rename document, new name is null") - } - - val sourceFile = getFileForDocId(documentId) - val sourceParentFile = sourceFile.parentFile - ?: throw IOException("Failed to rename document. File has no parent.") - val destFile = File(sourceParentFile.path, displayName) - - try { - if (!sourceFile.renameTo(destFile)) { - throw IOException("Failed to rename document. Renamed failed.") - } - } catch (e: Exception) { - throw IOException("Failed to rename document.", e) - } - - return getDocIdForFile(destFile) - } - - override fun deleteDocument(documentId: String) { - val file = getFileForDocId(documentId) - if (!file.delete()) { - throw IOException("Failed to delete document with id $documentId") - } - } - - override fun removeDocument( - documentId: String, - parentDocumentId: String, - ) { - val parent = getFileForDocId(parentDocumentId) - val file = getFileForDocId(documentId) - - val doesFileParentMatch = isChildFile(parent, file) - - if (parent == file || doesFileParentMatch) { - if (!file.delete()) { - throw IOException("Failed to delete document with id $documentId") - } - } else { - throw IOException("Failed to delete document with id $documentId") - } - } - - override fun copyDocument( - sourceDocumentId: String, - targetParentDocumentId: String - ): String { - val parent = getFileForDocId(targetParentDocumentId) - val oldFile = getFileForDocId(sourceDocumentId) - val newFile = File(parent.path, oldFile.name) - - try { - var wasNewFileCreated = false - if (newFile.createNewFile()) { - if (newFile.setWritable(true) && newFile.setReadable(true)) { - wasNewFileCreated = true - } - } - - if (!wasNewFileCreated) { - throw IOException("Failed to copy document " + sourceDocumentId + - ". Could not create new file.") - } - - FileInputStream(oldFile).use { inStream -> - FileOutputStream(newFile).use { outStream -> - // Transfer bytes from in to out - val buf = ByteArray(65536) - var len: Int - while ((inStream.read(buf).also { len = it }) > 0) { - outStream.write(buf, 0, len) - } - } - } - } catch (e: IOException) { - throw IOException("Failed to copy document: $sourceDocumentId", e) - } - return getDocIdForFile(newFile) - } - - override fun moveDocument( - sourceDocumentId: String, - sourceParentDocumentId: String, - targetParentDocumentId: String, - ): String { - try { - // Copy document, insisting that the parent is correct - val newDocumentId = copyDocument(sourceDocumentId, sourceParentDocumentId, - targetParentDocumentId) - // Remove old document - removeDocument(sourceDocumentId,sourceParentDocumentId) - return newDocumentId - } catch (e: IOException) { - throw IOException("Failed to move document $sourceDocumentId", e) - } - } - - override fun getDocumentType(documentId: String): String { - val file = getFileForDocId(documentId) - return getTypeForFile(file) - } - - private fun copyDocument( - sourceDocumentId: String, - sourceParentDocumentId: String, - targetParentDocumentId: String, - ): String { - if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) { - throw IOException( - "Failed to copy document with id " + - sourceDocumentId + ". Parent is not: " + sourceParentDocumentId - ) - } - return copyDocument(sourceDocumentId, targetParentDocumentId) - } - - private fun isChildFile(parentFile: File, childFile: File): Boolean { - return parentFile == childFile.parentFile - } - - private fun getDocIdForFile(file: File): String { - var path = file.absolutePath - - // Start at first char of path under root - val rootPath = baseDir.path - path = if (rootPath.equals(path)) { - "" - } else if (rootPath.endsWith("/")) { - path.substring(rootPath.length) - } else { - path.substring(rootPath.length + 1) - } - - return "$ROOT:$path" - } - - private fun getFileForDocId(docId: String): File { - var target = baseDir - if (docId == ROOT) { - return target - } - val splitIndex = docId.indexOf(':', 1) - if (splitIndex < 0) { - throw FileNotFoundException("Missing root for $docId") - } else { - val path = docId.substring(splitIndex + 1) - target = File(target, path) - if (!target.exists()) { - throw FileNotFoundException("Missing file for $docId at $target") - } - return target - } - } - - private fun includeFile(result: MatrixCursor, inDocId: String?, inFile: File?) { - val (docId, file) = if (inDocId == null) { - getDocIdForFile(inFile!!) to inFile - } else { - inDocId to getFileForDocId(inDocId) - } - - var flags = 0 - - if (file.isDirectory) { - // Add FLAG_DIR_SUPPORTS_CREATE if the file is a writable directory. - if (file.canWrite()) { - flags = flags or DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE - } - } - if (file.canWrite()) { - // If the file is writable set FLAG_SUPPORTS_WRITE and - // FLAG_SUPPORTS_DELETE - flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_WRITE - flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_DELETE - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - flags = - flags or DocumentsContract.Document.FLAG_SUPPORTS_RENAME - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - flags = - flags or DocumentsContract.Document.FLAG_SUPPORTS_REMOVE - flags = - flags or DocumentsContract.Document.FLAG_SUPPORTS_MOVE - flags = - flags or DocumentsContract.Document.FLAG_SUPPORTS_COPY - } - } - - val displayName = file.name - val mimeType = getTypeForFile(file) - - val row = result.newRow() - row.add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, docId) - row.add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, displayName) - row.add(DocumentsContract.Document.COLUMN_SIZE, file.length()) - row.add(DocumentsContract.Document.COLUMN_MIME_TYPE, mimeType) - row.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, file.lastModified()) - row.add(DocumentsContract.Document.COLUMN_FLAGS, flags) - row.add(DocumentsContract.Document.COLUMN_ICON, null) - } - } - - private companion object { - val DEFAULT_ROOT_PROJECTION = arrayOf( - DocumentsContract.Root.COLUMN_ROOT_ID, - DocumentsContract.Root.COLUMN_MIME_TYPES, - DocumentsContract.Root.COLUMN_FLAGS, - DocumentsContract.Root.COLUMN_ICON, - DocumentsContract.Root.COLUMN_TITLE, - DocumentsContract.Root.COLUMN_SUMMARY, - DocumentsContract.Root.COLUMN_DOCUMENT_ID, - DocumentsContract.Root.COLUMN_AVAILABLE_BYTES - ) - - val DEFAULT_DOCUMENT_PROJECTION: Array<String> = arrayOf( - DocumentsContract.Document.COLUMN_DOCUMENT_ID, - DocumentsContract.Document.COLUMN_MIME_TYPE, - DocumentsContract.Document.COLUMN_DISPLAY_NAME, - DocumentsContract.Document.COLUMN_LAST_MODIFIED, - DocumentsContract.Document.COLUMN_FLAGS, - DocumentsContract.Document.COLUMN_SIZE - ) - - const val ROOT: String = "root" - - fun resolveRootProjection(projection: Array<String>?): Array<String> { - return projection ?: DEFAULT_ROOT_PROJECTION - } - - fun resolveDocumentProjection(projection: Array<String>?): Array<String> { - return projection ?: DEFAULT_DOCUMENT_PROJECTION - } - - fun getTypeForFile(file: File): String { - return if (file.isDirectory) { - DocumentsContract.Document.MIME_TYPE_DIR - } else { - getTypeForName(file.name) - } - } - - fun getTypeForName(name: String): String { - val lastDot = name.lastIndexOf('.') - if (lastDot >= 0) { - val extension = name.substring(lastDot + 1) - val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - if (mime != null) { - return mime - } - } - return "application/octet-stream" - } - - fun getChildMimeTypes(): String { - val mimeTypes = setOf( - "image/*", - "text/*", - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - DocumentsContract.Document.MIME_TYPE_DIR, - "application/octet-stream", - ) - return mimeTypes.joinToString("\n") - } - } } diff --git a/libs/documents/src/test/java/org/the_jk/cleversync/documents/DocumentTreeTestHelper.kt b/libs/documents/src/test/java/org/the_jk/cleversync/documents/DocumentTreeTestHelper.kt new file mode 100644 index 0000000..5358920 --- /dev/null +++ b/libs/documents/src/test/java/org/the_jk/cleversync/documents/DocumentTreeTestHelper.kt @@ -0,0 +1,440 @@ +package org.the_jk.cleversync.documents + +import android.Manifest.permission.MANAGE_DOCUMENTS +import android.content.Context +import android.content.pm.ProviderInfo +import android.database.Cursor +import android.database.MatrixCursor +import android.os.Build +import android.os.CancellationSignal +import android.os.ParcelFileDescriptor +import android.provider.DocumentsContract +import android.provider.DocumentsProvider +import android.webkit.MimeTypeMap +import androidx.test.core.app.ApplicationProvider +import com.google.common.truth.Truth.assertThat +import org.junit.rules.TemporaryFolder +import org.robolectric.Robolectric +import org.robolectric.android.controller.ContentProviderController +import org.the_jk.cleversync.io.ModifiableTree +import java.io.File +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException + +class DocumentTreeTestHelper { + private lateinit var controller: ContentProviderController<FakeProvider> + + fun openTree(folder: TemporaryFolder): ModifiableTree { + val context = ApplicationProvider.getApplicationContext<Context>() + val contentResolver = context.contentResolver + val providerInfo = ProviderInfo() + providerInfo.authority = "org.the_jk.cleversync.documents.test" + providerInfo.grantUriPermissions = true + providerInfo.exported = true + providerInfo.readPermission = MANAGE_DOCUMENTS + providerInfo.writePermission = MANAGE_DOCUMENTS + controller = Robolectric + .buildContentProvider(FakeProvider::class.java) + + controller.get().setBaseDir(folder.root) + controller.create(providerInfo) + + val treeUri = DocumentsContract.buildTreeDocumentUri(providerInfo.authority, ROOT) + assertThat(DocumentsContract.isTreeUri(treeUri)).isTrue() + + return DocumentTreeFactory.modifiableTree( + contentResolver, + treeUri, + ) + } + + @Suppress("UNUSED_PARAMETER") + fun closeTree(tree: ModifiableTree) { + controller.shutdown() + } + + private class FakeProvider : DocumentsProvider() { + private lateinit var baseDir: File + + fun setBaseDir(file: File) { + baseDir = file + } + + override fun onCreate(): Boolean { + return true + } + + override fun queryRoots(projection: Array<String>?): Cursor { + val result = MatrixCursor(resolveRootProjection(projection)) + val row = result.newRow() + row.add(DocumentsContract.Root.COLUMN_ROOT_ID, ROOT) + row.add(DocumentsContract.Root.COLUMN_SUMMARY, "") + row.add(DocumentsContract.Root.COLUMN_FLAGS, + DocumentsContract.Root.FLAG_SUPPORTS_CREATE or + DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD) + row.add(DocumentsContract.Root.COLUMN_TITLE, "fake") + row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocIdForFile(baseDir)) + row.add(DocumentsContract.Root.COLUMN_MIME_TYPES, getChildMimeTypes()) + row.add(DocumentsContract.Root.COLUMN_AVAILABLE_BYTES, baseDir.freeSpace) + row.add(DocumentsContract.Root.COLUMN_ICON, null) + return result + } + + override fun queryDocument( + documentId: String?, + projection: Array<String>? + ): Cursor { + val result = MatrixCursor(resolveDocumentProjection(projection)) + includeFile(result, documentId, null) + return result + } + + override fun queryChildDocuments( + parentDocumentId: String, + projection: Array<String>?, + sortOrder: String? + ): Cursor { + val result = MatrixCursor(resolveDocumentProjection(projection)) + val parent = getFileForDocId(parentDocumentId) + parent.listFiles()?.forEach { file -> + includeFile(result, null, file) + } + return result + } + + override fun openDocument( + documentId: String, + mode: String?, + signal: CancellationSignal? + ): ParcelFileDescriptor { + val file = getFileForDocId(documentId) + val accessMode = ParcelFileDescriptor.parseMode(mode) + return ParcelFileDescriptor.open(file, accessMode) + } + + override fun isChildDocument( + parentDocumentId: String, + documentId: String + ): Boolean { + try { + val parentFile = getFileForDocId(parentDocumentId) + var childFile: File? = getFileForDocId(documentId) + // childFile can be any level of descendant of parentFile + // to return true. + do { + if (isChildFile(parentFile, childFile!!)) return true + childFile = childFile.parentFile + } while (childFile != null) + return false + } catch (_: FileNotFoundException) { + } + return false + } + + override fun createDocument( + parentDocumentId: String, + mimeType: String?, + displayName: String, + ): String { + val parent = getFileForDocId(parentDocumentId) + val file = File(parent.path, displayName) + try { + var documentWasCreated = false + if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) { + if (file.mkdir()) { + if (file.setWritable(true) && file.setWritable(true) && file.setExecutable(true)) { + documentWasCreated = true + } + } + } else { + if (file.createNewFile()) { + if (file.setWritable(true) && file.setReadable(true)) { + documentWasCreated = true + } + } + } + + if (!documentWasCreated) { + throw IOException("Failed to create document with name " + + displayName +" and documentId " + parentDocumentId) + } + } catch (e: IOException) { + throw IOException("Failed to create document with name " + + displayName +" and documentId " + parentDocumentId, e) + } + return getDocIdForFile(file) + } + + override fun renameDocument( + documentId: String, + displayName: String? + ): String { + if (displayName == null) { + throw IOException("Failed to rename document, new name is null") + } + + val sourceFile = getFileForDocId(documentId) + val sourceParentFile = sourceFile.parentFile + ?: throw IOException("Failed to rename document. File has no parent.") + val destFile = File(sourceParentFile.path, displayName) + + try { + if (!sourceFile.renameTo(destFile)) { + throw IOException("Failed to rename document. Renamed failed.") + } + } catch (e: Exception) { + throw IOException("Failed to rename document.", e) + } + + return getDocIdForFile(destFile) + } + + override fun deleteDocument(documentId: String) { + val file = getFileForDocId(documentId) + if (!file.delete()) { + throw IOException("Failed to delete document with id $documentId") + } + } + + override fun removeDocument( + documentId: String, + parentDocumentId: String, + ) { + val parent = getFileForDocId(parentDocumentId) + val file = getFileForDocId(documentId) + + val doesFileParentMatch = isChildFile(parent, file) + + if (parent == file || doesFileParentMatch) { + if (!file.delete()) { + throw IOException("Failed to delete document with id $documentId") + } + } else { + throw IOException("Failed to delete document with id $documentId") + } + } + + override fun copyDocument( + sourceDocumentId: String, + targetParentDocumentId: String + ): String { + val parent = getFileForDocId(targetParentDocumentId) + val oldFile = getFileForDocId(sourceDocumentId) + val newFile = File(parent.path, oldFile.name) + + try { + var wasNewFileCreated = false + if (newFile.createNewFile()) { + if (newFile.setWritable(true) && newFile.setReadable(true)) { + wasNewFileCreated = true + } + } + + if (!wasNewFileCreated) { + throw IOException("Failed to copy document " + sourceDocumentId + + ". Could not create new file.") + } + + FileInputStream(oldFile).use { inStream -> + FileOutputStream(newFile).use { outStream -> + // Transfer bytes from in to out + val buf = ByteArray(65536) + var len: Int + while ((inStream.read(buf).also { len = it }) > 0) { + outStream.write(buf, 0, len) + } + } + } + } catch (e: IOException) { + throw IOException("Failed to copy document: $sourceDocumentId", e) + } + return getDocIdForFile(newFile) + } + + override fun moveDocument( + sourceDocumentId: String, + sourceParentDocumentId: String, + targetParentDocumentId: String, + ): String { + try { + // Copy document, insisting that the parent is correct + val newDocumentId = copyDocument(sourceDocumentId, sourceParentDocumentId, + targetParentDocumentId) + // Remove old document + removeDocument(sourceDocumentId,sourceParentDocumentId) + return newDocumentId + } catch (e: IOException) { + throw IOException("Failed to move document $sourceDocumentId", e) + } + } + + override fun getDocumentType(documentId: String): String { + val file = getFileForDocId(documentId) + return getTypeForFile(file) + } + + private fun copyDocument( + sourceDocumentId: String, + sourceParentDocumentId: String, + targetParentDocumentId: String, + ): String { + if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) { + throw IOException( + "Failed to copy document with id " + + sourceDocumentId + ". Parent is not: " + sourceParentDocumentId + ) + } + return copyDocument(sourceDocumentId, targetParentDocumentId) + } + + private fun isChildFile(parentFile: File, childFile: File): Boolean { + return parentFile == childFile.parentFile + } + + private fun getDocIdForFile(file: File): String { + var path = file.absolutePath + + // Start at first char of path under root + val rootPath = baseDir.path + path = if (rootPath.equals(path)) { + "" + } else if (rootPath.endsWith("/")) { + path.substring(rootPath.length) + } else { + path.substring(rootPath.length + 1) + } + + return "$ROOT:$path" + } + + private fun getFileForDocId(docId: String): File { + var target = baseDir + if (docId == ROOT) { + return target + } + val splitIndex = docId.indexOf(':', 1) + if (splitIndex < 0) { + throw FileNotFoundException("Missing root for $docId") + } else { + val path = docId.substring(splitIndex + 1) + target = File(target, path) + if (!target.exists()) { + throw FileNotFoundException("Missing file for $docId at $target") + } + return target + } + } + + private fun includeFile(result: MatrixCursor, inDocId: String?, inFile: File?) { + val (docId, file) = if (inDocId == null) { + getDocIdForFile(inFile!!) to inFile + } else { + inDocId to getFileForDocId(inDocId) + } + + var flags = 0 + + if (file.isDirectory) { + // Add FLAG_DIR_SUPPORTS_CREATE if the file is a writable directory. + if (file.canWrite()) { + flags = flags or DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE + } + } + if (file.canWrite()) { + // If the file is writable set FLAG_SUPPORTS_WRITE and + // FLAG_SUPPORTS_DELETE + flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_WRITE + flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_DELETE + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + flags = + flags or DocumentsContract.Document.FLAG_SUPPORTS_RENAME + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + flags = + flags or DocumentsContract.Document.FLAG_SUPPORTS_REMOVE + flags = + flags or DocumentsContract.Document.FLAG_SUPPORTS_MOVE + flags = + flags or DocumentsContract.Document.FLAG_SUPPORTS_COPY + } + } + + val displayName = file.name + val mimeType = getTypeForFile(file) + + val row = result.newRow() + row.add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, docId) + row.add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, displayName) + row.add(DocumentsContract.Document.COLUMN_SIZE, file.length()) + row.add(DocumentsContract.Document.COLUMN_MIME_TYPE, mimeType) + row.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, file.lastModified()) + row.add(DocumentsContract.Document.COLUMN_FLAGS, flags) + row.add(DocumentsContract.Document.COLUMN_ICON, null) + } + } + + private companion object { + val DEFAULT_ROOT_PROJECTION = arrayOf( + DocumentsContract.Root.COLUMN_ROOT_ID, + DocumentsContract.Root.COLUMN_MIME_TYPES, + DocumentsContract.Root.COLUMN_FLAGS, + DocumentsContract.Root.COLUMN_ICON, + DocumentsContract.Root.COLUMN_TITLE, + DocumentsContract.Root.COLUMN_SUMMARY, + DocumentsContract.Root.COLUMN_DOCUMENT_ID, + DocumentsContract.Root.COLUMN_AVAILABLE_BYTES + ) + + val DEFAULT_DOCUMENT_PROJECTION: Array<String> = arrayOf( + DocumentsContract.Document.COLUMN_DOCUMENT_ID, + DocumentsContract.Document.COLUMN_MIME_TYPE, + DocumentsContract.Document.COLUMN_DISPLAY_NAME, + DocumentsContract.Document.COLUMN_LAST_MODIFIED, + DocumentsContract.Document.COLUMN_FLAGS, + DocumentsContract.Document.COLUMN_SIZE + ) + + const val ROOT: String = "root" + + fun resolveRootProjection(projection: Array<String>?): Array<String> { + return projection ?: DEFAULT_ROOT_PROJECTION + } + + fun resolveDocumentProjection(projection: Array<String>?): Array<String> { + return projection ?: DEFAULT_DOCUMENT_PROJECTION + } + + fun getTypeForFile(file: File): String { + return if (file.isDirectory) { + DocumentsContract.Document.MIME_TYPE_DIR + } else { + getTypeForName(file.name) + } + } + + fun getTypeForName(name: String): String { + val lastDot = name.lastIndexOf('.') + if (lastDot >= 0) { + val extension = name.substring(lastDot + 1) + val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) + if (mime != null) { + return mime + } + } + return "application/octet-stream" + } + + fun getChildMimeTypes(): String { + val mimeTypes = setOf( + "image/*", + "text/*", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + DocumentsContract.Document.MIME_TYPE_DIR, + "application/octet-stream", + ) + return mimeTypes.joinToString("\n") + } + } +} |
