Skip to content
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

Fix app crash on app launch with location permissions request #3478

Merged
merged 2 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ 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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,63 +18,48 @@

import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat

object PermissionUtils {

fun checkPermissions(context: Context, permissions: List<String>): Boolean {
for (permission in permissions) {
if (
ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED
) {
return false
}
fun checkPermissions(context: Context, permissions: List<String>): Boolean =
permissions.none {

Check warning on line 27 in android/engine/src/main/java/org/smartregister/fhircore/engine/util/location/PermissionUtils.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/util/location/PermissionUtils.kt#L27

Added line #L27 was not covered by tests
ContextCompat.checkSelfPermission(
context,
it,

Check warning on line 30 in android/engine/src/main/java/org/smartregister/fhircore/engine/util/location/PermissionUtils.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/util/location/PermissionUtils.kt#L29-L30

Added lines #L29 - L30 were not covered by tests
) != PackageManager.PERMISSION_GRANTED
}
return true
}

fun getLocationPermissionLauncher(
activity: AppCompatActivity,
permissions: Map<String, Boolean>,
onFineLocationPermissionGranted: () -> Unit,
onCoarseLocationPermissionGranted: () -> Unit,
onLocationPermissionDenied: () -> Unit,
): ActivityResultLauncher<Array<String>> {
return activity.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions(),
) { permissions ->
if (permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true) {
) {
when {

Check warning on line 40 in android/engine/src/main/java/org/smartregister/fhircore/engine/util/location/PermissionUtils.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/util/location/PermissionUtils.kt#L40

Added line #L40 was not covered by tests
permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true ->
onFineLocationPermissionGranted()
} else if (permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true) {
permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true ->
onCoarseLocationPermissionGranted()
} else {
onLocationPermissionDenied()
}
}
}

fun getStartActivityForResultLauncher(
activity: AppCompatActivity,
onResult: (resultCode: Int, data: Intent?) -> Unit,
): ActivityResultLauncher<Intent> {
return activity.registerForActivityResult(
ActivityResultContracts.StartActivityForResult(),
) { result ->
onResult(result.resultCode, result.data)
else -> onLocationPermissionDenied()

Check warning on line 45 in android/engine/src/main/java/org/smartregister/fhircore/engine/util/location/PermissionUtils.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/util/location/PermissionUtils.kt#L45

Added line #L45 was not covered by tests
}
}

fun hasFineLocationPermissions(context: Context): Boolean {
return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
fun hasFineLocationPermissions(context: Context): Boolean =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
}

fun hasCoarseLocationPermissions(context: Context): Boolean {
return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) ==
fun hasCoarseLocationPermissions(context: Context): Boolean =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
}

fun hasLocationPermissions(context: Context) =
checkPermissions(
context,
listOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,

Check warning on line 62 in android/engine/src/main/java/org/smartregister/fhircore/engine/util/location/PermissionUtils.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/util/location/PermissionUtils.kt#L58-L62

Added lines #L58 - L62 were not covered by tests
),
)

Check warning on line 64 in android/engine/src/main/java/org/smartregister/fhircore/engine/util/location/PermissionUtils.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/util/location/PermissionUtils.kt#L64

Added line #L64 was not covered by tests
}
1 change: 1 addition & 0 deletions android/engine/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
<string name="weeks">Week(s)</string>
<string name="days">Days(s)</string>
<string name="initializing">Initializing settings &#8230;</string>
<string name="initializing_application">Initializing application &#8230;</string>
<string name="username_sample" translatable="false">e.g JohnDoe</string>
<string name="percentage_progress" translatable="false">%1$d%%</string>
<string name="error_occurred">Something went wrong…</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.getValue
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
Expand All @@ -40,6 +39,7 @@
import io.sentry.android.navigation.SentryNavigationListener
import java.time.Instant
import javax.inject.Inject
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.hl7.fhir.r4.model.IdType
Expand All @@ -62,6 +62,8 @@
import org.smartregister.fhircore.quest.event.AppEvent
import org.smartregister.fhircore.quest.event.EventBus
import org.smartregister.fhircore.quest.ui.questionnaire.QuestionnaireActivity
import org.smartregister.fhircore.quest.ui.shared.ActivityOnResultType
import org.smartregister.fhircore.quest.ui.shared.ON_RESULT_TYPE
import org.smartregister.fhircore.quest.ui.shared.QuestionnaireHandler
import org.smartregister.fhircore.quest.ui.shared.models.QuestionnaireSubmission
import timber.log.Timber
Expand All @@ -81,13 +83,34 @@
val appMainViewModel by viewModels<AppMainViewModel>()
private val sentryNavListener =
SentryNavigationListener(enableNavigationBreadcrumbs = true, enableNavigationTracing = true)
private lateinit var locationPermissionLauncher: ActivityResultLauncher<Array<String>>
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>

private val locationPermissionLauncher: ActivityResultLauncher<Array<String>> =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {

Check warning on line 88 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L88

Added line #L88 was not covered by tests
permissions: Map<String, Boolean> ->
PermissionUtils.getLocationPermissionLauncher(
permissions = permissions,
onFineLocationPermissionGranted = { fetchLocation() },
onCoarseLocationPermissionGranted = { fetchLocation() },

Check warning on line 93 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L90-L93

Added lines #L90 - L93 were not covered by tests
onLocationPermissionDenied = {
showToast(
getString(R.string.location_permissions_denied),
Toast.LENGTH_SHORT,

Check warning on line 97 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L95-L97

Added lines #L95 - L97 were not covered by tests
)
},

Check warning on line 99 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L99

Added line #L99 was not covered by tests
)
}

