Skip to content

Commit 780ff1f

Browse files
authored
Refactor permission grant to separate methods / classes (#337)
* refactored permission grant to separate methods / classes * more refactoring * add first test * add more tests * more test * cleanup * wip * do not require permission for manual location in background * add more tests * reformat * cleanup tests * cleanup * fix tests * add ingore text
1 parent 1a6f810 commit 780ff1f

13 files changed

+935
-201
lines changed

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ android {
1111
defaultConfig {
1212
applicationId "org.blitzortung.android.app"
1313
minSdkVersion 21
14-
targetSdkVersion 35
14+
targetSdk 35
1515
versionCode 342
1616
versionName '2.4.1'
1717
multiDexEnabled false

app/src/main/java/org/blitzortung/android/app/Main.kt

Lines changed: 12 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,13 @@
1818

1919
package org.blitzortung.android.app
2020

21-
import android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
22-
import android.Manifest.permission.ACCESS_COARSE_LOCATION
23-
import android.Manifest.permission.ACCESS_FINE_LOCATION
24-
import android.Manifest.permission.POST_NOTIFICATIONS
25-
import android.Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
26-
import android.content.Context
27-
import android.content.DialogInterface
2821
import android.content.Intent
2922
import android.content.SharedPreferences
3023
import android.content.pm.PackageManager
3124
import android.graphics.Color
32-
import android.location.LocationManager.GPS_PROVIDER
33-
import android.location.LocationManager.NETWORK_PROVIDER
34-
import android.location.LocationManager.PASSIVE_PROVIDER
3525
import android.os.Build
3626
import android.os.Bundle
37-
import android.os.PowerManager
38-
import android.provider.Settings
3927
import android.text.format.DateFormat
40-
import android.util.AndroidRuntimeException
4128
import android.util.Log
4229
import android.view.KeyEvent
4330
import android.view.View
@@ -47,10 +34,7 @@ import android.widget.ImageButton
4734
import android.widget.SeekBar
4835
import android.widget.SeekBar.OnSeekBarChangeListener
4936
import android.widget.Toast
50-
import androidx.annotation.RequiresApi
51-
import androidx.appcompat.app.AlertDialog
5237
import androidx.core.content.edit
53-
import androidx.core.net.toUri
5438
import androidx.core.view.isVisible
5539
import androidx.core.view.WindowCompat // Import added
5640
import androidx.fragment.app.FragmentActivity
@@ -64,6 +48,12 @@ import org.blitzortung.android.app.components.VersionComponent
6448
import org.blitzortung.android.app.controller.ButtonColumnHandler
6549
import org.blitzortung.android.app.controller.HistoryController
6650
import org.blitzortung.android.app.databinding.MainBinding
51+
import org.blitzortung.android.app.permission.LocationProviderRelation
52+
import org.blitzortung.android.app.permission.PermissionsSupport
53+
import org.blitzortung.android.app.permission.requester.BackgroundLocationPermissionRequester
54+
import org.blitzortung.android.app.permission.requester.LocationPermissionRequester
55+
import org.blitzortung.android.app.permission.requester.NotificationPermissionRequester
56+
import org.blitzortung.android.app.permission.requester.WakeupPermissionRequester
6757
import org.blitzortung.android.app.view.OnSharedPreferenceChangeListener
6858
import org.blitzortung.android.app.view.PreferenceKey
6959
import org.blitzortung.android.app.view.components.StatusComponent
@@ -459,19 +449,12 @@ class Main : FragmentActivity(), OnSharedPreferenceChangeListener {
459449
Log.v(LOG_TAG, "Main.onResume()")
460450

461451
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
462-
val permissionRequests: List<Pair<String, () -> Boolean>> = listOf(
463-
Pair("location", { requestLocationPermissions(preferences) }),
464-
Pair("notification", { requestNotificationPermissions() }),
465-
Pair("background_location", { requestBackgroundLocationPermissions() }),
466-
Pair("wakeup", { requestWakeupPermissions(baseContext) }),
467-
)
468-
for (pair in permissionRequests) {
469-
val result = pair.second()
470-
Log.v(LOG_TAG, "Main.onResume() permission ${pair.first}: result: $result")
471-
if (result) {
472-
break
473-
}
474-
}
452+
PermissionsSupport.ensurePermissions(this,
453+
LocationPermissionRequester(preferences),
454+
NotificationPermissionRequester(),
455+
BackgroundLocationPermissionRequester(this, preferences),
456+
WakeupPermissionRequester(this, preferences)
457+
)
475458
}
476459

477460
mapFragment.updateForgroundColor(strikeColorHandler.lineColor)
@@ -628,177 +611,6 @@ class Main : FragmentActivity(), OnSharedPreferenceChangeListener {
628611
}
629612
}
630613

631-
private fun requestNotificationPermissions(): Boolean {
632-
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
633-
requestPermission(POST_NOTIFICATIONS, REQUEST_CODE_POST_NOTIFICATIONS, R.string.post_notifications_request)
634-
} else {
635-
false
636-
}
637-
}
638-
639-
@RequiresApi(Build.VERSION_CODES.M)
640-
private fun requestLocationPermissions(sharedPreferences: SharedPreferences): Boolean {
641-
val (permission, requestCode) = getLocationPermission(sharedPreferences)
642-
643-
return if (permission != null) {
644-
requestPermission(
645-
permission, requestCode, R.string.location_permission_required
646-
)
647-
} else {
648-
false
649-
}
650-
}
651-
652-
@RequiresApi(Build.VERSION_CODES.M)
653-
private fun requestBackgroundLocationPermissions(): Boolean {
654-
return if (isAtLeast(Build.VERSION_CODES.Q) && backgroundAlertEnabled && checkSelfPermission(
655-
ACCESS_BACKGROUND_LOCATION
656-
) != PackageManager.PERMISSION_GRANTED
657-
) {
658-
Log.v(LOG_TAG, "Main.requestLocationPermissions() open background permission dialog")
659-
val locationText = this.resources.getString(R.string.location_permission_background_disclosure)
660-
AlertDialog.Builder(this).setMessage(locationText).setCancelable(false)
661-
.setPositiveButton(android.R.string.ok) { dialog, _ ->
662-
dialog.dismiss()
663-
requestPermission(
664-
ACCESS_BACKGROUND_LOCATION,
665-
REQUEST_CODE_BACKGROUND_LOCATION,
666-
R.string.location_permission_background_required
667-
)
668-
}.setNegativeButton(android.R.string.cancel) { _, _ ->
669-
preferences.edit { put(PreferenceKey.BACKGROUND_QUERY_PERIOD, "0") }
670-
}.show()
671-
true
672-
} else {
673-
false
674-
}
675-
}
676-
677-
private fun getLocationPermission(sharedPreferences: SharedPreferences): Pair<String?, Int> {
678-
val locationProviderName = sharedPreferences.get(PreferenceKey.LOCATION_MODE, PASSIVE_PROVIDER)
679-
val permission = when (locationProviderName) {
680-
PASSIVE_PROVIDER, GPS_PROVIDER -> ACCESS_FINE_LOCATION
681-
NETWORK_PROVIDER -> ACCESS_COARSE_LOCATION
682-
else -> null
683-
}
684-
val requestCode = (LocationProviderRelation.byProviderName[locationProviderName]?.ordinal ?: Int.MAX_VALUE)
685-
return Pair(permission, requestCode)
686-
}
687-
688-
689-
@RequiresApi(Build.VERSION_CODES.M)
690-
private fun requestPermission(permission: String, requestCode: Int, permissionRequiredStringId: Int): Boolean {
691-
val shouldShowPermissionRationale = shouldShowRequestPermissionRationale(permission)
692-
val permissionIsGranted = checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
693-
Log.v(
694-
LOG_TAG,
695-
"Main.requestPermission() permission: $permission, requestCode: $requestCode, isGranted: $permissionIsGranted, shouldShowRationale: ${!shouldShowPermissionRationale}"
696-
)
697-
698-
return if (!permissionIsGranted) {
699-
if (shouldShowPermissionRationale) {
700-
requestPermissionsAfterDialog(permissionRequiredStringId, permission, requestCode)
701-
} else {
702-
requestPermissions(arrayOf(permission), requestCode)
703-
}
704-
true
705-
} else {
706-
false
707-
}
708-
}
709-
710-
@RequiresApi(Build.VERSION_CODES.M)
711-
private fun requestPermissionsAfterDialog(
712-
dialogTextResource: Int,
713-
permission: String,
714-
requestCode: Int,
715-
) {
716-
Log.v(
717-
LOG_TAG,
718-
"Main.requestPermissionsAfterDialog() permission: $permission, dialogResource: $dialogTextResource, requestCode: $requestCode"
719-
)
720-
721-
val locationText = resources.getString(dialogTextResource)
722-
AlertDialog.Builder(this).setMessage(locationText).setCancelable(false)
723-
.setPositiveButton(android.R.string.ok) { dialog, count ->
724-
dialog.dismiss()
725-
requestPermissions(arrayOf(permission), requestCode)
726-
}.show()
727-
}
728-
729-
@RequiresApi(Build.VERSION_CODES.M)
730-
private fun requestWakeupPermissions(context: Context): Boolean {
731-
Log.v(LOG_TAG, "requestWakeupPermissions() background alerts: $backgroundAlertEnabled")
732-
733-
if (backgroundAlertEnabled) {
734-
val pm = context.getSystemService(POWER_SERVICE)
735-
if (pm is PowerManager) {
736-
val packageName = context.packageName
737-
Log.v(LOG_TAG, "requestWakeupPermissions() package name $packageName")
738-
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
739-
val locationText = context.resources.getString(R.string.open_battery_optimiziation)
740-
741-
val dialogClickListener = DialogInterface.OnClickListener { dialog, which ->
742-
dialog.dismiss()
743-
when (which) {
744-
DialogInterface.BUTTON_POSITIVE -> {
745-
Log.v(LOG_TAG, "requestWakeupPermissions() request ignore battery optimizations")
746-
val allowIgnoreBatteryOptimization =
747-
context.checkSelfPermission(REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) == PackageManager.PERMISSION_GRANTED
748-
val intent = if (allowIgnoreBatteryOptimization) {
749-
Intent(
750-
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
751-
"package:$packageName".toUri()
752-
)
753-
} else {
754-
Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
755-
}
756-
757-
try {
758-
startActivity(intent)
759-
} catch (e: AndroidRuntimeException) {
760-
Toast.makeText(baseContext, R.string.background_query_toast, Toast.LENGTH_LONG)
761-
.show()
762-
Log.e(
763-
LOG_TAG,
764-
"requestWakeupPermissions() could not open battery optimization settings",
765-
e
766-
)
767-
}
768-
}
769-
770-
DialogInterface.BUTTON_NEGATIVE -> {
771-
preferences.edit().apply {
772-
putString(PreferenceKey.BACKGROUND_QUERY_PERIOD.toString(), 0.toString())
773-
apply()
774-
}
775-
}
776-
}
777-
}
778-
779-
AlertDialog.Builder(this).setMessage(locationText)
780-
.setPositiveButton(android.R.string.ok, dialogClickListener)
781-
.setNegativeButton(android.R.string.cancel, dialogClickListener).show()
782-
return true
783-
}
784-
} else {
785-
Log.w(LOG_TAG, "requestWakeupPermissions() could not get PowerManager")
786-
}
787-
}
788-
return false
789-
}
790-
791-
private enum class LocationProviderRelation(val providerName: String) {
792-
GPS(GPS_PROVIDER), PASSIVE(PASSIVE_PROVIDER), NETWORK(NETWORK_PROVIDER);
793-
794-
companion object {
795-
val byProviderName: Map<String, LocationProviderRelation> =
796-
entries.groupBy { it.providerName }.mapValues { it.value.first() }
797-
val byOrdinal: Map<Int, LocationProviderRelation> =
798-
entries.groupBy { it.ordinal }.mapValues { it.value.first() }
799-
}
800-
}
801-
802614
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
803615
if (keyCode == KeyEvent.KEYCODE_MENU) {
804616
Log.v(LOG_TAG, "Main.onKeyUp(KEYCODE_MENU)")
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.blitzortung.android.app.permission
2+
3+
import android.location.LocationManager
4+
5+
enum class LocationProviderRelation(val providerName: String) {
6+
GPS(LocationManager.GPS_PROVIDER), PASSIVE(LocationManager.PASSIVE_PROVIDER), NETWORK(LocationManager.NETWORK_PROVIDER);
7+
8+
companion object {
9+
val byProviderName: Map<String, LocationProviderRelation> =
10+
entries.groupBy { it.providerName }.mapValues { it.value.first() }
11+
val byOrdinal: Map<Int, LocationProviderRelation> =
12+
entries.groupBy { it.ordinal }.mapValues { it.value.first() }
13+
}
14+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.blitzortung.android.app.permission
2+
3+
import android.app.Activity
4+
import android.content.pm.PackageManager
5+
import android.os.Build
6+
import android.util.Log
7+
import androidx.annotation.RequiresApi
8+
import androidx.appcompat.app.AlertDialog
9+
import org.blitzortung.android.app.Main.Companion.LOG_TAG
10+
11+
class PermissionsSupport(
12+
private val activity: Activity
13+
) {
14+
15+
@RequiresApi(Build.VERSION_CODES.M)
16+
fun requestPermission(permission: String, requestCode: Int, permissionRequiredStringId: Int): Boolean {
17+
val shouldShowPermissionRationale = activity.shouldShowRequestPermissionRationale(permission)
18+
val permissionIsGranted = activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
19+
Log.v(
20+
LOG_TAG,
21+
"Main.requestPermission() permission: $permission, requestCode: $requestCode, isGranted: $permissionIsGranted, shouldShowRationale: ${!shouldShowPermissionRationale}"
22+
)
23+
24+
return if (!permissionIsGranted) {
25+
if (shouldShowPermissionRationale) {
26+
requestPermissionsAfterDialog(permissionRequiredStringId, permission, requestCode)
27+
} else {
28+
activity.requestPermissions(arrayOf(permission), requestCode)
29+
}
30+
true
31+
} else {
32+
false
33+
}
34+
}
35+
36+
@RequiresApi(Build.VERSION_CODES.M)
37+
private fun requestPermissionsAfterDialog(
38+
dialogTextResource: Int,
39+
permission: String,
40+
requestCode: Int,
41+
) {
42+
Log.v(
43+
LOG_TAG,
44+
"Main.requestPermissionsAfterDialog() permission: $permission, dialogResource: $dialogTextResource, requestCode: $requestCode"
45+
)
46+
47+
val message = activity.resources.getString(dialogTextResource)
48+
AlertDialog.Builder(activity).setMessage(message).setCancelable(false)
49+
.setPositiveButton(android.R.string.ok) { _, _ ->
50+
activity.requestPermissions(arrayOf(permission), requestCode)
51+
}.show()
52+
}
53+
54+
companion object {
55+
@RequiresApi(Build.VERSION_CODES.M)
56+
fun ensurePermissions(activity: Activity, vararg permissionRequesters : PermissionRequester) {
57+
58+
val permissionsSupport = PermissionsSupport(activity)
59+
60+
for (permissionRequester in permissionRequesters) {
61+
val result = permissionRequester.request(permissionsSupport)
62+
Log.v(LOG_TAG, "Main.onResume() permission ${permissionRequester.name}: result: $result")
63+
if (result) {
64+
break
65+
}
66+
}
67+
}
68+
}
69+
}
70+
71+
interface PermissionRequester {
72+
val name: String
73+
fun request(permissionsSupport: PermissionsSupport): Boolean
74+
}

0 commit comments

Comments
 (0)