Skip to content

Commit 292e92a

Browse files
authored
Better handling of denied location permissions (#344)
* update Android plugins * disable alerts without location permission and no manual location set * refactor permission request received snippets * fix leftover import * add tests for Notification permission callback * add tests for location permission callback * use Robolectric for permission testing * add missing translations * add missing test * add more missing strings * update translations
1 parent 21e3e57 commit 292e92a

File tree

24 files changed

+485
-117
lines changed

24 files changed

+485
-117
lines changed

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ android {
1212
applicationId "org.blitzortung.android.app"
1313
minSdkVersion 21
1414
targetSdkVersion 35
15-
versionCode 344
16-
versionName '2.4.3'
15+
versionCode 345
16+
versionName '2.4.4'
1717
multiDexEnabled false
1818
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
1919
}

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

Lines changed: 23 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ package org.blitzortung.android.app
2020

2121
import android.content.Intent
2222
import android.content.SharedPreferences
23-
import android.content.pm.PackageManager
2423
import android.graphics.Color
2524
import android.os.Build
2625
import android.os.Bundle
@@ -35,8 +34,8 @@ import android.widget.SeekBar
3534
import android.widget.SeekBar.OnSeekBarChangeListener
3635
import android.widget.Toast
3736
import androidx.core.content.edit
37+
import androidx.core.view.WindowCompat
3838
import androidx.core.view.isVisible
39-
import androidx.core.view.WindowCompat // Import added
4039
import androidx.fragment.app.FragmentActivity
4140
import androidx.preference.PreferenceManager
4241
import dagger.android.AndroidInjection
@@ -48,7 +47,7 @@ import org.blitzortung.android.app.components.VersionComponent
4847
import org.blitzortung.android.app.controller.ButtonColumnHandler
4948
import org.blitzortung.android.app.controller.HistoryController
5049
import org.blitzortung.android.app.databinding.MainBinding
51-
import org.blitzortung.android.app.permission.LocationProviderRelation
50+
import org.blitzortung.android.app.permission.PermissionRequester
5251
import org.blitzortung.android.app.permission.PermissionsSupport
5352
import org.blitzortung.android.app.permission.requester.BackgroundLocationPermissionRequester
5453
import org.blitzortung.android.app.permission.requester.LocationPermissionRequester
@@ -58,7 +57,6 @@ import org.blitzortung.android.app.view.OnSharedPreferenceChangeListener
5857
import org.blitzortung.android.app.view.PreferenceKey
5958
import org.blitzortung.android.app.view.components.StatusComponent
6059
import org.blitzortung.android.app.view.get
61-
import org.blitzortung.android.app.view.put
6260
import org.blitzortung.android.data.AUTO_GRID_SIZE_VALUE
6361
import org.blitzortung.android.data.MainDataHandler
6462
import org.blitzortung.android.data.Mode
@@ -125,6 +123,8 @@ class Main : FragmentActivity(), OnSharedPreferenceChangeListener {
125123
@set:Inject
126124
internal lateinit var changeLogComponent: ChangeLogComponent
127125

126+
private lateinit var permissionRequesters: Array<PermissionRequester>
127+
128128
private var currentResult: ResultEvent? = null
129129

130130
private val keepZoomOnGotoOwnLocation: Boolean
@@ -277,6 +277,13 @@ class Main : FragmentActivity(), OnSharedPreferenceChangeListener {
277277
override fun onStopTrackingTouch(p0: SeekBar?) {
278278
}
279279
})
280+
281+
permissionRequesters = arrayOf(
282+
LocationPermissionRequester(this, preferences),
283+
NotificationPermissionRequester(this, preferences),
284+
BackgroundLocationPermissionRequester(this, preferences),
285+
WakeupPermissionRequester(this, preferences)
286+
)
280287
}
281288

