mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 13:05:19 +00:00
Fix/foreground permissions scanning (#556)
* try catch the foreground location service if not available * ask for background permissions * background permissions in onboarding * small improvements
This commit is contained in:
+251
@@ -0,0 +1,251 @@
|
||||
package com.bitchat.android.onboarding
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
|
||||
/**
|
||||
* Explanation screen shown before requesting background location permission.
|
||||
*/
|
||||
@Composable
|
||||
fun BackgroundLocationPermissionScreen(
|
||||
modifier: Modifier,
|
||||
onContinue: () -> Unit,
|
||||
onRetry: () -> Unit,
|
||||
onSkip: () -> Unit
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
Box(modifier = modifier) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 24.dp)
|
||||
.padding(bottom = 88.dp)
|
||||
.verticalScroll(scrollState),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
HeaderSection(colorScheme)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = colorScheme.surfaceVariant.copy(alpha = 0.25f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.Top,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.LocationOn,
|
||||
contentDescription = stringResource(R.string.cd_location_services),
|
||||
tint = colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
.size(20.dp)
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.background_location_required_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = colorScheme.onBackground
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.background_location_explanation),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.background_location_settings_tip),
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
),
|
||||
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = colorScheme.surfaceVariant.copy(alpha = 0.25f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.Top,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Security,
|
||||
contentDescription = stringResource(R.string.cd_privacy_protected),
|
||||
tint = colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
.size(20.dp)
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.background_location_needs_for),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = colorScheme.onBackground
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.background_location_needs_bullets),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.background_location_privacy_note),
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Medium
|
||||
),
|
||||
color = colorScheme.onBackground
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth(),
|
||||
color = colorScheme.surface,
|
||||
shadowElevation = 8.dp
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Button(
|
||||
onClick = onContinue,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = colorScheme.primary
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.grant_background_location),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold
|
||||
),
|
||||
modifier = Modifier.padding(vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
OutlinedButton(
|
||||
onClick = onRetry,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.check_again),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
TextButton(
|
||||
onClick = onSkip,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.battery_optimization_skip),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HeaderSection(colorScheme: ColorScheme) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.headlineLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 32.sp
|
||||
),
|
||||
color = colorScheme.onBackground
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.background_location_required_subtitle),
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.7f)
|
||||
)
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.bitchat.android.onboarding
|
||||
|
||||
import android.content.Context
|
||||
|
||||
/**
|
||||
* Preference manager for background location skip choice.
|
||||
*/
|
||||
object BackgroundLocationPreferenceManager {
|
||||
private const val PREFS_NAME = "bitchat_settings"
|
||||
private const val KEY_BACKGROUND_LOCATION_SKIP = "background_location_skipped"
|
||||
|
||||
fun setSkipped(context: Context, skipped: Boolean) {
|
||||
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
prefs.edit().putBoolean(KEY_BACKGROUND_LOCATION_SKIP, skipped).apply()
|
||||
}
|
||||
|
||||
fun isSkipped(context: Context): Boolean {
|
||||
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
return prefs.getBoolean(KEY_BACKGROUND_LOCATION_SKIP, false)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ class OnboardingCoordinator(
|
||||
private val activity: ComponentActivity,
|
||||
private val permissionManager: PermissionManager,
|
||||
private val onOnboardingComplete: () -> Unit,
|
||||
private val onBackgroundLocationRequired: () -> Unit,
|
||||
private val onOnboardingFailed: (String) -> Unit
|
||||
) {
|
||||
|
||||
@@ -27,9 +28,11 @@ class OnboardingCoordinator(
|
||||
}
|
||||
|
||||
private var permissionLauncher: ActivityResultLauncher<Array<String>>? = null
|
||||
private var backgroundLocationLauncher: ActivityResultLauncher<String>? = null
|
||||
|
||||
init {
|
||||
setupPermissionLauncher()
|
||||
setupBackgroundLocationLauncher()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,6 +46,14 @@ class OnboardingCoordinator(
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupBackgroundLocationLauncher() {
|
||||
backgroundLocationLauncher = activity.registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { granted ->
|
||||
handleBackgroundLocationResult(granted)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the onboarding process
|
||||
*/
|
||||
@@ -50,9 +61,14 @@ class OnboardingCoordinator(
|
||||
Log.d(TAG, "Starting onboarding process")
|
||||
permissionManager.logPermissionStatus()
|
||||
|
||||
if (permissionManager.areAllPermissionsGranted()) {
|
||||
Log.d(TAG, "All permissions already granted, completing onboarding")
|
||||
completeOnboarding()
|
||||
if (permissionManager.areRequiredPermissionsGranted()) {
|
||||
if (shouldRequestBackgroundLocation()) {
|
||||
Log.d(TAG, "Foreground permissions granted; background location recommended")
|
||||
onBackgroundLocationRequired()
|
||||
} else {
|
||||
Log.d(TAG, "Required permissions already granted, completing onboarding")
|
||||
completeOnboarding()
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Missing permissions, need to start explanation flow")
|
||||
// The explanation screen will be shown by the calling activity
|
||||
@@ -76,7 +92,11 @@ class OnboardingCoordinator(
|
||||
val missingPermissions = (missingRequired + optionalToRequest).distinct()
|
||||
|
||||
if (missingPermissions.isEmpty()) {
|
||||
completeOnboarding()
|
||||
if (shouldRequestBackgroundLocation()) {
|
||||
onBackgroundLocationRequired()
|
||||
} else {
|
||||
completeOnboarding()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -98,13 +118,19 @@ class OnboardingCoordinator(
|
||||
val criticalGranted = criticalPermissions.all { permissions[it] == true }
|
||||
|
||||
when {
|
||||
allGranted -> {
|
||||
Log.d(TAG, "All permissions granted successfully")
|
||||
completeOnboarding()
|
||||
}
|
||||
criticalGranted -> {
|
||||
Log.d(TAG, "Critical permissions granted, can proceed with limited functionality")
|
||||
showPartialPermissionWarning(permissions)
|
||||
if (shouldRequestBackgroundLocation()) {
|
||||
Log.d(TAG, "Foreground permissions granted; requesting background location next")
|
||||
onBackgroundLocationRequired()
|
||||
return
|
||||
}
|
||||
if (allGranted) {
|
||||
Log.d(TAG, "All permissions granted successfully")
|
||||
completeOnboarding()
|
||||
} else {
|
||||
Log.d(TAG, "Critical permissions granted, can proceed with limited functionality")
|
||||
showPartialPermissionWarning(permissions)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Log.d(TAG, "Critical permissions denied")
|
||||
@@ -113,15 +139,50 @@ class OnboardingCoordinator(
|
||||
}
|
||||
}
|
||||
|
||||
fun requestBackgroundLocation() {
|
||||
val permission = permissionManager.getBackgroundLocationPermission()
|
||||
if (permission == null) {
|
||||
completeOnboarding()
|
||||
return
|
||||
}
|
||||
Log.d(TAG, "Requesting background location permission")
|
||||
backgroundLocationLauncher?.launch(permission)
|
||||
}
|
||||
|
||||
private fun handleBackgroundLocationResult(granted: Boolean) {
|
||||
if (granted) {
|
||||
Log.d(TAG, "Background location permission granted")
|
||||
} else {
|
||||
Log.w(TAG, "Background location permission denied; continuing without it")
|
||||
}
|
||||
completeOnboarding()
|
||||
}
|
||||
|
||||
fun skipBackgroundLocation() {
|
||||
Log.d(TAG, "User skipped background location permission")
|
||||
BackgroundLocationPreferenceManager.setSkipped(activity, true)
|
||||
completeOnboarding()
|
||||
}
|
||||
|
||||
fun checkBackgroundLocationAndProceed() {
|
||||
if (!shouldRequestBackgroundLocation()) {
|
||||
completeOnboarding()
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldRequestBackgroundLocation(): Boolean {
|
||||
return permissionManager.needsBackgroundLocationPermission() &&
|
||||
!permissionManager.isBackgroundLocationGranted() &&
|
||||
!BackgroundLocationPreferenceManager.isSkipped(activity)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of critical permissions that are absolutely required
|
||||
*/
|
||||
private fun getCriticalPermissions(): List<String> {
|
||||
// For bitchat, Bluetooth and location permissions are critical
|
||||
// Notifications are nice-to-have but not critical
|
||||
return permissionManager.getRequiredPermissions().filter { permission ->
|
||||
!permission.contains("POST_NOTIFICATIONS")
|
||||
}
|
||||
// Notifications are nice-to-have but not critical and are not included in getRequiredPermissions()
|
||||
return permissionManager.getRequiredPermissions()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,6 +269,7 @@ class OnboardingCoordinator(
|
||||
private fun getPermissionDisplayName(permission: String): String {
|
||||
return when {
|
||||
permission.contains("BLUETOOTH") -> "Bluetooth/Nearby Devices"
|
||||
permission.contains("BACKGROUND") -> "Background Location"
|
||||
permission.contains("LOCATION") -> "Location (for Bluetooth scanning)"
|
||||
permission.contains("NOTIFICATION") -> "Notifications"
|
||||
else -> permission.substringAfterLast(".")
|
||||
|
||||
@@ -6,8 +6,9 @@ enum class OnboardingState {
|
||||
LOCATION_CHECK,
|
||||
BATTERY_OPTIMIZATION_CHECK,
|
||||
PERMISSION_EXPLANATION,
|
||||
BACKGROUND_LOCATION_EXPLANATION,
|
||||
PERMISSION_REQUESTING,
|
||||
INITIALIZING,
|
||||
COMPLETE,
|
||||
ERROR
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,10 @@ import androidx.compose.material.icons.filled.Power
|
||||
import androidx.compose.material.icons.filled.Mic
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -209,29 +207,6 @@ private fun PermissionCategoryCard(
|
||||
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||
)
|
||||
|
||||
if (category.type == PermissionType.PRECISE_LOCATION) {
|
||||
// Extra emphasis for location permission
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Warning,
|
||||
contentDescription = stringResource(R.string.cd_warning),
|
||||
tint = Color(0xFFFF9800),
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.location_tracking_warning),
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = Color(0xFFFF9800)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,6 +215,7 @@ private fun getPermissionIcon(permissionType: PermissionType): ImageVector {
|
||||
return when (permissionType) {
|
||||
PermissionType.NEARBY_DEVICES -> Icons.Filled.Bluetooth
|
||||
PermissionType.PRECISE_LOCATION -> Icons.Filled.LocationOn
|
||||
PermissionType.BACKGROUND_LOCATION -> Icons.Filled.LocationOn
|
||||
PermissionType.MICROPHONE -> Icons.Filled.Mic
|
||||
PermissionType.NOTIFICATIONS -> Icons.Filled.Notifications
|
||||
PermissionType.BATTERY_OPTIMIZATION -> Icons.Filled.Power
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.bitchat.android.R
|
||||
|
||||
/**
|
||||
* Centralized permission management for bitchat app
|
||||
@@ -40,7 +41,8 @@ class PermissionManager(private val context: Context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all permissions required by the app
|
||||
* Get required permissions that can be requested together.
|
||||
* Background location is handled separately to ensure correct request order.
|
||||
* Note: Notification permission is optional and not included here,
|
||||
* so the app works without notification access.
|
||||
*/
|
||||
@@ -72,6 +74,27 @@ class PermissionManager(private val context: Context) {
|
||||
return permissions
|
||||
}
|
||||
|
||||
/**
|
||||
* Background location permission is required on Android 10+ for background BLE scanning.
|
||||
* Must be requested after foreground location permissions are granted.
|
||||
*/
|
||||
fun needsBackgroundLocationPermission(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||
}
|
||||
|
||||
fun getBackgroundLocationPermission(): String? {
|
||||
return if (needsBackgroundLocationPermission()) {
|
||||
Manifest.permission.ACCESS_BACKGROUND_LOCATION
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun isBackgroundLocationGranted(): Boolean {
|
||||
val permission = getBackgroundLocationPermission() ?: return true
|
||||
return isPermissionGranted(permission)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get optional permissions that improve the experience but aren't required.
|
||||
* Currently includes POST_NOTIFICATIONS on Android 13+.
|
||||
@@ -93,9 +116,13 @@ class PermissionManager(private val context: Context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all required permissions are granted
|
||||
* Check if all required permissions are granted (background location is optional).
|
||||
*/
|
||||
fun areAllPermissionsGranted(): Boolean {
|
||||
return areRequiredPermissionsGranted()
|
||||
}
|
||||
|
||||
fun areRequiredPermissionsGranted(): Boolean {
|
||||
return getRequiredPermissions().all { isPermissionGranted(it) }
|
||||
}
|
||||
|
||||
@@ -131,6 +158,11 @@ class PermissionManager(private val context: Context) {
|
||||
return getRequiredPermissions().filter { !isPermissionGranted(it) }
|
||||
}
|
||||
|
||||
fun getMissingBackgroundLocationPermission(): List<String> {
|
||||
val permission = getBackgroundLocationPermission() ?: return emptyList()
|
||||
return if (isPermissionGranted(permission)) emptyList() else listOf(permission)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categorized permission information for display
|
||||
*/
|
||||
@@ -177,6 +209,19 @@ class PermissionManager(private val context: Context) {
|
||||
)
|
||||
)
|
||||
|
||||
if (needsBackgroundLocationPermission()) {
|
||||
val backgroundPermission = listOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
|
||||
categories.add(
|
||||
PermissionCategory(
|
||||
type = PermissionType.BACKGROUND_LOCATION,
|
||||
description = context.getString(R.string.perm_background_location_desc),
|
||||
permissions = backgroundPermission,
|
||||
isGranted = backgroundPermission.all { isPermissionGranted(it) },
|
||||
systemDescription = context.getString(R.string.perm_background_location_system)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Notifications category (if applicable)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
categories.add(
|
||||
@@ -216,7 +261,7 @@ class PermissionManager(private val context: Context) {
|
||||
appendLine("Permission Diagnostics:")
|
||||
appendLine("Android SDK: ${Build.VERSION.SDK_INT}")
|
||||
appendLine("First time launch: ${isFirstTimeLaunch()}")
|
||||
appendLine("All permissions granted: ${areAllPermissionsGranted()}")
|
||||
appendLine("Required permissions granted: ${areAllPermissionsGranted()}")
|
||||
appendLine()
|
||||
|
||||
getCategorizedPermissions().forEach { category ->
|
||||
@@ -228,7 +273,7 @@ class PermissionManager(private val context: Context) {
|
||||
appendLine()
|
||||
}
|
||||
|
||||
val missing = getMissingPermissions()
|
||||
val missing = getMissingPermissions() + getMissingBackgroundLocationPermission()
|
||||
if (missing.isNotEmpty()) {
|
||||
appendLine("Missing permissions:")
|
||||
missing.forEach { permission ->
|
||||
@@ -260,6 +305,7 @@ data class PermissionCategory(
|
||||
enum class PermissionType(val nameValue: String) {
|
||||
NEARBY_DEVICES("Nearby Devices"),
|
||||
PRECISE_LOCATION("Precise Location"),
|
||||
BACKGROUND_LOCATION("Background Location"),
|
||||
MICROPHONE("Microphone"),
|
||||
NOTIFICATIONS("Notifications"),
|
||||
BATTERY_OPTIMIZATION("Battery Optimization"),
|
||||
|
||||
Reference in New Issue
Block a user