-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Location co-ordinates on questionnaire submission #2997
Changes from 32 commits
563fca0
6c52152
dcfbe3c
6a9307e
093138c
cf61cc8
ca395b2
eabd1be
13c170d
2427914
d2dd2f0
e7babb2
57ee61e
6d0a310
16285f7
ba0fc6f
8d8df47
66ec65f
a910980
06c6e7c
52bea17
c59f335
8431a58
4c39e81
ae10339
7cbbac0
642542d
a686ce0
1a8e82c
94a01e0
150c64f
3b46ada
7493e33
2f47f93
1a9ed26
a06ae76
4e12927
baae9b3
5e4dee6
2423fc5
516f484
8f63da1
554eb83
fe035a6
066d1b9
a107cc5
1797620
d30b3bf
5d2d057
4b054df
56c26b3
14e18fe
1210019
5623799
da25d8d
e3719a6
b148c88
ed31f2d
e2857a2
cc8727a
02e818b
7f8a65c
aebe8cf
76908ef
ebdd7bb
e5577fe
a6587f8
1b4aaa4
f5a9f1a
8568ea7
b4a0513
9cc8d0c
e5d3a9f
eeee521
6b009f2
34fffda
ac82739
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -16,19 +16,26 @@ | |||||
|
||||||
package org.smartregister.fhircore.quest.ui.questionnaire | ||||||
|
||||||
import android.Manifest | ||||||
import android.app.Activity | ||||||
import android.app.AlertDialog | ||||||
import android.content.Intent | ||||||
import android.location.Location | ||||||
import android.os.Bundle | ||||||
import android.os.Parcelable | ||||||
import android.provider.Settings | ||||||
import android.view.View | ||||||
import android.widget.Toast | ||||||
import androidx.activity.OnBackPressedCallback | ||||||
import androidx.activity.result.ActivityResultLauncher | ||||||
import androidx.activity.viewModels | ||||||
import androidx.core.os.bundleOf | ||||||
import androidx.fragment.app.commit | ||||||
import androidx.lifecycle.lifecycleScope | ||||||
import com.google.android.fhir.datacapture.QuestionnaireFragment | ||||||
import com.google.android.fhir.logicalId | ||||||
import com.google.android.gms.location.FusedLocationProviderClient | ||||||
import com.google.android.gms.location.LocationServices | ||||||
import dagger.hilt.android.AndroidEntryPoint | ||||||
import java.io.Serializable | ||||||
import java.util.LinkedList | ||||||
|
@@ -51,6 +58,9 @@ | |||||
import org.smartregister.fhircore.engine.util.extension.showToast | ||||||
import org.smartregister.fhircore.quest.R | ||||||
import org.smartregister.fhircore.quest.databinding.QuestionnaireActivityBinding | ||||||
import org.smartregister.fhircore.quest.util.LocationUtils | ||||||
import org.smartregister.fhircore.quest.util.PermissionUtils | ||||||
import org.smartregister.fhircore.quest.util.ResourceUtils | ||||||
import timber.log.Timber | ||||||
|
||||||
@AndroidEntryPoint | ||||||
|
@@ -62,9 +72,16 @@ | |||||
private lateinit var viewBinding: QuestionnaireActivityBinding | ||||||
private var questionnaire: Questionnaire? = null | ||||||
private var alertDialog: AlertDialog? = null | ||||||
private lateinit var fusedLocationClient: FusedLocationProviderClient | ||||||
private var currLocation: Location? = null | ||||||
private lateinit var locationPermissionLauncher: ActivityResultLauncher<Array<String>> | ||||||
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent> | ||||||
|
||||||
override fun onCreate(savedInstanceState: Bundle?) { | ||||||
super.onCreate(savedInstanceState) | ||||||
|
||||||
setupLocationServices() | ||||||
Check warning on line 83 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L83
|
||||||
|
||||||
setTheme(org.smartregister.fhircore.engine.R.style.AppTheme_Questionnaire) | ||||||
viewBinding = QuestionnaireActivityBinding.inflate(layoutInflater) | ||||||
setContentView(viewBinding.root) | ||||||
|
@@ -107,6 +124,94 @@ | |||||
) | ||||||
} | ||||||
|
||||||
fun setupLocationServices() { | ||||||
if (viewModel.applicationConfiguration.logQuestionnaireLocation) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) | ||||||
Check warning on line 129 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L129
|
||||||
|
||||||
if (!LocationUtils.isLocationEnabled(this)) { | ||||||
openLocationServicesSettings() | ||||||
Check warning on line 132 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L132
|
||||||
} | ||||||
|
||||||
if (!hasLocationPermissions()) { | ||||||
launchLocationPermissionsDialog() | ||||||
Check warning on line 136 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L136
|
||||||
} | ||||||
|
||||||
if (LocationUtils.isLocationEnabled(this) && hasLocationPermissions()) { | ||||||
fetchLocation(true) | ||||||
Check warning on line 140 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L140
|
||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
fun hasLocationPermissions(): Boolean { | ||||||
return PermissionUtils.checkPermissions( | ||||||
this, | ||||||
listOf( | ||||||
Manifest.permission.ACCESS_COARSE_LOCATION, | ||||||
Manifest.permission.ACCESS_FINE_LOCATION, | ||||||
Check warning on line 150 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L146-L150
|
||||||
), | ||||||
) | ||||||
} | ||||||
|
||||||
fun openLocationServicesSettings() { | ||||||
activityResultLauncher = | ||||||
PermissionUtils.getStartActivityForResultLauncher(this) { resultCode, _ -> | ||||||
Check warning on line 157 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L156-L157
|
||||||
if (resultCode == RESULT_OK || hasLocationPermissions()) { | ||||||
fetchLocation() | ||||||
Check warning on line 159 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L159
|
||||||
} | ||||||
} | ||||||
|
||||||
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) | ||||||
showLocationSettingsDialog(intent) | ||||||
Check warning on line 164 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L163-L164
|
||||||
} | ||||||
|
||||||
private fun showLocationSettingsDialog(intent: Intent) { | ||||||
AlertDialog.Builder(this) | ||||||
.setMessage(getString(R.string.location_services_disabled)) | ||||||
.setCancelable(true) | ||||||
Check warning on line 170 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L168-L170
|
||||||
.setPositiveButton(getString(R.string.yes)) { _, _ -> activityResultLauncher.launch(intent) } | ||||||
.setNegativeButton(getString(R.string.no)) { dialog, _ -> dialog.cancel() } | ||||||
.show() | ||||||
Check warning on line 173 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L172-L173
|
||||||
} | ||||||
|
||||||
fun launchLocationPermissionsDialog() { | ||||||
locationPermissionLauncher = | ||||||
PermissionUtils.getLocationPermissionLauncher( | ||||||
this, | ||||||
onFineLocationPermissionGranted = { fetchLocation(true) }, | ||||||
onCoarseLocationPermissionGranted = { fetchLocation(false) }, | ||||||
Check warning on line 181 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L177-L181
|
||||||
onLocationPermissionDenied = { | ||||||
Toast.makeText( | ||||||
this, | ||||||
getString(R.string.location_permissions_denied), | ||||||
Toast.LENGTH_SHORT, | ||||||
Check warning on line 186 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L183-L186
|
||||||
) | ||||||
.show() | ||||||
Timber.e("Location permissions denied") | ||||||
}, | ||||||
Check warning on line 190 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L188-L190
|
||||||
) | ||||||
|
||||||
locationPermissionLauncher.launch( | ||||||
arrayOf( | ||||||
Manifest.permission.ACCESS_FINE_LOCATION, | ||||||
Manifest.permission.ACCESS_COARSE_LOCATION, | ||||||
Check warning on line 196 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L195-L196
|
||||||
), | ||||||
) | ||||||
} | ||||||
|
||||||
fun fetchLocation(highAccuracy: Boolean = true) { | ||||||
lifecycleScope.launch { | ||||||
try { | ||||||
Check warning on line 203 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L201-L203
|
||||||
if (highAccuracy) { | ||||||
currLocation = LocationUtils.getAccurateLocation(fusedLocationClient) | ||||||
DebbieArita marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} else { | ||||||
currLocation = LocationUtils.getApproximateLocation(fusedLocationClient) | ||||||
} | ||||||
} catch (e: Exception) { | ||||||
Timber.e(e, "Failed to get GPS location") | ||||||
Check warning on line 210 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L209-L210
|
||||||
DebbieArita marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
override fun onSaveInstanceState(outState: Bundle) { | ||||||
super.onSaveInstanceState(outState) | ||||||
outState.clear() | ||||||
|
@@ -238,6 +343,13 @@ | |||||
if (questionnaireResponse != null && questionnaire != null) { | ||||||
viewModel.run { | ||||||
setProgressState(QuestionnaireProgressState.ExtractionInProgress(true)) | ||||||
|
||||||
if (currLocation != null) { | ||||||
questionnaireResponse.contained.add( | ||||||
ResourceUtils.createLocationResource(gpsLocation = currLocation), | ||||||
Check warning on line 349 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L348-L349
|
||||||
) | ||||||
} | ||||||
|
||||||
handleQuestionnaireSubmission( | ||||||
questionnaire = questionnaire!!, | ||||||
currentQuestionnaireResponse = questionnaireResponse, | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* | ||
* Copyright 2021-2024 Ona Systems, Inc | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.smartregister.fhircore.quest.util | ||
|
||
import android.annotation.SuppressLint | ||
import android.content.Context | ||
import android.location.Location | ||
import android.location.LocationManager | ||
import com.google.android.gms.location.FusedLocationProviderClient | ||
import com.google.android.gms.location.Priority | ||
import com.google.android.gms.tasks.CancellationToken | ||
import com.google.android.gms.tasks.CancellationTokenSource | ||
import com.google.android.gms.tasks.OnTokenCanceledListener | ||
import kotlin.coroutines.resume | ||
import kotlin.coroutines.resumeWithException | ||
import kotlin.coroutines.suspendCoroutine | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.withContext | ||
import timber.log.Timber | ||
|
||
object LocationUtils { | ||
|
||
fun isLocationEnabled(context: Context): Boolean { | ||
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager | ||
|
||
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || | ||
locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) | ||
} | ||
|
||
@SuppressLint("MissingPermission") | ||
suspend fun getAccurateLocation( | ||
fusedLocationClient: FusedLocationProviderClient, | ||
): Location? { | ||
return withContext(Dispatchers.IO) { | ||
suspendCoroutine<Location> { continuation -> | ||
fusedLocationClient | ||
.getCurrentLocation( | ||
Priority.PRIORITY_HIGH_ACCURACY, | ||
object : CancellationToken() { | ||
Check warning on line 53 in android/quest/src/main/java/org/smartregister/fhircore/quest/util/LocationUtils.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/util/LocationUtils.kt#L50-L53
|
||
override fun onCanceledRequested(p0: OnTokenCanceledListener) = | ||
CancellationTokenSource().token | ||
|
||
override fun isCancellationRequested() = false | ||
}, | ||
) | ||
.addOnSuccessListener { location: Location? -> | ||
if (location != null) { | ||
Timber.d( | ||
"Accurate location - lat: ${location.latitude}; long: ${location.longitude}; alt: ${location.altitude}", | ||
Check warning on line 63 in android/quest/src/main/java/org/smartregister/fhircore/quest/util/LocationUtils.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/util/LocationUtils.kt#L62-L63
|
||
) | ||
continuation.resume(location) | ||
} | ||
} | ||
.addOnFailureListener { e -> | ||
Timber.e(e, "Failed to get accurate location") | ||
continuation.resumeWithException(e) | ||
Check warning on line 70 in android/quest/src/main/java/org/smartregister/fhircore/quest/util/LocationUtils.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/util/LocationUtils.kt#L68-L70
|
||
} | ||
} | ||
} | ||
} | ||
|
||
@SuppressLint("MissingPermission") | ||
suspend fun getApproximateLocation( | ||
fusedLocationClient: FusedLocationProviderClient, | ||
): Location? { | ||
return withContext(Dispatchers.IO) { | ||
suspendCoroutine<Location> { continuation -> | ||
fusedLocationClient | ||
.getCurrentLocation( | ||
Priority.PRIORITY_BALANCED_POWER_ACCURACY, | ||
object : CancellationToken() { | ||
Check warning on line 85 in android/quest/src/main/java/org/smartregister/fhircore/quest/util/LocationUtils.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/util/LocationUtils.kt#L82-L85
|
||
override fun onCanceledRequested(p0: OnTokenCanceledListener) = | ||
CancellationTokenSource().token | ||
|
||
override fun isCancellationRequested() = false | ||
}, | ||
) | ||
.addOnSuccessListener { location: Location? -> | ||
if (location != null) { | ||
Timber.d( | ||
"Approx location - lat: ${location.latitude}; long: ${location.longitude}; alt: ${location.altitude}", | ||
Check warning on line 95 in android/quest/src/main/java/org/smartregister/fhircore/quest/util/LocationUtils.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/util/LocationUtils.kt#L94-L95
|
||
qiarie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
continuation.resume(location) | ||
} | ||
} | ||
.addOnFailureListener { e -> continuation.resumeWithException(e) } | ||
Check warning on line 100 in android/quest/src/main/java/org/smartregister/fhircore/quest/util/LocationUtils.kt Codecov / codecov/patchandroid/quest/src/main/java/org/smartregister/fhircore/quest/util/LocationUtils.kt#L100
|
||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we know that we will (at some point) want to log location at multiple points, can we change this data structure to reflect that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pld Should we move the setting to
QuestionnaireConfig
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is appropriate here, because in the future we plan to do location logging outside of the Questionnaire flow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pld Did you have another use case in mind?
In cases where we would need to log the location in multiple places, I'd go with a location config with all options explicitly listed as below:
Usage:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pld Using reflection will not be possible in the application configuration. Kotlin Serialization will be unable to deserialize the type. We could opt for a list of enums instead. Every place that needs location logging will be identified by an enum type.
Example
ApplicationConfiguration
Suggest an appropriate name for the property/class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK yea if we can't use class names, and maybe that won't map to the semantic anyhow, that's fine, enum seems straightforward.
The other use case I had in mind is that we might want to log the location every 5 minutes. Or if you could complete a task by clicking a button in the profile, we might want to log the location on click
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pld This is noted. We had discussed with @marklosh the need to have a periodic logger for their distance from current location use case since picking the location takes time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have created this issue for the addition of enums to configure location logging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok let's make the change in this PR before we merge it