282289
private fun initializeOsmDroid() {
@@ -441,6 +448,7 @@ class Main : FragmentActivity(), OnSharedPreferenceChangeListener {
441448
Log.d(LOG_TAG, "Main.onRestart()")
442449
}
443450

451+
444452
override fun onResume() {
445453
super.onResume()
446454

@@ -449,12 +457,9 @@ class Main : FragmentActivity(), OnSharedPreferenceChangeListener {
449457
Log.v(LOG_TAG, "Main.onResume()")
450458

451459
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
452-
PermissionsSupport.ensure(this,
453-
LocationPermissionRequester(preferences),
454-
NotificationPermissionRequester(preferences),
455-
BackgroundLocationPermissionRequester(this, preferences),
456-
WakeupPermissionRequester(this, preferences)
457-
)
460+
PermissionsSupport.ensure(
461+
this, *permissionRequesters
462+
)
458463
}
459464

460465
mapFragment.updateForgroundColor(strikeColorHandler.lineColor)
@@ -581,43 +586,15 @@ class Main : FragmentActivity(), OnSharedPreferenceChangeListener {
581586
}
582587

583588
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
584-
val locationProviderRelation = LocationProviderRelation.byOrdinal[requestCode]
585-
if (locationProviderRelation != null) {
586-
val providerName = locationProviderRelation.providerName
587-
if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
588-
val previousValue = preferences.get(PreferenceKey.LOCATION_MODE, "n/a")
589-
Log.i(
590-
LOG_TAG,
591-
"Main.onRequestPermissionResult() $providerName permission has now been granted. (code $requestCode, previous: $previousValue)"
592-
)
593-
preferences.edit {
594-
put(PreferenceKey.LOCATION_MODE, providerName)
595-
}
596-
locationHandler.update(preferences)
597-
} else {
598-
Log.i(
599-
LOG_TAG,
600-
"Main.onRequestPermissionResult() $providerName permission was NOT granted. (code $requestCode)"
601-
)
602-
locationHandler.shutdown()
603-
}
604-
} else if (requestCode == REQUEST_CODE_POST_NOTIFICATIONS && grantResults.isNotEmpty()) {
605-
val alertEnabled = preferences.get(PreferenceKey.ALERT_ENABLED, false)
606-
if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_DENIED && (alertEnabled || backgroundAlertEnabled)) {
607-
Log.i(
608-
LOG_TAG,
609-
"Main.onRequestPermissionResult() POST_NOTIFICATIONS permission was NOT granted but is required for alerts. Disabling alerts and background queries"
610-
)
611-
Toast.makeText(baseContext, R.string.post_notifications_required_for_alerts, Toast.LENGTH_LONG).show()
612-
preferences.edit {
613-
put(PreferenceKey.ALERT_ENABLED, false)
614-
put(PreferenceKey.BACKGROUND_QUERY_PERIOD, "0")
615-
}
616-
}
617-
}else {
618-
Log.i(LOG_TAG, "Main.onRequestPermissionResult() permissions: $permissions, requestCode: $requestCode")
619-
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
589+
Log.v(
590+
LOG_TAG,
591+
"Main.onRequestPermissionResult() permissions: ${permissions.joinToString(",")}, requestCode: $requestCode"
592+
)
593+
for (requester in permissionRequesters) {
594+
val handled = requester.onRequestPermissionsResult(requestCode, permissions, grantResults)
595+
if (handled) return
620596
}
597+
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
621598
}
622599

623600
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
@@ -712,7 +689,5 @@ class Main : FragmentActivity(), OnSharedPreferenceChangeListener {
712689
companion object {
713690
const val LOG_TAG = "BO_ANDROID"
714691
const val MAP_FRAGMENT_TAG = "org.blitzortung.MAP_FRAGMENT_TAG"
715-
const val REQUEST_CODE_POST_NOTIFICATIONS = 101
716-
const val REQUEST_CODE_BACKGROUND_LOCATION = 102
717692
}
718693
}

app/src/main/java/org/blitzortung/android/app/permission/PermissionsManager.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,5 @@ class PermissionsSupport(
7171
interface PermissionRequester {
7272
val name: String
7373
fun request(permissionsSupport: PermissionsSupport): Boolean
74+
fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray): Boolean = false
7475
}

