mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 09:25:19 +00:00
feat(battery): add battery optimization management for background reliability
Add battery optimization permission and management system to ensure app runs reliably in background: - Add REQUEST_IGNORE_BATTERY_OPTIMIZATIONS permission - Create BatteryOptimizationManager to handle battery optimization checks and requests - Add battery optimization UI screens and flow in onboarding - Integrate battery optimization checks into MainActivity flow
This commit is contained in:
@@ -19,6 +19,9 @@
|
|||||||
<!-- Haptic feedback permission -->
|
<!-- Haptic feedback permission -->
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
|
||||||
|
<!-- Battery optimization permission -->
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
|
||||||
<!-- Hardware features -->
|
<!-- Hardware features -->
|
||||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
|
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
|
||||||
<uses-feature android:name="android.hardware.bluetooth" android:required="true" />
|
<uses-feature android:name="android.hardware.bluetooth" android:required="true" />
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
private lateinit var onboardingCoordinator: OnboardingCoordinator
|
private lateinit var onboardingCoordinator: OnboardingCoordinator
|
||||||
private lateinit var bluetoothStatusManager: BluetoothStatusManager
|
private lateinit var bluetoothStatusManager: BluetoothStatusManager
|
||||||
private lateinit var locationStatusManager: LocationStatusManager
|
private lateinit var locationStatusManager: LocationStatusManager
|
||||||
|
private lateinit var batteryOptimizationManager: BatteryOptimizationManager
|
||||||
|
|
||||||
// Core mesh service - managed at app level
|
// Core mesh service - managed at app level
|
||||||
private lateinit var meshService: BluetoothMeshService
|
private lateinit var meshService: BluetoothMeshService
|
||||||
@@ -50,14 +51,17 @@ class MainActivity : ComponentActivity() {
|
|||||||
private var onboardingState by mutableStateOf(OnboardingState.CHECKING)
|
private var onboardingState by mutableStateOf(OnboardingState.CHECKING)
|
||||||
private var bluetoothStatus by mutableStateOf(BluetoothStatus.ENABLED)
|
private var bluetoothStatus by mutableStateOf(BluetoothStatus.ENABLED)
|
||||||
private var locationStatus by mutableStateOf(LocationStatus.ENABLED)
|
private var locationStatus by mutableStateOf(LocationStatus.ENABLED)
|
||||||
|
private var batteryOptimizationStatus by mutableStateOf(BatteryOptimizationStatus.DISABLED)
|
||||||
private var errorMessage by mutableStateOf("")
|
private var errorMessage by mutableStateOf("")
|
||||||
private var isBluetoothLoading by mutableStateOf(false)
|
private var isBluetoothLoading by mutableStateOf(false)
|
||||||
private var isLocationLoading by mutableStateOf(false)
|
private var isLocationLoading by mutableStateOf(false)
|
||||||
|
private var isBatteryOptimizationLoading by mutableStateOf(false)
|
||||||
|
|
||||||
enum class OnboardingState {
|
enum class OnboardingState {
|
||||||
CHECKING,
|
CHECKING,
|
||||||
BLUETOOTH_CHECK,
|
BLUETOOTH_CHECK,
|
||||||
LOCATION_CHECK,
|
LOCATION_CHECK,
|
||||||
|
BATTERY_OPTIMIZATION_CHECK,
|
||||||
PERMISSION_EXPLANATION,
|
PERMISSION_EXPLANATION,
|
||||||
PERMISSION_REQUESTING,
|
PERMISSION_REQUESTING,
|
||||||
INITIALIZING,
|
INITIALIZING,
|
||||||
@@ -65,6 +69,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
ERROR
|
ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@@ -85,6 +91,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
onLocationEnabled = ::handleLocationEnabled,
|
onLocationEnabled = ::handleLocationEnabled,
|
||||||
onLocationDisabled = ::handleLocationDisabled
|
onLocationDisabled = ::handleLocationDisabled
|
||||||
)
|
)
|
||||||
|
batteryOptimizationManager = BatteryOptimizationManager(
|
||||||
|
activity = this,
|
||||||
|
context = this,
|
||||||
|
onBatteryOptimizationDisabled = ::handleBatteryOptimizationDisabled,
|
||||||
|
onBatteryOptimizationFailed = ::handleBatteryOptimizationFailed
|
||||||
|
)
|
||||||
onboardingCoordinator = OnboardingCoordinator(
|
onboardingCoordinator = OnboardingCoordinator(
|
||||||
activity = this,
|
activity = this,
|
||||||
permissionManager = permissionManager,
|
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 -> {
|
OnboardingState.PERMISSION_EXPLANATION -> {
|
||||||
PermissionExplanationScreen(
|
PermissionExplanationScreen(
|
||||||
permissionCategories = permissionManager.getCategorizedPermissions(),
|
permissionCategories = permissionManager.getCategorizedPermissions(),
|
||||||
@@ -300,8 +330,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
when (locationStatus) {
|
when (locationStatus) {
|
||||||
LocationStatus.ENABLED -> {
|
LocationStatus.ENABLED -> {
|
||||||
// Location services enabled, proceed with permission/onboarding check
|
// Location services enabled, check battery optimization next
|
||||||
proceedWithPermissionCheck()
|
checkBatteryOptimizationAndProceed()
|
||||||
}
|
}
|
||||||
LocationStatus.DISABLED -> {
|
LocationStatus.DISABLED -> {
|
||||||
// Show location enable screen (should have permissions as existing user)
|
// 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")
|
android.util.Log.d("MainActivity", "Location services enabled by user")
|
||||||
isLocationLoading = false
|
isLocationLoading = false
|
||||||
locationStatus = LocationStatus.ENABLED
|
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()
|
proceedWithPermissionCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,11 +471,16 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleOnboardingComplete() {
|
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 currentBluetoothStatus = bluetoothStatusManager.checkBluetoothStatus()
|
||||||
val currentLocationStatus = locationStatusManager.checkLocationStatus()
|
val currentLocationStatus = locationStatusManager.checkLocationStatus()
|
||||||
|
val currentBatteryOptimizationStatus = when {
|
||||||
|
!batteryOptimizationManager.isBatteryOptimizationSupported() -> BatteryOptimizationStatus.NOT_SUPPORTED
|
||||||
|
batteryOptimizationManager.isBatteryOptimizationDisabled() -> BatteryOptimizationStatus.DISABLED
|
||||||
|
else -> BatteryOptimizationStatus.ENABLED
|
||||||
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
currentBluetoothStatus != BluetoothStatus.ENABLED -> {
|
currentBluetoothStatus != BluetoothStatus.ENABLED -> {
|
||||||
@@ -403,9 +497,19 @@ class MainActivity : ComponentActivity() {
|
|||||||
onboardingState = OnboardingState.LOCATION_CHECK
|
onboardingState = OnboardingState.LOCATION_CHECK
|
||||||
isLocationLoading = false
|
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 -> {
|
else -> {
|
||||||
// Both are enabled, proceed to app initialization
|
// Bluetooth, Location, and Battery Optimization are all properly configured, proceed to app initialization
|
||||||
android.util.Log.d("MainActivity", "Both Bluetooth and Location services are enabled, proceeding to 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
|
onboardingState = OnboardingState.INITIALIZING
|
||||||
initializeApp()
|
initializeApp()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<Intent>? = 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
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -237,6 +237,7 @@ private fun getPermissionEmoji(permissionType: PermissionType): String {
|
|||||||
PermissionType.NEARBY_DEVICES -> "📱"
|
PermissionType.NEARBY_DEVICES -> "📱"
|
||||||
PermissionType.PRECISE_LOCATION -> "📍"
|
PermissionType.PRECISE_LOCATION -> "📍"
|
||||||
PermissionType.NOTIFICATIONS -> "🔔"
|
PermissionType.NOTIFICATIONS -> "🔔"
|
||||||
|
PermissionType.BATTERY_OPTIMIZATION -> "🔋"
|
||||||
PermissionType.OTHER -> "🔧"
|
PermissionType.OTHER -> "🔧"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,6 +247,7 @@ private fun getPermissionIconColor(permissionType: PermissionType): Color {
|
|||||||
PermissionType.NEARBY_DEVICES -> Color(0xFF2196F3) // Blue
|
PermissionType.NEARBY_DEVICES -> Color(0xFF2196F3) // Blue
|
||||||
PermissionType.PRECISE_LOCATION -> Color(0xFFFF9800) // Orange
|
PermissionType.PRECISE_LOCATION -> Color(0xFFFF9800) // Orange
|
||||||
PermissionType.NOTIFICATIONS -> Color(0xFF4CAF50) // Green
|
PermissionType.NOTIFICATIONS -> Color(0xFF4CAF50) // Green
|
||||||
|
PermissionType.BATTERY_OPTIMIZATION -> Color(0xFFF44336) // Red
|
||||||
PermissionType.OTHER -> Color(0xFF9C27B0) // Purple
|
PermissionType.OTHER -> Color(0xFF9C27B0) // Purple
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.Manifest
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.PowerManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
|
||||||
@@ -86,6 +87,31 @@ class PermissionManager(private val context: Context) {
|
|||||||
return getRequiredPermissions().all { isPermissionGranted(it) }
|
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
|
* 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
|
return categories
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,5 +247,6 @@ enum class PermissionType(val nameValue: String) {
|
|||||||
NEARBY_DEVICES("Nearby Devices"),
|
NEARBY_DEVICES("Nearby Devices"),
|
||||||
PRECISE_LOCATION("Precise Location"),
|
PRECISE_LOCATION("Precise Location"),
|
||||||
NOTIFICATIONS("Notifications"),
|
NOTIFICATIONS("Notifications"),
|
||||||
|
BATTERY_OPTIMIZATION("Battery Optimization"),
|
||||||
OTHER("Other")
|
OTHER("Other")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user