Skip to content

Commit 575da29

Browse files
fix: improve permission request handling (#137)
1 parent d1b8b86 commit 575da29

13 files changed

Lines changed: 639 additions & 148 deletions

File tree

app/src/main/java/com/mubarak/mbcompass/core/location/LocationHelper.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import android.content.Context
55
import android.content.pm.PackageManager
66
import android.location.Location
77
import android.location.LocationManager
8+
import android.os.Build
89
import android.os.Bundle
910
import android.os.SystemClock
1011
import android.util.Log
1112
import androidx.core.content.ContextCompat
13+
import androidx.core.location.LocationManagerCompat
1214
import com.mubarak.mbcompass.data.AppPreferences
1315
import com.mubarak.mbcompass.features.tracks.TrackingConstants
1416
import com.mubarak.mbcompass.features.tracks.model.Track
@@ -21,6 +23,23 @@ object LocationHelper {
2123

2224
private const val TAG = "LocationHelper"
2325

26+
//Check if location services (GPS/Network) are enabled
27+
fun isLocationEnabled(context: Context): Boolean {
28+
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
29+
30+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
31+
LocationManagerCompat.isLocationEnabled(locationManager)
32+
} else {
33+
try {
34+
val gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
35+
val networkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
36+
gpsEnabled || networkEnabled
37+
} catch (e: Exception) {
38+
false
39+
}
40+
}
41+
}
42+
2443
fun getDefaultLocation(): Location {
2544
val defaultLocation = Location(LocationManager.NETWORK_PROVIDER)
2645
defaultLocation.latitude = TrackingConstants.DEFAULT_LATITUDE
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
package com.mubarak.mbcompass.core.permission
2+
3+
import android.Manifest
4+
import android.app.AlertDialog
5+
import android.content.Context
6+
import android.content.Intent
7+
import android.content.pm.PackageManager
8+
import android.net.Uri
9+
import android.os.Build
10+
import android.provider.Settings
11+
import androidx.activity.result.ActivityResultLauncher
12+
import androidx.core.content.ContextCompat
13+
import androidx.fragment.app.Fragment
14+
import com.mubarak.mbcompass.R
15+
16+
class PermissionHandler(
17+
private val fragment: Fragment
18+
) {
19+
20+
private val context: Context
21+
get() = fragment.requireContext()
22+
23+
24+
fun requestLocationPermission(
25+
launcher: ActivityResultLauncher<String>,
26+
onGranted: () -> Unit,
27+
onDenied: () -> Unit
28+
) {
29+
when {
30+
// Already granted
31+
hasLocationPermission() -> {
32+
onGranted()
33+
}
34+
35+
// Should show rationale (user denied before)
36+
fragment.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) -> {
37+
showLocationRationaleDialog(
38+
onPositive = { launcher.launch(Manifest.permission.ACCESS_FINE_LOCATION) },
39+
onNegative = onDenied
40+
)
41+
}
42+
43+
// First time asking
44+
else -> {
45+
showLocationEducationDialog(
46+
onPositive = { launcher.launch(Manifest.permission.ACCESS_FINE_LOCATION) },
47+
onNegative = onDenied
48+
)
49+
}
50+
}
51+
}
52+
53+
54+
private fun showLocationEducationDialog(
55+
onPositive: () -> Unit,
56+
onNegative: () -> Unit
57+
) {
58+
AlertDialog.Builder(context)
59+
.setTitle(R.string.location_permission_title)
60+
.setMessage(R.string.location_permission_message)
61+
.setIcon(R.drawable.location_icon24px)
62+
.setPositiveButton(R.string.grant_permission) { _, _ ->
63+
onPositive()
64+
}
65+
.setNegativeButton(R.string.not_now) { _, _ ->
66+
onNegative()
67+
}
68+
.setCancelable(false)
69+
.show()
70+
}
71+
72+
73+
// Rationale dialog
74+
private fun showLocationRationaleDialog(
75+
onPositive: () -> Unit,
76+
onNegative: () -> Unit
77+
) {
78+
AlertDialog.Builder(context)
79+
.setTitle(R.string.location_permission_required)
80+
.setMessage(R.string.location_permission_rationale)
81+
.setIcon(R.drawable.location_icon24px)
82+
.setPositiveButton(R.string.try_again) { _, _ ->
83+
onPositive()
84+
}
85+
.setNeutralButton(R.string.open_settings) { _, _ ->
86+
openAppSettings()
87+
}
88+
.setNegativeButton(R.string.cancel, null)
89+
.show()
90+
}
91+
92+
93+
fun hasLocationPermission(): Boolean {
94+
return ContextCompat.checkSelfPermission(
95+
context,
96+
Manifest.permission.ACCESS_FINE_LOCATION
97+
) == PackageManager.PERMISSION_GRANTED
98+
}
99+
100+
101+
fun requestNotificationPermission(
102+
launcher: ActivityResultLauncher<String>,
103+
onGranted: () -> Unit,
104+
onDenied: () -> Unit
105+
) {
106+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
107+
// Not needed on Android 12 and below
108+
onGranted()
109+
return
110+
}
111+
112+
when {
113+
hasNotificationPermission() -> {
114+
onGranted()
115+
}
116+
117+
fragment.shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) -> {
118+
showNotificationRationaleDialog(
119+
onPositive = { launcher.launch(Manifest.permission.POST_NOTIFICATIONS) },
120+
onNegative = onDenied
121+
)
122+
}
123+
124+
else -> {
125+
showNotificationEducationDialog(
126+
onPositive = { launcher.launch(Manifest.permission.POST_NOTIFICATIONS) },
127+
onNegative = onDenied
128+
)
129+
}
130+
}
131+
}
132+
133+
private fun showNotificationEducationDialog(
134+
onPositive: () -> Unit,
135+
onNegative: () -> Unit
136+
) {
137+
AlertDialog.Builder(context)
138+
.setTitle(R.string.notification_permission_title)
139+
.setMessage(R.string.notification_permission_message)
140+
.setPositiveButton(R.string.grant_permission) { _, _ ->
141+
onPositive()
142+
}
143+
.setNegativeButton(R.string.not_now) { _, _ ->
144+
onNegative()
145+
}
146+
.setCancelable(false)
147+
.show()
148+
}
149+
150+
private fun showNotificationRationaleDialog(
151+
onPositive: () -> Unit,
152+
onNegative: () -> Unit
153+
) {
154+
AlertDialog.Builder(context)
155+
.setTitle(R.string.notification_permission_required)
156+
.setMessage(R.string.notification_permission_rationale)
157+
.setPositiveButton(R.string.try_again) { _, _ ->
158+
onPositive()
159+
}
160+
.setNeutralButton(R.string.open_settings) { _, _ ->
161+
openAppSettings()
162+
}
163+
.setNegativeButton(R.string.cancel, null)
164+
.show()
165+
}
166+
167+
fun hasNotificationPermission(): Boolean {
168+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
169+
ContextCompat.checkSelfPermission(
170+
context,
171+
Manifest.permission.POST_NOTIFICATIONS
172+
) == PackageManager.PERMISSION_GRANTED
173+
} else {
174+
true // Not required on older versions
175+
}
176+
}
177+
178+
fun requestActivityRecognitionPermission(
179+
launcher: ActivityResultLauncher<String>,
180+
onGranted: () -> Unit,
181+
onDenied: () -> Unit
182+
) {
183+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
184+
// Not needed on Android 9 and below
185+
onGranted()
186+
return
187+
}
188+
189+
when {
190+
hasActivityRecognitionPermission() -> {
191+
onGranted()
192+
}
193+
194+
fragment.shouldShowRequestPermissionRationale(Manifest.permission.ACTIVITY_RECOGNITION) -> {
195+
showActivityRecognitionRationaleDialog(
196+
onPositive = { launcher.launch(Manifest.permission.ACTIVITY_RECOGNITION) },
197+
onNegative = onDenied
198+
)
199+
}
200+
201+
else -> {
202+
showActivityRecognitionEducationDialog(
203+
onPositive = { launcher.launch(Manifest.permission.ACTIVITY_RECOGNITION) },
204+
onNegative = onDenied
205+
)
206+
}
207+
}
208+
}
209+
210+
private fun showActivityRecognitionEducationDialog(
211+
onPositive: () -> Unit,
212+
onNegative: () -> Unit
213+
) {
214+
AlertDialog.Builder(context)
215+
.setTitle(R.string.activity_recognition_permission_title)
216+
.setMessage(R.string.activity_recognition_permission_message)
217+
.setPositiveButton(R.string.grant_permission) { _, _ ->
218+
onPositive()
219+
}
220+
.setNegativeButton(R.string.not_now) { _, _ ->
221+
onNegative()
222+
}
223+
.setCancelable(false)
224+
.show()
225+
}
226+
227+
private fun showActivityRecognitionRationaleDialog(
228+
onPositive: () -> Unit,
229+
onNegative: () -> Unit
230+
) {
231+
AlertDialog.Builder(context)
232+
.setTitle(R.string.activity_recognition_permission_required)
233+
.setMessage(R.string.activity_recognition_permission_rationale)
234+
.setPositiveButton(R.string.try_again) { _, _ ->
235+
onPositive()
236+
}
237+
.setNeutralButton(R.string.open_settings) { _, _ ->
238+
openAppSettings()
239+
}
240+
.setNegativeButton(R.string.cancel, null)
241+
.show()
242+
}
243+
244+
fun hasActivityRecognitionPermission(): Boolean {
245+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
246+
ContextCompat.checkSelfPermission(
247+
context,
248+
Manifest.permission.ACTIVITY_RECOGNITION
249+
) == PackageManager.PERMISSION_GRANTED
250+
} else {
251+
true // Not required on older versions
252+
}
253+
}
254+
255+
fun openAppSettings() {
256+
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
257+
data = Uri.fromParts("package", context.packageName, null)
258+
flags = Intent.FLAG_ACTIVITY_NEW_TASK
259+
}
260+
context.startActivity(intent)
261+
}
262+
263+
264+
fun openLocationSettings() {
265+
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS).apply {
266+
flags = Intent.FLAG_ACTIVITY_NEW_TASK
267+
}
268+
context.startActivity(intent)
269+
}
270+
}

0 commit comments

Comments
 (0)