app/src/main/java/org/blitzortung/android/app/permission/requester/BackgroundLocationPermissionRequester.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import androidx.annotation.RequiresApi
1010
import androidx.appcompat.app.AlertDialog
1111
import androidx.core.content.edit
1212
import org.blitzortung.android.app.Main.Companion.LOG_TAG
13-
import org.blitzortung.android.app.Main.Companion.REQUEST_CODE_BACKGROUND_LOCATION
1413
import org.blitzortung.android.app.R
1514
import org.blitzortung.android.app.permission.PermissionRequester
1615
import org.blitzortung.android.app.permission.PermissionsSupport
@@ -56,5 +55,6 @@ class BackgroundLocationPermissionRequester(
5655
internal fun isBackgroundAlertEnabled(preferences: SharedPreferences) : Boolean =
5756
preferences.get(PreferenceKey.BACKGROUND_QUERY_PERIOD, "0")
5857
.toInt() > 0
58+
const val REQUEST_CODE_BACKGROUND_LOCATION = 102
5959
}
6060
}

app/src/main/java/org/blitzortung/android/app/permission/requester/LocationPermissionRequester.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,30 @@ package org.blitzortung.android.app.permission.requester
22

33
import android.Manifest.permission.ACCESS_COARSE_LOCATION
44
import android.Manifest.permission.ACCESS_FINE_LOCATION
5+
import android.app.Activity
56
import android.content.SharedPreferences
7+
import android.content.pm.PackageManager
68
import android.location.LocationManager.GPS_PROVIDER
79
import android.location.LocationManager.NETWORK_PROVIDER
810
import android.location.LocationManager.PASSIVE_PROVIDER
911
import android.os.Build
12+
import android.util.Log
13+
import android.widget.Toast
1014
import androidx.annotation.RequiresApi
15+
import androidx.core.content.edit
16+
import org.blitzortung.android.app.Main.Companion.LOG_TAG
1117
import org.blitzortung.android.app.R
1218
import org.blitzortung.android.app.permission.LocationProviderRelation
1319
import org.blitzortung.android.app.permission.PermissionRequester
1420
import org.blitzortung.android.app.permission.PermissionsSupport
1521
import org.blitzortung.android.app.view.PreferenceKey
1622
import org.blitzortung.android.app.view.get
23+
import org.blitzortung.android.app.view.put
24+
import org.blitzortung.android.location.LocationHandler.Companion.MANUAL_PROVIDER
25+
import org.blitzortung.android.location.provider.ManualLocationProvider
1726

1827
class LocationPermissionRequester(
28+
private val activity: Activity,
1929
private val preferences: SharedPreferences
2030
) : PermissionRequester {
2131
override val name: String = "location"
@@ -33,6 +43,43 @@ class LocationPermissionRequester(
3343
}
3444
}
3545