private lateinit var fusedLocationClient: FusedLocationProviderClient

override val startForResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {

Check warning on line 106 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L106

Added line #L106 was not covered by tests
activityResult: ActivityResult ->
val onResultType = activityResult.data?.extras?.getString(ON_RESULT_TYPE)
if (
activityResult.resultCode == Activity.RESULT_OK &&
!onResultType.isNullOrBlank() &&
ActivityOnResultType.valueOf(onResultType) == ActivityOnResultType.QUESTIONNAIRE
) {
lifecycleScope.launch { onSubmitQuestionnaire(activityResult) }
}
}
Expand Down Expand Up @@ -127,6 +150,7 @@
retrieveAppMainUiState()
withContext(dispatcherProvider.io()) { schedulePeriodicJobs(this@AppMainActivity) }
}

setupLocationServices()

findViewById<View>(R.id.mainScreenProgressBar).apply { visibility = View.GONE }
Expand Down Expand Up @@ -188,92 +212,61 @@
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

if (!LocationUtils.isLocationEnabled(this)) {
openLocationServicesSettings()
showLocationSettingsDialog(
Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS).apply {
putExtra(ON_RESULT_TYPE, ActivityOnResultType.LOCATION.name)
},

Check warning on line 218 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L215-L218

Added lines #L215 - L218 were not covered by tests
)
}

if (!hasLocationPermissions()) {
launchLocationPermissionsDialog()
if (!PermissionUtils.hasLocationPermissions(this)) {
locationPermissionLauncher.launch(

Check warning on line 223 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L223

Added line #L223 was not covered by tests
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,

Check warning on line 226 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L225-L226

Added lines #L225 - L226 were not covered by tests
),
)
}

if (LocationUtils.isLocationEnabled(this) && hasLocationPermissions()) {
if (LocationUtils.isLocationEnabled(this) && PermissionUtils.hasLocationPermissions(this)) {
fetchLocation()
}
}
}

fun hasLocationPermissions(): Boolean {
return PermissionUtils.checkPermissions(
this,
listOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
),
)
}

private fun openLocationServicesSettings() {
activityResultLauncher =
PermissionUtils.getStartActivityForResultLauncher(this) { resultCode, _ ->
if (resultCode == RESULT_OK || hasLocationPermissions()) {
Timber.d("Location or permissions successfully enabled")
}
}

val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
showLocationSettingsDialog(intent)
}

private fun showLocationSettingsDialog(intent: Intent) {
AlertDialog.Builder(this)
.setMessage(getString(R.string.location_services_disabled))
.setCancelable(true)
.setPositiveButton(getString(R.string.yes)) { _, _ -> activityResultLauncher.launch(intent) }
.setPositiveButton(getString(R.string.yes)) { _, _ -> startForResult.launch(intent) }

Check warning on line 241 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L241

Added line #L241 was not covered by tests
.setNegativeButton(getString(R.string.no)) { dialog, _ -> dialog.cancel() }
.show()
}

fun launchLocationPermissionsDialog() {
locationPermissionLauncher =
PermissionUtils.getLocationPermissionLauncher(
this,
onFineLocationPermissionGranted = { fetchLocation() },
onCoarseLocationPermissionGranted = { fetchLocation() },
onLocationPermissionDenied = {
Toast.makeText(
this,
getString(R.string.location_permissions_denied),
Toast.LENGTH_SHORT,
)
.show()
},
)

locationPermissionLauncher.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
),
)
}

private fun fetchLocation() {
val context = this
lifecycleScope.launch {
val retrievedLocation =
if (PermissionUtils.hasFineLocationPermissions(context)) {
LocationUtils.getAccurateLocation(fusedLocationClient)
} else if (PermissionUtils.hasCoarseLocationPermissions(context)) {
LocationUtils.getApproximateLocation(fusedLocationClient)
} else {
null
}
retrievedLocation?.let {
protoDataStore.writeLocationCoordinates(
LocationCoordinate(it.latitude, it.longitude, it.altitude, Instant.now()),
)
}
async(dispatcherProvider.io()) {
when {

Check warning on line 251 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L250-L251

Added lines #L250 - L251 were not covered by tests
PermissionUtils.hasFineLocationPermissions(context) ->
LocationUtils.getAccurateLocation(fusedLocationClient)
PermissionUtils.hasCoarseLocationPermissions(context) ->
LocationUtils.getApproximateLocation(fusedLocationClient)
else -> null

Check warning on line 256 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L256

Added line #L256 was not covered by tests
}
}
.await()

Check warning on line 259 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L259

Added line #L259 was not covered by tests
?.also {
protoDataStore.writeLocationCoordinates(
LocationCoordinate(it.latitude, it.longitude, it.altitude, Instant.now()),

Check warning on line 262 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L261-L262

Added lines #L261 - L262 were not covered by tests
)
}

if (retrievedLocation == null) {
this@AppMainActivity.showToast("Failed to get GPS location", Toast.LENGTH_LONG)
withContext(dispatcherProvider.main()) {
showToast(getString(R.string.failed_to_get_gps_location), Toast.LENGTH_LONG)

Check warning on line 268 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt#L268

Added line #L268 was not covered by tests
}
}
}
}
Expand Down
Loading
Loading