Skip to content

Commit

Permalink
zxtune-android: fix browser for Android 14+.
Browse files Browse the repository at this point in the history
  • Loading branch information
vitamin-caig committed Oct 17, 2023
1 parent b48f06d commit 2d34613
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.provider.DocumentsContract
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import androidx.core.database.getStringOrNull
import androidx.documentfile.provider.DocumentFile
import app.zxtune.R
import app.zxtune.Util
import app.zxtune.fs.local.Document
Expand Down Expand Up @@ -45,9 +46,8 @@ class VfsRootLocalStorageAccessFramework(private val context: Context) : StubObj
else -> super.getExtension(id)
}

override fun enumerate(visitor: VfsDir.Visitor) = manager.storageVolumes
.also { visitor.onItemsCount(it.size) }
.forEach {
override fun enumerate(visitor: VfsDir.Visitor) =
manager.storageVolumes.also { visitor.onItemsCount(it.size) }.forEach {
if (it.isMounted()) {
StorageRoot(Identifier.fromRoot(it.rootId()), it.getDescription(context)).run {
visitor.onDir(this)
Expand All @@ -65,11 +65,17 @@ class VfsRootLocalStorageAccessFramework(private val context: Context) : StubObj
id.root.isEmpty() -> convertLegacyIdentifier(id)?.let {
resolve(it)
}

id.path.isEmpty() -> StorageRoot(id)
else -> when (resolver.getType(id.documentUri)) {
null -> null
DocumentsContract.Document.MIME_TYPE_DIR -> LocalDir(id)
else -> LocalFile(id)
else -> accessibleDirectories.findAncestor(id)?.let { treeId ->
DocumentFile.fromTreeUri(context, id.getDocumentUriUsingTree(treeId))?.run {
if (isDirectory) LocalDir(id, treeId) else LocalFile(id, treeId)
}
} ?: if (accessibleDirectories.getDirectChildrenOf(id.parent).contains(id)) {
// Phantom dir
LocalDir(id)
} else {
null
}
}

Expand Down Expand Up @@ -103,9 +109,7 @@ class VfsRootLocalStorageAccessFramework(private val context: Context) : StubObj
}

private inner class LocalDir(
id: Identifier,
treeId: Identifier? = null,
override val description: String = ""
id: Identifier, treeId: Identifier? = null, override val description: String = ""
) : BaseObject(id), VfsDir {

private val treeId by lazy {
Expand Down Expand Up @@ -147,8 +151,7 @@ class VfsRootLocalStorageAccessFramework(private val context: Context) : StubObj
}

private fun getPhantomDirs(id: Identifier, visitor: VfsDir.Visitor) =
accessibleDirectories.getDirectChildrenOf(id)
.also { visitor.onItemsCount(it.size) }
accessibleDirectories.getDirectChildrenOf(id).also { visitor.onItemsCount(it.size) }
.forEach {
visitor.onDir(LocalDir(it))
}
Expand All @@ -166,9 +169,9 @@ class VfsRootLocalStorageAccessFramework(private val context: Context) : StubObj

override fun getExtension(id: String) = when (id) {
VfsExtensions.FILE_DESCRIPTOR -> resolver.openFileDescriptor(
fileUri,
"r"
fileUri, "r"
)?.fileDescriptor

else -> super.getExtension(id)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,21 @@ class VfsRootLocalStorageAccessFrameworkTest {
on { contentResolver } doReturn resolver
on { getSystemService(StorageManager::class.java) } doReturn storageManager
}
private val documentFile = mock<DocumentFile>()

private val underTest = VfsRootLocalStorageAccessFramework(context)

private val visitor = mock<VfsDir.Visitor>()

@Before
fun setUp() {
reset(resolver, storageManager, visitor)
ShadowDocumentFile.document = null
reset(resolver, storageManager, documentFile, visitor)
ShadowDocumentFile.document = documentFile
}

@After
fun tearDown() = verifyNoMoreInteractions(resolver, storageManager, context, visitor)
fun tearDown() =
verifyNoMoreInteractions(resolver, storageManager, context, visitor)

@Test
fun `no storages`() {
Expand All @@ -68,7 +70,6 @@ class VfsRootLocalStorageAccessFrameworkTest {
val permittedId1 = Identifier("storage", "path/subpath/dir/accessible")
val permittedId2 = Identifier("storage", "path/subpath/access")
resolver.stub {
on { getType(any()) } doReturn DocumentsContract.Document.MIME_TYPE_DIR
on { query(any(), any(), anyOrNull(), anyOrNull()) } doThrow SecurityException()
on { persistedUriPermissions } doAnswer {
arrayOf(permittedId1, permittedId2).map { permId ->
Expand All @@ -87,10 +88,8 @@ class VfsRootLocalStorageAccessFrameworkTest {
val captor = argumentCaptor<VfsDir>()
inOrder(context, resolver, visitor) {
verify(context).contentResolver
// resolve
verify(resolver).getType(id.documentUri)
// permission query uri + enumerate
verify(resolver, times(2)).persistedUriPermissions
// resolve(try real, try phantom) + permission query uri + enumerate
verify(resolver, times(4)).persistedUriPermissions
verify(visitor).onItemsCount(2)
verify(visitor, times(2)).onDir(captor.capture())
}
Expand Down Expand Up @@ -150,7 +149,7 @@ class VfsRootLocalStorageAccessFrameworkTest {
// subpath
verify(storageManager).storageVolumes
verify(context).contentResolver
verify(resolver).getType(id2.copy(path = "sub/path").documentUri)
verify(resolver, times(2)).persistedUriPermissions
// unknown + volume root
verify(storageManager, times(2)).storageVolumes
}
Expand All @@ -162,29 +161,29 @@ class VfsRootLocalStorageAccessFrameworkTest {
val id = Identifier("stor", "path/to/file")
val descriptor = FileDescriptor()
resolver.stub {
on { getType(any()) } doReturn "binary/type"
on { persistedUriPermissions } doAnswer {
listOf(
mock {
on { uri } doReturn permId.treeDocumentUri
on { isReadPermission } doReturn true
}
)
listOf(mock {
on { uri } doReturn permId.treeDocumentUri
on { isReadPermission } doReturn true
})
}
on { openFileDescriptor(any(), any()) } doAnswer {
mock {
on { fileDescriptor } doReturn descriptor
}
}
}
documentFile.stub {
on { uri } doReturn id.getDocumentUriUsingTree(permId)
}
(underTest.resolve(id.fsUri) as VfsFile).run {
assertEquals("file", name)
assertEquals(null, file)
assertSame(descriptor, fileDescriptor)
}
inOrder(context, resolver) {
verify(context).contentResolver
verify(resolver).getType(id.documentUri)
// findAncestor
verify(resolver).persistedUriPermissions
verify(resolver).openFileDescriptor(id.getDocumentUriUsingTree(permId), "r")
}
Expand Down Expand Up @@ -255,7 +254,6 @@ class VfsRootLocalStorageAccessFrameworkTest {
val subDir = Identifier("root", "dir/subdir")
val subFile = Identifier("root", "dir/subfile")
resolver.stub {
on { getType(any()) } doReturn DocumentsContract.Document.MIME_TYPE_DIR
on { query(any(), any(), anyOrNull(), anyOrNull()) } doAnswer {
MatrixCursor(it.getArgument<Array<String>>(1)).apply {
newRow().apply {
Expand All @@ -275,14 +273,16 @@ class VfsRootLocalStorageAccessFrameworkTest {
}
}
on { persistedUriPermissions } doAnswer {
listOf(
mock {
on { uri } doReturn permId.treeDocumentUri
on { isReadPermission } doReturn true
}
)
listOf(mock {
on { uri } doReturn permId.treeDocumentUri
on { isReadPermission } doReturn true
})
}
}
documentFile.stub {
on { uri } doReturn id.getDocumentUriUsingTree(permId)
on { isDirectory } doReturn true
}
(underTest.resolve(id.fsUri) as VfsDir).run {
assertEquals("dir", name)
assertEquals(null, permissionQueryUri)
Expand All @@ -291,15 +291,10 @@ class VfsRootLocalStorageAccessFrameworkTest {
inOrder(context, resolver, visitor) {
verify(context).contentResolver
// resolve
verify(resolver).getType(id.documentUri)
// permission query uri
verify(resolver).persistedUriPermissions
// enumerate
verify(resolver).query(
eq(id.getTreeChildDocumentUri(permId)),
any(),
eq(null),
eq(null)
eq(id.getTreeChildDocumentUri(permId)), any(), eq(null), eq(null)
)
verify(visitor).onItemsCount(2)
verify(visitor).onDir(argThat {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ class ShadowDocumentFile {
@Implementation
@JvmStatic
fun fromSingleUri(ctx: Context, uri: Uri) = document?.takeIf { uri == it.uri }

@Implementation
@JvmStatic
fun fromTreeUri(ctx: Context, uri: Uri) = document?.takeIf { uri == it.uri }
}
}

Expand Down

0 comments on commit 2d34613

Please sign in to comment.