46+
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray): Boolean {
47+
val locationProviderRelation = LocationProviderRelation.byOrdinal[requestCode]
48+
val alertEnabled = preferences.get(PreferenceKey.ALERT_ENABLED, false)
49+
return if (locationProviderRelation != null) {
50+
val providerName = locationProviderRelation.providerName
51+
if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
52+
val previousValue = preferences.get(PreferenceKey.LOCATION_MODE, "n/a")
53+
Log.i(
54+
LOG_TAG,
55+
"Main.onRequestPermissionResult() $providerName permission has been granted. (code $requestCode, previous: $previousValue)"
56+
)
57+
preferences.edit {
58+
put(PreferenceKey.LOCATION_MODE, providerName)
59+
}
60+
} else {
61+
Log.i(
62+
LOG_TAG,
63+
"Main.onRequestPermissionResult() $providerName permission was NOT granted. (code $requestCode)"
64+
)
65+
preferences.edit {
66+
put(PreferenceKey.LOCATION_MODE, MANUAL_PROVIDER)
67+
}
68+
if (alertEnabled && ManualLocationProvider.getManualLocation(preferences) == null) {
69+
Toast.makeText(activity, R.string.location_required_for_alerts, Toast.LENGTH_LONG).show()
70+
preferences.edit {
71+
put(PreferenceKey.ALERT_ENABLED, false)
72+
put(PreferenceKey.BACKGROUND_QUERY_PERIOD, "0")
73+
}
74+
}
75+
}
76+
true
77+
} else {
78+
false
79+
}
80+
}
81+
82+
3683
companion object {
3784
internal fun getLocationPermission(preferences: SharedPreferences): Pair<String?, Int> {
3885
val locationProviderName = preferences.get(PreferenceKey.LOCATION_MODE, PASSIVE_PROVIDER)

app/src/main/java/org/blitzortung/android/app/permission/requester/NotificationPermissionRequester.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
package org.blitzortung.android.app.permission.requester
22

33
import android.Manifest.permission.POST_NOTIFICATIONS
4+
import android.app.Activity
45
import android.content.SharedPreferences
6+
import android.content.pm.PackageManager
57
import android.os.Build
68
import android.util.Log
9+
import android.widget.Toast
10+
import androidx.core.content.edit
711
import org.blitzortung.android.app.Main.Companion.LOG_TAG
8-
import org.blitzortung.android.app.Main.Companion.REQUEST_CODE_POST_NOTIFICATIONS
912
import org.blitzortung.android.app.R
1013
import org.blitzortung.android.app.permission.PermissionRequester
1114
import org.blitzortung.android.app.permission.PermissionsSupport
1215
import org.blitzortung.android.app.permission.requester.BackgroundLocationPermissionRequester.Companion.isBackgroundAlertEnabled
1316
import org.blitzortung.android.app.view.PreferenceKey
1417
import org.blitzortung.android.app.view.get
18+
import org.blitzortung.android.app.view.put
1519

1620
class NotificationPermissionRequester(
21+
private val activity: Activity,
1722
val preferences: SharedPreferences
1823
) : PermissionRequester {
1924
override val name: String = "notification"
@@ -28,9 +33,33 @@ class NotificationPermissionRequester(
2833
}
2934
}
3035

36+
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray): Boolean {
37+
val alertEnabled = isAlertEnabled(preferences)
38+
val backgroundAlertEnabled = isBackgroundAlertEnabled(preferences)
39+
40+
return if (requestCode == REQUEST_CODE_POST_NOTIFICATIONS && grantResults.isNotEmpty()) {
41+
if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_DENIED && (alertEnabled || backgroundAlertEnabled)) {
42+
Log.i(
43+
LOG_TAG,
44+
"Main.onRequestPermissionResult() POST_NOTIFICATIONS permission was NOT granted but is required for alerts. Disabling alerts and background queries"
45+
)
46+
Toast.makeText(activity, R.string.post_notifications_required_for_alerts, Toast.LENGTH_LONG).show()
47+
preferences.edit {
48+
put(PreferenceKey.ALERT_ENABLED, false)
49+
put(PreferenceKey.BACKGROUND_QUERY_PERIOD, "0")
50+
}
51+
}
52+
true
53+
} else {
54+
false
55+
}
56+
}
57+
3158
companion object {
3259
internal fun isAlertEnabled(preferences: SharedPreferences) : Boolean =
3360
preferences.get(PreferenceKey.ALERT_ENABLED, false)
61+
62+
const val REQUEST_CODE_POST_NOTIFICATIONS = 101
3463
}
3564

3665
}

