Skip to content

Commit df496d4

Browse files
authored
Merge pull request #6169 from grzesiek2010/COLLECT-5846
Allow MBTile files to be imported
2 parents b537b21 + 63a7a4b commit df496d4

29 files changed

+1187
-273
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package org.odk.collect.androidshared.system
2+
3+
import android.content.ContentResolver
4+
import android.content.Context
5+
import android.net.Uri
6+
import android.provider.OpenableColumns
7+
import android.webkit.MimeTypeMap
8+
import androidx.core.net.toFile
9+
import java.io.File
10+
import java.io.FileOutputStream
11+
12+
fun Uri.copyToFile(context: Context, dest: File) {
13+
try {
14+
context.contentResolver.openInputStream(this)?.use { inputStream ->
15+
FileOutputStream(dest).use { outputStream ->
16+
inputStream.copyTo(outputStream)
17+
}
18+
}
19+
} catch (e: Exception) {
20+
// ignore
21+
}
22+
}
23+
24+
fun Uri.getFileExtension(context: Context): String? {
25+
var extension = getFileName(context)?.substringAfterLast(".", "")
26+
27+
if (extension.isNullOrEmpty()) {
28+
val mimeType = context.contentResolver.getType(this)
29+
30+
extension = if (scheme == ContentResolver.SCHEME_CONTENT) {
31+
MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
32+
} else {
33+
MimeTypeMap.getFileExtensionFromUrl(toString())
34+
}
35+
36+
if (extension.isNullOrEmpty()) {
37+
extension = mimeType?.substringAfterLast("/", "")
38+
}
39+
}
40+
41+
return if (extension.isNullOrEmpty()) {
42+
null
43+
} else {
44+
".$extension"
45+
}
46+
}
47+
48+
fun Uri.getFileName(context: Context): String? {
49+
var fileName: String? = null
50+
51+
try {
52+
when (scheme) {
53+
ContentResolver.SCHEME_FILE -> fileName = toFile().name
54+
ContentResolver.SCHEME_CONTENT -> {
55+
val cursor = context.contentResolver.query(this, null, null, null, null)
56+
cursor?.use {
57+
if (it.moveToFirst()) {
58+
val fileNameColumnIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
59+
if (fileNameColumnIndex != -1) {
60+
fileName = it.getString(fileNameColumnIndex)
61+
}
62+
}
63+
}
64+
}
65+
ContentResolver.SCHEME_ANDROID_RESOURCE -> {
66+
// for uris like [android.resource://com.example.app/1234567890]
67+
val resourceId = lastPathSegment?.toIntOrNull()
68+
if (resourceId != null) {
69+
fileName = context.resources.getResourceName(resourceId)
70+
} else {
71+
// for uris like [android.resource://com.example.app/raw/sample]
72+
val packageName = authority
73+
if (pathSegments.size >= 2) {
74+
val resourceType = pathSegments[0]
75+
val resourceEntryName = pathSegments[1]
76+
val resId = context.resources.getIdentifier(resourceEntryName, resourceType, packageName)
77+
if (resId != 0) {
78+
fileName = context.resources.getResourceName(resId)
79+
}
80+
}
81+
}
82+
}
83+
}
84+
85+
if (fileName == null) {
86+
fileName = path?.substringAfterLast("/")
87+
}
88+
} catch (e: Exception) {
89+
// ignore
90+
}
91+
92+
return fileName
93+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.odk.collect.androidshared.system
2+
3+
import android.app.Application
4+
import androidx.core.net.toUri
5+
import androidx.test.core.app.ApplicationProvider
6+
import androidx.test.ext.junit.runners.AndroidJUnit4
7+
import org.hamcrest.CoreMatchers.equalTo
8+
import org.hamcrest.MatcherAssert.assertThat
9+
import org.junit.Test
10+
import org.junit.runner.RunWith
11+
import org.odk.collect.shared.TempFiles
12+
13+
@RunWith(AndroidJUnit4::class)
14+
class UriExtTest {
15+
private val context = ApplicationProvider.getApplicationContext<Application>()
16+
17+
@Test
18+
fun `copyToFile copies the source file to the target file`() {
19+
val sourceFile = TempFiles.createTempFile().also {
20+
it.writeText("blah")
21+
}
22+
val sourceFileUri = sourceFile.toUri()
23+
val targetFile = TempFiles.createTempFile()
24+
25+
sourceFileUri.copyToFile(context, targetFile)
26+
assertThat(targetFile.readText(), equalTo(sourceFile.readText()))
27+
}
28+
29+
@Test
30+
fun `getFileExtension returns file extension`() {
31+
val file = TempFiles.createTempFile(".jpg")
32+
val fileUri = file.toUri()
33+
34+
assertThat(fileUri.getFileExtension(context), equalTo(".jpg"))
35+
}
36+
37+
@Test
38+
fun `getFileName returns file name`() {
39+
val file = TempFiles.createTempFile()
40+
val fileUri = file.toUri()
41+
42+
assertThat(fileUri.getFileName(context), equalTo(file.name))
43+
}
44+
}

collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyModule.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@
137137
import org.odk.collect.webpage.ExternalWebPageHelper;
138138

139139
import java.io.File;
140-
import java.util.Arrays;
141140

142141
import javax.inject.Named;
143142
import javax.inject.Singleton;
@@ -573,7 +572,8 @@ public PreferenceVisibilityHandler providesDisabledPreferencesRemover(SettingsPr
573572
@Provides
574573
public ReferenceLayerRepository providesReferenceLayerRepository(StoragePathProvider storagePathProvider, SettingsProvider settingsProvider) {
575574
return new DirectoryReferenceLayerRepository(
576-
Arrays.asList(storagePathProvider.getOdkDirPath(StorageSubdirectory.LAYERS), storagePathProvider.getOdkDirPath(StorageSubdirectory.SHARED_LAYERS)),
575+
storagePathProvider.getOdkDirPath(StorageSubdirectory.SHARED_LAYERS),
576+
storagePathProvider.getOdkDirPath(StorageSubdirectory.LAYERS),
577577
() -> MapConfiguratorProvider.getConfigurator(
578578
settingsProvider.getUnprotectedSettings().getString(ProjectKeys.KEY_BASEMAP_SOURCE)
579579
)

collect_app/src/main/java/org/odk/collect/android/preferences/screens/MapsPreferencesFragment.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class MapsPreferencesFragment : BaseProjectPreferencesFragment(), Preference.OnP
5656
override fun onCreate(savedInstanceState: Bundle?) {
5757
childFragmentManager.fragmentFactory = FragmentFactoryBuilder()
5858
.forClass(OfflineMapLayersPicker::class) {
59-
OfflineMapLayersPicker(referenceLayerRepository, scheduler, settingsProvider, externalWebPageHelper)
59+
OfflineMapLayersPicker(requireActivity().activityResultRegistry, referenceLayerRepository, scheduler, settingsProvider, externalWebPageHelper)
6060
}
6161
.build()
6262

@@ -110,8 +110,8 @@ class MapsPreferencesFragment : BaseProjectPreferencesFragment(), Preference.OnP
110110
onBasemapSourceChanged(MapConfiguratorProvider.getConfigurator())
111111
basemapSourcePref.setOnPreferenceChangeListener { _: Preference?, value: Any ->
112112
val cftor = MapConfiguratorProvider.getConfigurator(value.toString())
113-
if (!cftor.isAvailable(context)) {
114-
cftor.showUnavailableMessage(context)
113+
if (!cftor.isAvailable(requireContext())) {
114+
cftor.showUnavailableMessage(requireContext())
115115
false
116116
} else {
117117
onBasemapSourceChanged(cftor)
@@ -142,7 +142,7 @@ class MapsPreferencesFragment : BaseProjectPreferencesFragment(), Preference.OnP
142142
val baseCategory = findPreference<PreferenceCategory>(CATEGORY_BASEMAP)
143143
baseCategory!!.removeAll()
144144
baseCategory.addPreference(basemapSourcePref)
145-
for (pref in cftor.createPrefs(context, settingsProvider.getUnprotectedSettings())) {
145+
for (pref in cftor.createPrefs(requireContext(), settingsProvider.getUnprotectedSettings())) {
146146
pref.isIconSpaceReserved = false
147147
baseCategory.addPreference(pref)
148148
}

collect_app/src/main/res/layout/reference_layer_help_footer.xml

Lines changed: 0 additions & 18 deletions
This file was deleted.

docs/CODE-GUIDELINES.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,18 @@ Collect is a multi module Gradle project. Modules should have a focused feature
178178
There's no easy way to define exactly when a new module should be pulled out of an existing one or when new code calls for a new module - it's best to discuss that with the team before making any decisions. Once a structure has been agreed on, to add a new module:
179179

180180
1. Click `File > New > New module...` in Android Studio
181-
1. Decide whether the new module should be an "Android Library" or "Java or Kotlin Library" - ideally as much code as possible could avoid relying on Android but a lot of features will require at least one Android Library module
182-
1. Review the generated `build.gradle` and remove any unnecessary dependencies or setup
183-
1. Add quality checks to the module's `build.gradle`:
181+
2. Decide whether the new module should be an "Android Library" or "Java or Kotlin Library" - ideally as much code as possible could avoid relying on Android but a lot of features will require at least one Android Library module
182+
3. Review the generated `build.gradle` and remove any unnecessary dependencies or setup
183+
4. Add quality checks to the module's `build.gradle`:
184184

185185
```
186186
apply from: '../config/quality.gradle'
187187
```
188188

189-
1. If the module will have tests, make sure they get run on CI by adding a line to `test_modules.txt` with `<module-name>:test` for a Java Library or `<module-name>:testDebug` for an Android library
189+
5. If the module will have tests, make sure they get run on CI by adding a line to `test_modules.txt` with `<module-name>` and if it's a non-Android module, registering the `testDebug` task in its `build.gradle` file:
190+
191+
```
192+
tasks.register("testDebug") {
193+
dependsOn("test")
194+
}
195+
```

geo/src/main/java/org/odk/collect/geo/geopoint/GeoPointMapActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ public void onCreate(Bundle savedInstanceState) {
141141

142142
getSupportFragmentManager().setFragmentFactory(new FragmentFactoryBuilder()
143143
.forClass(MapFragment.class, () -> (Fragment) mapFragmentFactory.createMapFragment())
144-
.forClass(OfflineMapLayersPicker.class, () -> new OfflineMapLayersPicker(referenceLayerRepository, scheduler, settingsProvider, externalWebPageHelper))
144+
.forClass(OfflineMapLayersPicker.class, () -> new OfflineMapLayersPicker(getActivityResultRegistry(), referenceLayerRepository, scheduler, settingsProvider, externalWebPageHelper))
145145
.build()
146146
);
147147

geo/src/main/java/org/odk/collect/geo/geopoly/GeoPolyActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ public void handleOnBackPressed() {
154154

155155
getSupportFragmentManager().setFragmentFactory(new FragmentFactoryBuilder()
156156
.forClass(MapFragment.class, () -> (Fragment) mapFragmentFactory.createMapFragment())
157-
.forClass(OfflineMapLayersPicker.class, () -> new OfflineMapLayersPicker(referenceLayerRepository, scheduler, settingsProvider, externalWebPageHelper))
157+
.forClass(OfflineMapLayersPicker.class, () -> new OfflineMapLayersPicker(getActivityResultRegistry(), referenceLayerRepository, scheduler, settingsProvider, externalWebPageHelper))
158158
.build()
159159
);
160160

geo/src/main/java/org/odk/collect/geo/selection/SelectionMapFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class SelectionMapFragment(
9797
mapFragmentFactory.createMapFragment() as Fragment
9898
}
9999
.forClass(OfflineMapLayersPicker::class) {
100-
OfflineMapLayersPicker(referenceLayerRepository, scheduler, settingsProvider, externalWebPageHelper)
100+
OfflineMapLayersPicker(requireActivity().activityResultRegistry, referenceLayerRepository, scheduler, settingsProvider, externalWebPageHelper)
101101
}
102102
.build()
103103

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<vector
2+
xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:width="24dp"
4+
android:height="24dp"
5+
android:viewportWidth="24"
6+
android:viewportHeight="24"
7+
android:autoMirrored="true"
8+
android:tint="?colorOnSurface">
9+
10+
<path
11+
android:fillColor="?colorOnSurface"
12+
android:pathData="M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27 -7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z"/>
13+
</vector>

0 commit comments

Comments
 (0)