Skip to content

Commit

Permalink
Improved UriExt and added tests
Browse files Browse the repository at this point in the history
  • Loading branch information
grzesiek2010 committed Jun 7, 2024
1 parent ef62803 commit 63a7a4b
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package org.odk.collect.androidshared.system

import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import android.webkit.MimeTypeMap
import androidx.core.net.toFile
import java.io.File
import java.io.FileOutputStream

fun Uri.copyToFile(contentResolver: ContentResolver, dest: File) {
fun Uri.copyToFile(context: Context, dest: File) {
try {
contentResolver.openInputStream(this)?.use { inputStream ->
context.contentResolver.openInputStream(this)?.use { inputStream ->
FileOutputStream(dest).use { outputStream ->
inputStream.copyTo(outputStream)
}
Expand All @@ -18,19 +21,73 @@ fun Uri.copyToFile(contentResolver: ContentResolver, dest: File) {
}
}

fun Uri.getFileName(contentResolver: ContentResolver): String? {
fun Uri.getFileExtension(context: Context): String? {
var extension = getFileName(context)?.substringAfterLast(".", "")

if (extension.isNullOrEmpty()) {
val mimeType = context.contentResolver.getType(this)

extension = if (scheme == ContentResolver.SCHEME_CONTENT) {
MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
} else {
MimeTypeMap.getFileExtensionFromUrl(toString())
}

if (extension.isNullOrEmpty()) {
extension = mimeType?.substringAfterLast("/", "")
}
}

return if (extension.isNullOrEmpty()) {
null
} else {
".$extension"
}
}

fun Uri.getFileName(context: Context): String? {
var fileName: String? = null
if (scheme == ContentResolver.SCHEME_CONTENT) {
val cursor = contentResolver.query(this, null, null, null, null)
cursor.use {
if (it != null && it.moveToFirst()) {
val fileNameColumnIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
fileName = it.getString(fileNameColumnIndex)

try {
when (scheme) {
ContentResolver.SCHEME_FILE -> fileName = toFile().name
ContentResolver.SCHEME_CONTENT -> {
val cursor = context.contentResolver.query(this, null, null, null, null)
cursor?.use {
if (it.moveToFirst()) {
val fileNameColumnIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (fileNameColumnIndex != -1) {
fileName = it.getString(fileNameColumnIndex)
}
}
}
}
ContentResolver.SCHEME_ANDROID_RESOURCE -> {
// for uris like [android.resource://com.example.app/1234567890]
val resourceId = lastPathSegment?.toIntOrNull()
if (resourceId != null) {
fileName = context.resources.getResourceName(resourceId)
} else {
// for uris like [android.resource://com.example.app/raw/sample]
val packageName = authority
if (pathSegments.size >= 2) {
val resourceType = pathSegments[0]
val resourceEntryName = pathSegments[1]
val resId = context.resources.getIdentifier(resourceEntryName, resourceType, packageName)
if (resId != 0) {
fileName = context.resources.getResourceName(resId)
}
}
}
}
}

if (fileName == null) {
fileName = path?.substringAfterLast("/")
}
} catch (e: Exception) {
// ignore
}
if (fileName == null) {
fileName = path?.substringAfterLast("/")
}

return fileName
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.odk.collect.androidshared.system

import android.app.Application
import androidx.core.net.toUri
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.odk.collect.shared.TempFiles

@RunWith(AndroidJUnit4::class)
class UriExtTest {
private val context = ApplicationProvider.getApplicationContext<Application>()

@Test
fun `copyToFile copies the source file to the target file`() {
val sourceFile = TempFiles.createTempFile().also {
it.writeText("blah")
}
val sourceFileUri = sourceFile.toUri()
val targetFile = TempFiles.createTempFile()

sourceFileUri.copyToFile(context, targetFile)
assertThat(targetFile.readText(), equalTo(sourceFile.readText()))
}

@Test
fun `getFileExtension returns file extension`() {
val file = TempFiles.createTempFile(".jpg")
val fileUri = file.toUri()

assertThat(fileUri.getFileExtension(context), equalTo(".jpg"))
}

@Test
fun `getFileName returns file name`() {
val file = TempFiles.createTempFile()
val fileUri = file.toUri()

assertThat(fileUri.getFileName(context), equalTo(file.name))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class OfflineMapLayersImporter(
val viewModel: OfflineMapLayersViewModel by activityViewModels {
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return OfflineMapLayersViewModel(referenceLayerRepository, scheduler, settingsProvider, requireContext().contentResolver) as T
return OfflineMapLayersViewModel(referenceLayerRepository, scheduler, settingsProvider) as T
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class OfflineMapLayersPicker(
private val viewModel: OfflineMapLayersViewModel by activityViewModels {
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return OfflineMapLayersViewModel(referenceLayerRepository, scheduler, settingsProvider, requireContext().contentResolver) as T
return OfflineMapLayersViewModel(referenceLayerRepository, scheduler, settingsProvider) as T
}
}
}
Expand All @@ -39,7 +39,7 @@ class OfflineMapLayersPicker(

private val getLayers = registerForActivityResult(ActivityResultContracts.GetMultipleContents(), registry) { uris ->
if (uris.isNotEmpty()) {
viewModel.loadLayersToImport(uris)
viewModel.loadLayersToImport(uris, requireContext())
DialogFragmentUtils.showIfNotShowing(
OfflineMapLayersImporter::class.java,
childFragmentManager
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package org.odk.collect.maps.layers

import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.odk.collect.androidshared.system.copyToFile
import org.odk.collect.androidshared.system.getFileExtension
import org.odk.collect.androidshared.system.getFileName
import org.odk.collect.async.Scheduler
import org.odk.collect.settings.SettingsProvider
Expand All @@ -16,8 +17,7 @@ import java.io.File
class OfflineMapLayersViewModel(
private val referenceLayerRepository: ReferenceLayerRepository,
private val scheduler: Scheduler,
private val settingsProvider: SettingsProvider,
private val contentResolver: ContentResolver
private val settingsProvider: SettingsProvider
) : ViewModel() {
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
Expand Down Expand Up @@ -49,7 +49,7 @@ class OfflineMapLayersViewModel(
)
}

fun loadLayersToImport(uris: List<Uri>) {
fun loadLayersToImport(uris: List<Uri>, context: Context) {
_isLoading.value = true
scheduler.immediate(
background = {
Expand All @@ -58,10 +58,10 @@ class OfflineMapLayersViewModel(
}
val layers = mutableListOf<ReferenceLayer>()
uris.forEach { uri ->
uri.getFileName(contentResolver)?.let { fileName ->
if (fileName.endsWith(MbtilesFile.FILE_EXTENSION)) {
if (uri.getFileExtension(context) == MbtilesFile.FILE_EXTENSION) {
uri.getFileName(context)?.let { fileName ->
val layerFile = File(tempLayersDir, fileName).also { file ->
uri.copyToFile(contentResolver, file)
uri.copyToFile(context, file)
}
layers.add(ReferenceLayer(layerFile.absolutePath, layerFile, MbtilesFile.readName(layerFile) ?: layerFile.name))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class OfflineMapLayersImporterTest {
launchFragment().onFragment {
scheduler.flush()
assertThat(it.isVisible, equalTo(true))
it.viewModel.loadLayersToImport(emptyList())
it.viewModel.loadLayersToImport(emptyList(), it.requireContext())
onView(withId(org.odk.collect.maps.R.id.add_layer_button)).perform(click())
scheduler.flush()
RobolectricHelpers.runLooper()
Expand All @@ -77,7 +77,7 @@ class OfflineMapLayersImporterTest {
val file2 = TempFiles.createTempFile("layer2", MbtilesFile.FILE_EXTENSION)

launchFragment().onFragment {
it.viewModel.loadLayersToImport(listOf(file1.toUri(), file2.toUri()))
it.viewModel.loadLayersToImport(listOf(file1.toUri(), file2.toUri()), it.requireContext())
}

onView(withId(org.odk.collect.maps.R.id.progress_indicator)).check(matches(isDisplayed()))
Expand Down Expand Up @@ -150,7 +150,7 @@ class OfflineMapLayersImporterTest {
val file2 = TempFiles.createTempFile("layer2", MbtilesFile.FILE_EXTENSION)

launchFragment().onFragment {
it.viewModel.loadLayersToImport(listOf(file1.toUri(), file2.toUri()))
it.viewModel.loadLayersToImport(listOf(file1.toUri(), file2.toUri()), it.requireContext())
}

scheduler.flush()
Expand All @@ -166,7 +166,7 @@ class OfflineMapLayersImporterTest {
val file2 = TempFiles.createTempFile("layer2", MbtilesFile.FILE_EXTENSION)

val scenario = launchFragment().onFragment {
it.viewModel.loadLayersToImport(listOf(file1.toUri(), file2.toUri()))
it.viewModel.loadLayersToImport(listOf(file1.toUri(), file2.toUri()), it.requireContext())
}

scheduler.flush()
Expand All @@ -184,7 +184,7 @@ class OfflineMapLayersImporterTest {
val file2 = TempFiles.createTempFile("layer2", ".txt")

launchFragment().onFragment {
it.viewModel.loadLayersToImport(listOf(file1.toUri(), file2.toUri()))
it.viewModel.loadLayersToImport(listOf(file1.toUri(), file2.toUri()), it.requireContext())
}

scheduler.flush()
Expand All @@ -200,7 +200,7 @@ class OfflineMapLayersImporterTest {
val file2 = TempFiles.createTempFile("layer2", MbtilesFile.FILE_EXTENSION)

launchFragment().onFragment {
it.viewModel.loadLayersToImport(listOf(file1.toUri(), file2.toUri()))
it.viewModel.loadLayersToImport(listOf(file1.toUri(), file2.toUri()), it.requireContext())
}

scheduler.flush()
Expand All @@ -224,7 +224,7 @@ class OfflineMapLayersImporterTest {
val file2 = TempFiles.createTempFile("layer2", MbtilesFile.FILE_EXTENSION)

launchFragment().onFragment {
it.viewModel.loadLayersToImport(listOf(file1.toUri(), file2.toUri()))
it.viewModel.loadLayersToImport(listOf(file1.toUri(), file2.toUri()), it.requireContext())
}

scheduler.flush()
Expand Down

0 comments on commit 63a7a4b

Please sign in to comment.