diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0ea01524..3cc22c28 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,9 @@ + + + diff --git a/app/src/main/java/com/bitchat/android/MainActivity.kt b/app/src/main/java/com/bitchat/android/MainActivity.kt index 1ed97a92..2d601eae 100644 --- a/app/src/main/java/com/bitchat/android/MainActivity.kt +++ b/app/src/main/java/com/bitchat/android/MainActivity.kt @@ -34,6 +34,7 @@ class MainActivity : ComponentActivity() { private lateinit var onboardingCoordinator: OnboardingCoordinator private lateinit var bluetoothStatusManager: BluetoothStatusManager private lateinit var locationStatusManager: LocationStatusManager + private lateinit var batteryOptimizationManager: BatteryOptimizationManager // Core mesh service - managed at app level private lateinit var meshService: BluetoothMeshService @@ -50,14 +51,17 @@ class MainActivity : ComponentActivity() { private var onboardingState by mutableStateOf(OnboardingState.CHECKING) private var bluetoothStatus by mutableStateOf(BluetoothStatus.ENABLED) private var locationStatus by mutableStateOf(LocationStatus.ENABLED) + private var batteryOptimizationStatus by mutableStateOf(BatteryOptimizationStatus.DISABLED) private var errorMessage by mutableStateOf("") private var isBluetoothLoading by mutableStateOf(false) private var isLocationLoading by mutableStateOf(false) + private var isBatteryOptimizationLoading by mutableStateOf(false) enum class OnboardingState { CHECKING, BLUETOOTH_CHECK, LOCATION_CHECK, + BATTERY_OPTIMIZATION_CHECK, PERMISSION_EXPLANATION, PERMISSION_REQUESTING, INITIALIZING, @@ -65,6 +69,8 @@ class MainActivity : ComponentActivity() { ERROR } + + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -85,6 +91,12 @@ class MainActivity : ComponentActivity() { onLocationEnabled = ::handleLocationEnabled, onLocationDisabled = ::handleLocationDisabled ) + batteryOptimizationManager = BatteryOptimizationManager( + activity = this, + context = this, + onBatteryOptimizationDisabled = ::handleBatteryOptimizationDisabled, + onBatteryOptimizationFailed = ::handleBatteryOptimizationFailed + ) onboardingCoordinator = OnboardingCoordinator( activity = this, permissionManager = permissionManager, @@ -142,6 +154,24 @@ class MainActivity : ComponentActivity() { ) } + OnboardingState.BATTERY_OPTIMIZATION_CHECK -> { + BatteryOptimizationScreen( + status = batteryOptimizationStatus, + onDisableBatteryOptimization = { + isBatteryOptimizationLoading = true + batteryOptimizationManager.requestDisableBatteryOptimization() + }, + onRetry = { + checkBatteryOptimizationAndProceed() + }, + onSkip = { + // Skip battery optimization and proceed + proceedWithPermissionCheck() + }, + isLoading = isBatteryOptimizationLoading + ) + } + OnboardingState.PERMISSION_EXPLANATION -> { PermissionExplanationScreen( permissionCategories = permissionManager.getCategorizedPermissions(), @@ -300,8 +330,8 @@ class MainActivity : ComponentActivity() { when (locationStatus) { LocationStatus.ENABLED -> { - // Location services enabled, proceed with permission/onboarding check - proceedWithPermissionCheck() + // Location services enabled, check battery optimization next + checkBatteryOptimizationAndProceed() } LocationStatus.DISABLED -> { // Show location enable screen (should have permissions as existing user) @@ -325,6 +355,65 @@ class MainActivity : ComponentActivity() { android.util.Log.d("MainActivity", "Location services enabled by user") isLocationLoading = false locationStatus = LocationStatus.ENABLED + checkBatteryOptimizationAndProceed() + } + + /** + * Check Battery Optimization status and proceed with onboarding flow + */ + private fun checkBatteryOptimizationAndProceed() { + android.util.Log.d("MainActivity", "Checking battery optimization status") + + // For first-time users, skip battery optimization check and go straight to permissions + // We'll check battery optimization after permissions are granted + if (permissionManager.isFirstTimeLaunch()) { + android.util.Log.d("MainActivity", "First-time launch, skipping battery optimization check - will check after permissions") + proceedWithPermissionCheck() + return + } + + // For existing users, check battery optimization status + batteryOptimizationManager.logBatteryOptimizationStatus() + + batteryOptimizationStatus = when { + !batteryOptimizationManager.isBatteryOptimizationSupported() -> BatteryOptimizationStatus.NOT_SUPPORTED + batteryOptimizationManager.isBatteryOptimizationDisabled() -> BatteryOptimizationStatus.DISABLED + else -> BatteryOptimizationStatus.ENABLED + } + + when (batteryOptimizationStatus) { + BatteryOptimizationStatus.DISABLED, BatteryOptimizationStatus.NOT_SUPPORTED -> { + // Battery optimization is disabled or not supported, proceed with permission check + proceedWithPermissionCheck() + } + BatteryOptimizationStatus.ENABLED -> { + // Show battery optimization disable screen + android.util.Log.d("MainActivity", "Battery optimization enabled, showing disable screen") + onboardingState = OnboardingState.BATTERY_OPTIMIZATION_CHECK + isBatteryOptimizationLoading = false + } + } + } + + /** + * Handle Battery Optimization disabled callback + */ + private fun handleBatteryOptimizationDisabled() { + android.util.Log.d("MainActivity", "Battery optimization disabled by user") + isBatteryOptimizationLoading = false + batteryOptimizationStatus = BatteryOptimizationStatus.DISABLED + proceedWithPermissionCheck() + } + + /** + * Handle Battery Optimization failed callback + */ + private fun handleBatteryOptimizationFailed(message: String) { + android.util.Log.w("MainActivity", "Battery optimization disable failed: $message") + isBatteryOptimizationLoading = false + + // Don't treat this as a fatal error - proceed with onboarding + // User can always change this setting later proceedWithPermissionCheck() } @@ -382,11 +471,16 @@ class MainActivity : ComponentActivity() { } private fun handleOnboardingComplete() { - android.util.Log.d("MainActivity", "Onboarding completed, checking Bluetooth and Location before initializing app") + android.util.Log.d("MainActivity", "Onboarding completed, checking Bluetooth, Location, and Battery Optimization before initializing app") - // After permissions are granted, re-check both Bluetooth and Location status + // After permissions are granted, re-check Bluetooth, Location, and Battery Optimization status val currentBluetoothStatus = bluetoothStatusManager.checkBluetoothStatus() val currentLocationStatus = locationStatusManager.checkLocationStatus() + val currentBatteryOptimizationStatus = when { + !batteryOptimizationManager.isBatteryOptimizationSupported() -> BatteryOptimizationStatus.NOT_SUPPORTED + batteryOptimizationManager.isBatteryOptimizationDisabled() -> BatteryOptimizationStatus.DISABLED + else -> BatteryOptimizationStatus.ENABLED + } when { currentBluetoothStatus != BluetoothStatus.ENABLED -> { @@ -403,9 +497,19 @@ class MainActivity : ComponentActivity() { onboardingState = OnboardingState.LOCATION_CHECK isLocationLoading = false } + currentBatteryOptimizationStatus == BatteryOptimizationStatus.ENABLED -> { + // Battery optimization is still enabled, show disable screen + android.util.Log.d("MainActivity", "Permissions granted, but Battery optimization still enabled. Showing Battery optimization disable screen.") + batteryOptimizationStatus = currentBatteryOptimizationStatus + onboardingState = OnboardingState.BATTERY_OPTIMIZATION_CHECK + isBatteryOptimizationLoading = false + } else -> { - // Both are enabled, proceed to app initialization - android.util.Log.d("MainActivity", "Both Bluetooth and Location services are enabled, proceeding to initialization") + // Bluetooth, Location, and Battery Optimization are all properly configured, proceed to app initialization + android.util.Log.d("MainActivity", "Bluetooth, Location, and Battery Optimization all properly configured, proceeding to initialization") + bluetoothStatus = currentBluetoothStatus + locationStatus = currentLocationStatus + batteryOptimizationStatus = currentBatteryOptimizationStatus onboardingState = OnboardingState.INITIALIZING initializeApp() } diff --git a/app/src/main/java/com/bitchat/android/onboarding/BatteryOptimizationManager.kt b/app/src/main/java/com/bitchat/android/onboarding/BatteryOptimizationManager.kt new file mode 100644 index 00000000..77ede503 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/onboarding/BatteryOptimizationManager.kt @@ -0,0 +1,177 @@ +package com.bitchat.android.onboarding + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.PowerManager +import android.provider.Settings +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts + +/** + * Manages battery optimization settings for the app + * Handles checking if the app is whitelisted from battery optimization + * and requesting the user to disable battery optimization + */ +class BatteryOptimizationManager( + private val activity: ComponentActivity, + private val context: Context, + private val onBatteryOptimizationDisabled: () -> Unit, + private val onBatteryOptimizationFailed: (String) -> Unit +) { + + companion object { + private const val TAG = "BatteryOptimizationManager" + } + + private var batteryOptimizationLauncher: ActivityResultLauncher? = null + + init { + setupBatteryOptimizationLauncher() + } + + /** + * Setup the battery optimization request launcher + */ + private fun setupBatteryOptimizationLauncher() { + batteryOptimizationLauncher = activity.registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + Log.d(TAG, "Battery optimization request result: ${result.resultCode}") + + // Check if battery optimization is now disabled + if (isBatteryOptimizationDisabled()) { + Log.d(TAG, "Battery optimization successfully disabled") + onBatteryOptimizationDisabled() + } else { + Log.w(TAG, "Battery optimization still enabled after user interaction") + // Don't treat this as a failure - user might have chosen not to disable it + // We'll proceed anyway but log the status + onBatteryOptimizationDisabled() + } + } + } + + /** + * Check if battery optimization is disabled for this app + */ + fun isBatteryOptimizationDisabled(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + try { + val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager + val isIgnoring = powerManager.isIgnoringBatteryOptimizations(context.packageName) + Log.d(TAG, "Battery optimization disabled: $isIgnoring") + isIgnoring + } catch (e: Exception) { + Log.e(TAG, "Error checking battery optimization status", e) + // If we can't check, assume it's enabled (more conservative) + false + } + } else { + // Battery optimization doesn't exist on Android < 6.0 + Log.d(TAG, "Battery optimization not applicable for Android < 6.0") + true + } + } + + /** + * Request to disable battery optimization for this app + */ + fun requestDisableBatteryOptimization() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + try { + Log.d(TAG, "Requesting to disable battery optimization") + + val intent = Intent().apply { + action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS + data = Uri.parse("package:${context.packageName}") + } + + // Check if the intent can be resolved + if (intent.resolveActivity(context.packageManager) != null) { + batteryOptimizationLauncher?.launch(intent) + } else { + Log.w(TAG, "Battery optimization settings not available, opening general settings") + openBatteryOptimizationSettings() + } + } catch (e: Exception) { + Log.e(TAG, "Error requesting battery optimization disable", e) + onBatteryOptimizationFailed("Unable to open battery optimization settings: ${e.message}") + } + } else { + Log.d(TAG, "Battery optimization not applicable for Android < 6.0") + onBatteryOptimizationDisabled() + } + } + + /** + * Open general battery optimization settings if direct request fails + */ + private fun openBatteryOptimizationSettings() { + try { + val intent = Intent().apply { + action = Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS + } + + if (intent.resolveActivity(context.packageManager) != null) { + batteryOptimizationLauncher?.launch(intent) + } else { + // Fallback to general application settings + openAppSettings() + } + } catch (e: Exception) { + Log.e(TAG, "Error opening battery optimization settings", e) + onBatteryOptimizationFailed("Unable to open settings: ${e.message}") + } + } + + /** + * Open app settings as a last resort + */ + private fun openAppSettings() { + try { + val intent = Intent().apply { + action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + data = Uri.fromParts("package", context.packageName, null) + } + batteryOptimizationLauncher?.launch(intent) + } catch (e: Exception) { + Log.e(TAG, "Error opening app settings", e) + onBatteryOptimizationFailed("Unable to open app settings: ${e.message}") + } + } + + /** + * Check if battery optimization is supported on this device + */ + fun isBatteryOptimizationSupported(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + } + + /** + * Get battery optimization status for logging + */ + fun getBatteryOptimizationStatus(): String { + return when { + !isBatteryOptimizationSupported() -> "Not supported (Android < 6.0)" + isBatteryOptimizationDisabled() -> "Disabled (app is whitelisted)" + else -> "Enabled (app is being optimized)" + } + } + + /** + * Log battery optimization status for debugging + */ + fun logBatteryOptimizationStatus() { + Log.d(TAG, "Battery optimization status: ${getBatteryOptimizationStatus()}") + } +} + +enum class BatteryOptimizationStatus { + ENABLED, + DISABLED, + NOT_SUPPORTED +} \ No newline at end of file diff --git a/app/src/main/java/com/bitchat/android/onboarding/BatteryOptimizationScreen.kt b/app/src/main/java/com/bitchat/android/onboarding/BatteryOptimizationScreen.kt new file mode 100644 index 00000000..5213768b --- /dev/null +++ b/app/src/main/java/com/bitchat/android/onboarding/BatteryOptimizationScreen.kt @@ -0,0 +1,296 @@ +package com.bitchat.android.onboarding + +import androidx.compose.animation.core.* +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp + +/** + * Screen shown when checking battery optimization status or requesting battery optimization disable + */ + + + +@Composable +fun BatteryOptimizationScreen( + status: BatteryOptimizationStatus, + onDisableBatteryOptimization: () -> Unit, + onRetry: () -> Unit, + onSkip: () -> Unit, + isLoading: Boolean = false +) { + val colorScheme = MaterialTheme.colorScheme + + Box( + modifier = Modifier + .fillMaxSize() + .padding(32.dp), + contentAlignment = Alignment.Center + ) { + when (status) { + BatteryOptimizationStatus.ENABLED -> { + BatteryOptimizationEnabledContent( + onDisableBatteryOptimization = onDisableBatteryOptimization, + onRetry = onRetry, + onSkip = onSkip, + colorScheme = colorScheme, + isLoading = isLoading + ) + } + + BatteryOptimizationStatus.DISABLED -> { + BatteryOptimizationCheckingContent( + colorScheme = colorScheme + ) + } + + BatteryOptimizationStatus.NOT_SUPPORTED -> { + BatteryOptimizationNotSupportedContent( + onRetry = onRetry, + colorScheme = colorScheme + ) + } + } + } +} + +@Composable +private fun BatteryOptimizationEnabledContent( + onDisableBatteryOptimization: () -> Unit, + onRetry: () -> Unit, + onSkip: () -> Unit, + colorScheme: ColorScheme, + isLoading: Boolean +) { + Column( + verticalArrangement = Arrangement.spacedBy(24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "bitchat*", + style = MaterialTheme.typography.headlineLarge.copy( + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, + color = colorScheme.primary + ) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Icon( + imageVector = Icons.Outlined.BatteryAlert, + contentDescription = "Battery Optimization", + modifier = Modifier.size(64.dp), + tint = colorScheme.error + ) + + Text( + text = "Battery Optimization Detected", + style = MaterialTheme.typography.headlineSmall.copy( + fontWeight = FontWeight.SemiBold, + color = colorScheme.onSurface + ), + textAlign = TextAlign.Center + ) + + Text( + text = "bitchat needs to run in the background to maintain mesh network connections and relay messages for other users.", + style = MaterialTheme.typography.bodyLarge.copy( + color = colorScheme.onSurfaceVariant + ), + textAlign = TextAlign.Center + ) + + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = colorScheme.surfaceVariant.copy(alpha = 0.5f) + ) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = "Why disable battery optimization?", + style = MaterialTheme.typography.titleSmall.copy( + fontWeight = FontWeight.SemiBold, + color = colorScheme.onSurface + ) + ) + + Text( + text = "• Ensures reliable message delivery\n• Maintains mesh network connectivity\n• Allows background message relay\n• Prevents connection drops", + style = MaterialTheme.typography.bodyMedium.copy( + color = colorScheme.onSurfaceVariant + ) + ) + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + Button( + onClick = onDisableBatteryOptimization, + modifier = Modifier.fillMaxWidth(), + enabled = !isLoading + ) { + if (isLoading) { + CircularProgressIndicator( + modifier = Modifier.size(16.dp), + strokeWidth = 2.dp, + color = colorScheme.onPrimary + ) + Spacer(modifier = Modifier.width(8.dp)) + } + Text("Disable Battery Optimization") + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + OutlinedButton( + onClick = onRetry, + modifier = Modifier.weight(1f), + enabled = !isLoading + ) { + Text("Check Again") + } + + TextButton( + onClick = onSkip, + modifier = Modifier.weight(1f), + enabled = !isLoading + ) { + Text("Skip for Now") + } + } + + Text( + text = "Note: You can change this setting later in Android Settings > Apps > bitchat > Battery", + style = MaterialTheme.typography.bodySmall.copy( + color = colorScheme.onSurfaceVariant + ), + textAlign = TextAlign.Center + ) + } +} + +@Composable +private fun BatteryOptimizationCheckingContent( + colorScheme: ColorScheme +) { + Column( + verticalArrangement = Arrangement.spacedBy(32.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "bitchat*", + style = MaterialTheme.typography.headlineLarge.copy( + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, + color = colorScheme.primary + ) + ) + + val infiniteTransition = rememberInfiniteTransition(label = "rotation") + val rotation by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 360f, + animationSpec = infiniteRepeatable( + animation = tween(2000, easing = LinearEasing), + repeatMode = RepeatMode.Restart + ), + label = "rotation" + ) + + Icon( + imageVector = Icons.Filled.BatteryStd, + contentDescription = "Checking Battery Optimization", + modifier = Modifier + .size(64.dp) + .rotate(rotation), + tint = colorScheme.primary + ) + + Text( + text = "Battery Optimization Disabled", + style = MaterialTheme.typography.headlineSmall.copy( + fontWeight = FontWeight.SemiBold, + color = colorScheme.onSurface + ), + textAlign = TextAlign.Center + ) + + Text( + text = "bitchat can run reliably in the background", + style = MaterialTheme.typography.bodyLarge.copy( + color = colorScheme.onSurfaceVariant + ), + textAlign = TextAlign.Center + ) + } +} + +@Composable +private fun BatteryOptimizationNotSupportedContent( + onRetry: () -> Unit, + colorScheme: ColorScheme +) { + Column( + verticalArrangement = Arrangement.spacedBy(24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "bitchat*", + style = MaterialTheme.typography.headlineLarge.copy( + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, + color = colorScheme.primary + ) + ) + + Icon( + imageVector = Icons.Filled.CheckCircle, + contentDescription = "Battery Optimization Not Supported", + modifier = Modifier.size(64.dp), + tint = colorScheme.primary + ) + + Text( + text = "Battery Optimization Not Required", + style = MaterialTheme.typography.headlineSmall.copy( + fontWeight = FontWeight.SemiBold, + color = colorScheme.onSurface + ), + textAlign = TextAlign.Center + ) + + Text( + text = "Your device doesn't require battery optimization settings. bitchat will run normally.", + style = MaterialTheme.typography.bodyLarge.copy( + color = colorScheme.onSurfaceVariant + ), + textAlign = TextAlign.Center + ) + + Button( + onClick = onRetry, + modifier = Modifier.fillMaxWidth() + ) { + Text("Continue") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bitchat/android/onboarding/PermissionExplanationScreen.kt b/app/src/main/java/com/bitchat/android/onboarding/PermissionExplanationScreen.kt index 4f066096..7c24cadc 100644 --- a/app/src/main/java/com/bitchat/android/onboarding/PermissionExplanationScreen.kt +++ b/app/src/main/java/com/bitchat/android/onboarding/PermissionExplanationScreen.kt @@ -237,6 +237,7 @@ private fun getPermissionEmoji(permissionType: PermissionType): String { PermissionType.NEARBY_DEVICES -> "šŸ“±" PermissionType.PRECISE_LOCATION -> "šŸ“" PermissionType.NOTIFICATIONS -> "šŸ””" + PermissionType.BATTERY_OPTIMIZATION -> "šŸ”‹" PermissionType.OTHER -> "šŸ”§" } } @@ -246,6 +247,7 @@ private fun getPermissionIconColor(permissionType: PermissionType): Color { PermissionType.NEARBY_DEVICES -> Color(0xFF2196F3) // Blue PermissionType.PRECISE_LOCATION -> Color(0xFFFF9800) // Orange PermissionType.NOTIFICATIONS -> Color(0xFF4CAF50) // Green + PermissionType.BATTERY_OPTIMIZATION -> Color(0xFFF44336) // Red PermissionType.OTHER -> Color(0xFF9C27B0) // Purple } } diff --git a/app/src/main/java/com/bitchat/android/onboarding/PermissionManager.kt b/app/src/main/java/com/bitchat/android/onboarding/PermissionManager.kt index 7d293324..cfec35e7 100644 --- a/app/src/main/java/com/bitchat/android/onboarding/PermissionManager.kt +++ b/app/src/main/java/com/bitchat/android/onboarding/PermissionManager.kt @@ -4,6 +4,7 @@ import android.Manifest import android.content.Context import android.content.pm.PackageManager import android.os.Build +import android.os.PowerManager import android.util.Log import androidx.core.content.ContextCompat @@ -86,6 +87,31 @@ class PermissionManager(private val context: Context) { return getRequiredPermissions().all { isPermissionGranted(it) } } + /** + * Check if battery optimization is disabled for this app + */ + fun isBatteryOptimizationDisabled(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + try { + val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager + powerManager.isIgnoringBatteryOptimizations(context.packageName) + } catch (e: Exception) { + Log.e(TAG, "Error checking battery optimization status", e) + false + } + } else { + // Battery optimization doesn't exist on Android < 6.0 + true + } + } + + /** + * Check if battery optimization is supported on this device + */ + fun isBatteryOptimizationSupported(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + } + /** * Get the list of permissions that are missing */ @@ -152,6 +178,19 @@ class PermissionManager(private val context: Context) { ) } + // Battery optimization category (if applicable) + if (isBatteryOptimizationSupported()) { + categories.add( + PermissionCategory( + type = PermissionType.BATTERY_OPTIMIZATION, + description = "Disable battery optimization to ensure bitchat runs reliably in the background and maintains mesh network connections", + permissions = listOf("BATTERY_OPTIMIZATION"), // Custom identifier + isGranted = isBatteryOptimizationDisabled(), + systemDescription = "Allow bitchat to run without battery restrictions" + ) + ) + } + return categories } @@ -208,5 +247,6 @@ enum class PermissionType(val nameValue: String) { NEARBY_DEVICES("Nearby Devices"), PRECISE_LOCATION("Precise Location"), NOTIFICATIONS("Notifications"), + BATTERY_OPTIMIZATION("Battery Optimization"), OTHER("Other") }