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")
}