app/src/main/java/org/blitzortung/android/location/provider/ManualLocationProvider.kt

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,9 @@ class ManualLocationProvider(locationUpdate: (Location?) -> Unit, private val sh
1414
override val isEnabled: Boolean = true
1515

1616
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: PreferenceKey) {
17-
val doubleConverter = fun(x: String): Double? {
18-
try {
19-
return x.toDouble()
20-
} catch (e: NumberFormatException) {
21-
Log.e(Main.LOG_TAG, "bad longitude/latitude number format '$x'")
22-
}
23-
24-
return null
25-
}
26-
2717
when (key) {
2818
PreferenceKey.LOCATION_LONGITUDE, PreferenceKey.LOCATION_LATITUDE -> {
29-
val location = Location("")
30-
31-
location.longitude =
32-
sharedPreferences.getAndConvert(PreferenceKey.LOCATION_LONGITUDE, "11.0", doubleConverter) ?: 11.0
33-
location.latitude =
34-
sharedPreferences.getAndConvert(PreferenceKey.LOCATION_LATITUDE, "49.0", doubleConverter) ?: 49.0
35-
36-
sendLocationUpdate(location)
19+
sendLocationUpdate(getManualLocation(sharedPreferences))
3720
}
3821

3922
else -> {}
@@ -58,4 +41,34 @@ class ManualLocationProvider(locationUpdate: (Location?) -> Unit, private val sh
5841

5942
override fun reconfigureProvider(isInBackground: Boolean) { /* Nothing to do here */
6043
}
61-
}
44+
45+
companion object {
46+
fun getManualLocation(
47+
sharedPreferences: SharedPreferences,
48+
): Location? {
49+
val doubleConverter = fun(x: String): Double? {
50+
try {
51+
return x.toDouble()
52+
} catch (e: NumberFormatException) {
53+
Log.d(Main.LOG_TAG, "bad longitude/latitude number format '$x'")
54+
}
55+
56+
return null
57+
}
58+
59+
val longitude =
60+
sharedPreferences.getAndConvert(PreferenceKey.LOCATION_LONGITUDE, "", doubleConverter)
61+
val latitude =
62+
sharedPreferences.getAndConvert(PreferenceKey.LOCATION_LATITUDE, "", doubleConverter)
63+
64+
return if (longitude != null && latitude != null) {
65+
Location("").also {
66+
it.longitude = longitude
67+
it.latitude = latitude
68+
}
69+
} else {
70+
null
71+
}
72+
}
73+
}
74+
}

app/src/main/res/values-cs/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,4 +307,6 @@ Ruský překlad: Ivan Karev (karev.ivan@gmail.com)\n
307307
<string name="background_query_period_dialog_message">Použití periodického dotazu na pozadí pro upozornění, když aplikace není spuštěna, bude vyžadovat čtení polohy vašeho zařízení, pokud není nastavena na manuální polohu.</string>
308308
<string name="post_notifications_request">Povolte oprávnění k zasílání oznámení, pokud si přejete dostávat upozornění.</string>
309309
<string name="default_alarm_signal">Výchozí zvuk budíku</string>
310+
<string name="location_required_for_alerts">Pro upozornění je vyžadována poloha, zakázání upozornění a dotazů na pozadí.</string>
311+
<string name="post_notifications_required_for_alerts">Pro upozornění je vyžadováno oprávnění k oznamování, zakázání upozornění a dotazů na pozadí.</string>
310312
</resources>

app/src/main/res/values-de/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,4 +354,6 @@ Ivan Karev (karev.ivan@gmail.com)\nTschechisch: Jakub Mareček (jakubmarecek715@
354354
<string name="background_query_period_dialog_message">Die Verwendung einer periodischen Hintergrundabfrage für Warnungen, wenn die App nicht ausgeführt wird, erfordert das Auslesen des Standorts Ihres Geräts, sofern dieser nicht auf manuellen Standort eingestellt ist.</string>
355355
<string name="post_notifications_request">Aktivieren Sie die Berechtigung zum Posten von Benachrichtigungen, wenn Benachrichtigungswarnungen gewünscht sind.</string>
356356
<string name="default_alarm_signal">Standard-Alarmton</string>
357+
<string name="location_required_for_alerts">Für Warnungen ist eine Standortbestimmung erforderlich. Warnungen und Hintergrundabfragen werden deaktiviert.</string>
358+
<string name="post_notifications_required_for_alerts">Die Berechtigung zur Benachrichtigung ist für Warnungen erforderlich. Warnungen und Hintergrundabfragen werden deaktiviert.</string>
357359
</resources>

app/src/main/res/values-es/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,6 @@ Traducción ruso por: Ivan Karev (karev.ivan@gmail.com)\n
333333
<string name="background_query_period_dialog_message">El uso de una consulta periódica en segundo plano para alertas cuando la aplicación no se está ejecutando requerirá leer la ubicación de su dispositivo si no está configurado en ubicación manual.</string>
334334
<string name="post_notifications_request">Habilite el permiso para publicar notificaciones si desea recibir alertas de notificación.</string>
335335
<string name="default_alarm_signal">Sonido de alarma predeterminado</string>
336+
<string name="location_required_for_alerts">Se requiere la ubicación para las alertas, deshabilitando las alertas y las consultas en segundo plano.</string>
337+
<string name="post_notifications_required_for_alerts">Se requiere permiso de notificación para las alertas, deshabilitando las alertas y las consultas en segundo plano.</string>
336338
</resources>

0 commit comments

Comments
 (0)