-
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 8 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,33 @@ | |
|
||
package org.smartregister.fhircore.quest.ui.questionnaire | ||
|
||
import android.Manifest | ||
import android.annotation.SuppressLint | ||
import android.app.Activity | ||
import android.app.AlertDialog | ||
import android.content.Intent | ||
import android.content.pm.PackageManager | ||
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.contract.ActivityResultContracts | ||
import androidx.activity.viewModels | ||
import androidx.core.app.ActivityCompat | ||
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 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 dagger.hilt.android.AndroidEntryPoint | ||
import java.io.Serializable | ||
import java.util.LinkedList | ||
|
@@ -62,8 +76,19 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() { | |
private var questionnaire: Questionnaire? = null | ||
private var alertDialog: AlertDialog? = null | ||
|
||
private lateinit var fusedLocationClient: FusedLocationProviderClient | ||
private val loc = org.hl7.fhir.r4.model.Location() | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
|
||
val applicationConfiguration = viewModel.applicationConfiguration | ||
|
||
if (applicationConfiguration.logQuestionnaireLocation) { | ||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) | ||
getLocation() | ||
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. is this running in the foreground or background? If there is not a GPS fix, what happens? 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. @pld There are some updates to the flow. The GPS location request is made in the background when a questionnaire is launched. This is done after config, permissions and location services checks are complete. On submit, the GPS location - if available - is used to create the location resource. |
||
} | ||
|
||
setTheme(R.style.AppTheme_Questionnaire) | ||
viewBinding = QuestionnaireActivityBinding.inflate(layoutInflater) | ||
setContentView(viewBinding.root) | ||
|
@@ -231,6 +256,9 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() { | |
if (questionnaireResponse != null && questionnaire != null) { | ||
viewModel.run { | ||
setProgressState(QuestionnaireProgressState.ExtractionInProgress(true)) | ||
|
||
questionnaireResponse.contained.add(loc) | ||
|
||
handleQuestionnaireSubmission( | ||
questionnaire = questionnaire!!, | ||
currentQuestionnaireResponse = questionnaireResponse, | ||
|
@@ -251,11 +279,112 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() { | |
) | ||
finish() | ||
} | ||
// do background location processing | ||
// check here that "logQuestionnaireLocation": true | ||
DebbieArita marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} | ||
} | ||
|
||
private fun getLocation() { | ||
DebbieArita marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if ( | ||
ActivityCompat.checkSelfPermission( | ||
this@QuestionnaireActivity, | ||
Manifest.permission.ACCESS_FINE_LOCATION, | ||
) != PackageManager.PERMISSION_GRANTED && | ||
ActivityCompat.checkSelfPermission( | ||
this@QuestionnaireActivity, | ||
Manifest.permission.ACCESS_COARSE_LOCATION, | ||
) != PackageManager.PERMISSION_GRANTED | ||
) { | ||
val locationPermissionRequest = | ||
[email protected]( | ||
ActivityResultContracts.RequestMultiplePermissions(), | ||
) { permissions -> | ||
when { | ||
permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> { | ||
getCurrentLocation() | ||
} | ||
permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> { | ||
// request approx permissions | ||
getCurrentApproximateLocation() | ||
} | ||
else -> { | ||
// No location access granted. Notify and fail | ||
Toast.makeText( | ||
this, | ||
"Location Access Permission not granted", | ||
Toast.LENGTH_LONG, | ||
) | ||
.show() | ||
} | ||
} | ||
} | ||
locationPermissionRequest.launch( | ||
arrayOf( | ||
Manifest.permission.ACCESS_FINE_LOCATION, | ||
Manifest.permission.ACCESS_COARSE_LOCATION, | ||
), | ||
) | ||
} else { | ||
getCurrentLocation() | ||
} | ||
} | ||
|
||
@SuppressLint("MissingPermission") | ||
private fun getCurrentLocation() { | ||
fusedLocationClient | ||
.getCurrentLocation( | ||
Priority.PRIORITY_HIGH_ACCURACY, | ||
object : CancellationToken() { | ||
override fun onCanceledRequested(p0: OnTokenCanceledListener) = | ||
CancellationTokenSource().token | ||
|
||
override fun isCancellationRequested() = false | ||
}, | ||
) | ||
.addOnSuccessListener { location: Location? -> | ||
if (location != null) { | ||
Timber.d("current location${location.latitude} ${location.longitude}") | ||
loc.position.altitude = location.altitude.toBigDecimal() | ||
loc.position.latitude = location.latitude.toBigDecimal() | ||
loc.position.longitude = location.longitude.toBigDecimal() | ||
} | ||
} | ||
.addOnFailureListener { | ||
// Location services are not enabled, prompt the user to enable them | ||
val settingsIntent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) | ||
this.startActivity(settingsIntent) | ||
} | ||
} | ||
|
||
@SuppressLint("MissingPermission") | ||
private fun getCurrentApproximateLocation() { | ||
fusedLocationClient | ||
.getCurrentLocation( | ||
Priority.PRIORITY_BALANCED_POWER_ACCURACY, | ||
object : CancellationToken() { | ||
override fun onCanceledRequested(p0: OnTokenCanceledListener) = | ||
CancellationTokenSource().token | ||
|
||
override fun isCancellationRequested() = false | ||
}, | ||
) | ||
.addOnSuccessListener { approxLocation: Location? -> | ||
if (approxLocation != null) { | ||
Timber.d("current location${approxLocation.latitude} ${approxLocation.longitude}") | ||
loc.position.altitude = approxLocation.altitude.toBigDecimal() | ||
loc.position.latitude = approxLocation.latitude.toBigDecimal() | ||
loc.position.longitude = approxLocation.longitude.toBigDecimal() | ||
} | ||
} | ||
.addOnFailureListener { | ||
// Location services are not enabled, prompt the user to enable them | ||
val settingsIntent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) | ||
this.startActivity(settingsIntent) | ||
} | ||
} | ||
|
||
private fun handleBackPress() { | ||
if (questionnaireConfig.isReadOnly()) { | ||
finish() | ||
|
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