mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-25 20:44:38 +00:00
android: workaround of system restricted background (#2873)
* android: workaround of system restricted background * strings * exclude lockscreen call from requirements to unrestrict * texts * added button and changed behaviour * texts 2 * button instead of alert * padding * bigger padding * refactor * don't jump on button hide * no exceptions for lockscreen * sometimes do not show off alert --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
38dc14f041
commit
9543af4784
@@ -93,12 +93,12 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
|
||||
fun allowToStartServiceAfterAppExit() = with(chatModel.controller) {
|
||||
appPrefs.notificationsMode.get() == NotificationsMode.SERVICE &&
|
||||
(!NotificationsMode.SERVICE.requiresIgnoringBattery || SimplexService.isIgnoringBatteryOptimizations())
|
||||
(!NotificationsMode.SERVICE.requiresIgnoringBattery || SimplexService.isBackgroundAllowed())
|
||||
}
|
||||
|
||||
private fun allowToStartPeriodically() = with(chatModel.controller) {
|
||||
appPrefs.notificationsMode.get() == NotificationsMode.PERIODIC &&
|
||||
(!NotificationsMode.PERIODIC.requiresIgnoringBattery || SimplexService.isIgnoringBatteryOptimizations())
|
||||
(!NotificationsMode.PERIODIC.requiresIgnoringBattery || SimplexService.isBackgroundAllowed())
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -158,7 +158,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
}
|
||||
|
||||
override fun androidNotificationsModeChanged(mode: NotificationsMode) {
|
||||
if (mode.requiresIgnoringBattery && !SimplexService.isIgnoringBatteryOptimizations()) {
|
||||
if (mode.requiresIgnoringBattery && !SimplexService.isBackgroundAllowed()) {
|
||||
appPrefs.backgroundServiceNoticeShown.set(false)
|
||||
}
|
||||
SimplexService.StartReceiver.toggleReceiver(mode == NotificationsMode.SERVICE)
|
||||
@@ -172,7 +172,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
if (mode != NotificationsMode.PERIODIC) {
|
||||
MessagesFetcherWorker.cancelAll()
|
||||
}
|
||||
SimplexService.showBackgroundServiceNoticeIfNeeded()
|
||||
SimplexService.showBackgroundServiceNoticeIfNeeded(showOffAlert = false)
|
||||
}
|
||||
|
||||
override fun androidChatStartedAfterBeingOff() {
|
||||
@@ -199,6 +199,19 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun androidIsBackgroundCallAllowed(): Boolean = !SimplexService.isBackgroundRestricted()
|
||||
|
||||
override suspend fun androidAskToAllowBackgroundCalls(): Boolean {
|
||||
if (SimplexService.isBackgroundRestricted()) {
|
||||
val userChoice: CompletableDeferred<Boolean> = CompletableDeferred()
|
||||
SimplexService.showBGRestrictedInCall {
|
||||
userChoice.complete(it)
|
||||
}
|
||||
return userChoice.await()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.os.*
|
||||
import android.provider.Settings
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -17,7 +18,6 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.work.*
|
||||
import chat.simplex.common.AppLock
|
||||
import chat.simplex.common.AppLock.clearAuthState
|
||||
import chat.simplex.common.helpers.requiresIgnoringBattery
|
||||
import chat.simplex.common.model.ChatController
|
||||
import chat.simplex.common.model.NotificationsMode
|
||||
@@ -50,6 +50,7 @@ class SimplexService: Service() {
|
||||
} else {
|
||||
Log.d(TAG, "null intent. Probably restarted by the system.")
|
||||
}
|
||||
startForeground(SIMPLEX_SERVICE_ID, serviceNotification)
|
||||
return START_STICKY // to restart if killed
|
||||
}
|
||||
|
||||
@@ -353,7 +354,7 @@ class SimplexService: Service() {
|
||||
|
||||
private fun getPreferences(context: Context): SharedPreferences = context.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE)
|
||||
|
||||
fun showBackgroundServiceNoticeIfNeeded() {
|
||||
fun showBackgroundServiceNoticeIfNeeded(showOffAlert: Boolean = true) {
|
||||
val appPrefs = ChatController.appPrefs
|
||||
val mode = appPrefs.notificationsMode.get()
|
||||
Log.d(TAG, "showBackgroundServiceNoticeIfNeeded")
|
||||
@@ -362,27 +363,28 @@ class SimplexService: Service() {
|
||||
|
||||
if (!appPrefs.backgroundServiceNoticeShown.get()) {
|
||||
// the branch for the new users who have never seen service notice
|
||||
if (!mode.requiresIgnoringBattery || isIgnoringBatteryOptimizations()) {
|
||||
if (!mode.requiresIgnoringBattery || isBackgroundAllowed()) {
|
||||
showBGServiceNotice(mode)
|
||||
} else {
|
||||
showBGServiceNoticeIgnoreOptimization(mode)
|
||||
} else if (isBackgroundRestricted()) {
|
||||
showBGServiceNoticeSystemRestricted(mode, showOffAlert)
|
||||
} else if (!isIgnoringBatteryOptimizations()) {
|
||||
showBGServiceNoticeIgnoreOptimization(mode, showOffAlert)
|
||||
}
|
||||
// set both flags, so that if the user doesn't allow ignoring optimizations, the service will be disabled without additional notice
|
||||
appPrefs.backgroundServiceNoticeShown.set(true)
|
||||
appPrefs.backgroundServiceBatteryNoticeShown.set(true)
|
||||
} else if (mode.requiresIgnoringBattery && isBackgroundRestricted()) {
|
||||
// the branch for users who have app installed, and have seen the service notice,
|
||||
// but the service is running AND system background restriction is on OR the battery optimization for the app is in RESTRICTED state
|
||||
showBGServiceNoticeSystemRestricted(mode, showOffAlert)
|
||||
if (!appPrefs.backgroundServiceBatteryNoticeShown.get()) {
|
||||
appPrefs.backgroundServiceBatteryNoticeShown.set(true)
|
||||
}
|
||||
} else if (mode.requiresIgnoringBattery && !isIgnoringBatteryOptimizations()) {
|
||||
// the branch for users who have app installed, and have seen the service notice,
|
||||
// but the battery optimization for the app is on (Android 12) AND the service is running
|
||||
if (appPrefs.backgroundServiceBatteryNoticeShown.get()) {
|
||||
// users have been presented with battery notice before - they did not allow ignoring optimizations -> disable service
|
||||
showDisablingServiceNotice(mode)
|
||||
appPrefs.notificationsMode.set(NotificationsMode.OFF)
|
||||
StartReceiver.toggleReceiver(false)
|
||||
MessagesFetcherWorker.cancelAll()
|
||||
safeStopService()
|
||||
} else {
|
||||
// show battery optimization notice
|
||||
showBGServiceNoticeIgnoreOptimization(mode)
|
||||
// but the battery optimization for the app is in OPTIMIZED state (Android 12+) AND the service is running
|
||||
showBGServiceNoticeIgnoreOptimization(mode, showOffAlert)
|
||||
if (!appPrefs.backgroundServiceBatteryNoticeShown.get()) {
|
||||
appPrefs.backgroundServiceBatteryNoticeShown.set(true)
|
||||
}
|
||||
} else {
|
||||
@@ -425,13 +427,17 @@ class SimplexService: Service() {
|
||||
)
|
||||
}
|
||||
|
||||
private fun showBGServiceNoticeIgnoreOptimization(mode: NotificationsMode) = AlertManager.shared.showAlert {
|
||||
private fun showBGServiceNoticeIgnoreOptimization(mode: NotificationsMode, showOffAlert: Boolean) = AlertManager.shared.showAlert {
|
||||
val ignoreOptimization = {
|
||||
AlertManager.shared.hideAlert()
|
||||
askAboutIgnoringBatteryOptimization()
|
||||
}
|
||||
val disableNotifications = {
|
||||
AlertManager.shared.hideAlert()
|
||||
disableNotifications(mode, showOffAlert)
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = ignoreOptimization,
|
||||
onDismissRequest = disableNotifications,
|
||||
title = {
|
||||
Row {
|
||||
Icon(
|
||||
@@ -454,12 +460,98 @@ class SimplexService: Service() {
|
||||
Text(annotatedStringResource(MR.strings.turn_off_battery_optimization))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = disableNotifications) { Text(stringResource(MR.strings.disable_notifications_button), color = MaterialTheme.colors.error) }
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = ignoreOptimization) { Text(stringResource(MR.strings.ok)) }
|
||||
TextButton(onClick = ignoreOptimization) { Text(stringResource(MR.strings.turn_off_battery_optimization_button)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun showBGServiceNoticeSystemRestricted(mode: NotificationsMode, showOffAlert: Boolean) = AlertManager.shared.showAlert {
|
||||
val unrestrict = {
|
||||
AlertManager.shared.hideAlert()
|
||||
askToUnrestrictBackground()
|
||||
}
|
||||
val disableNotifications = {
|
||||
AlertManager.shared.hideAlert()
|
||||
disableNotifications(mode, showOffAlert)
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = disableNotifications,
|
||||
title = {
|
||||
Row {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_bolt),
|
||||
contentDescription =
|
||||
if (mode == NotificationsMode.SERVICE) stringResource(MR.strings.icon_descr_instant_notifications) else stringResource(MR.strings.periodic_notifications),
|
||||
)
|
||||
Text(
|
||||
if (mode == NotificationsMode.SERVICE) stringResource(MR.strings.service_notifications) else stringResource(MR.strings.periodic_notifications),
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
annotatedStringResource(MR.strings.system_restricted_background_desc),
|
||||
Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
Text(annotatedStringResource(MR.strings.system_restricted_background_warn))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = disableNotifications) { Text(stringResource(MR.strings.disable_notifications_button), color = MaterialTheme.colors.error) }
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = unrestrict) { Text(stringResource(MR.strings.turn_off_system_restriction_button)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun showBGRestrictedInCall(onDismiss: (allowedCall: Boolean) -> Unit) = AlertManager.shared.showAlert {
|
||||
val unrestrict = {
|
||||
askToUnrestrictBackground()
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = AlertManager.shared::hideAlert,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(MR.strings.system_restricted_background_in_call_title),
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
annotatedStringResource(MR.strings.system_restricted_background_in_call_desc),
|
||||
Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
Text(annotatedStringResource(MR.strings.system_restricted_background_in_call_warn))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = unrestrict) { Text(stringResource(MR.strings.turn_off_system_restriction_button)) }
|
||||
}
|
||||
)
|
||||
val scope = rememberCoroutineScope()
|
||||
DisposableEffect(Unit) {
|
||||
scope.launch {
|
||||
repeat(10000) {
|
||||
delay(200)
|
||||
if (!isBackgroundRestricted()) {
|
||||
AlertManager.shared.hideAlert()
|
||||
}
|
||||
}
|
||||
}
|
||||
onDispose {
|
||||
onDismiss(!isBackgroundRestricted())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDisablingServiceNotice(mode: NotificationsMode) = AlertManager.shared.showAlert {
|
||||
AlertDialog(
|
||||
onDismissRequest = AlertManager.shared::hideAlert,
|
||||
@@ -490,11 +582,22 @@ class SimplexService: Service() {
|
||||
)
|
||||
}
|
||||
|
||||
fun isBackgroundAllowed(): Boolean = isIgnoringBatteryOptimizations() && !isBackgroundRestricted()
|
||||
|
||||
fun isIgnoringBatteryOptimizations(): Boolean {
|
||||
val powerManager = androidAppContext.getSystemService(Application.POWER_SERVICE) as PowerManager
|
||||
return powerManager.isIgnoringBatteryOptimizations(androidAppContext.packageName)
|
||||
}
|
||||
|
||||
fun isBackgroundRestricted(): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= 28) {
|
||||
val activityService = androidAppContext.getSystemService(ACTIVITY_SERVICE) as ActivityManager
|
||||
activityService.isBackgroundRestricted
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun askAboutIgnoringBatteryOptimization() {
|
||||
Intent().apply {
|
||||
@SuppressLint("BatteryLife")
|
||||
@@ -505,5 +608,29 @@ class SimplexService: Service() {
|
||||
androidAppContext.startActivity(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun askToUnrestrictBackground() {
|
||||
Intent().apply {
|
||||
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
data = Uri.parse("package:${androidAppContext.packageName}")
|
||||
// This flag is needed when you start a new activity from non-Activity context
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
try {
|
||||
androidAppContext.startActivity(this)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e(TAG, e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun disableNotifications(mode: NotificationsMode, showOffAlert: Boolean) {
|
||||
if (showOffAlert) {
|
||||
showDisablingServiceNotice(mode)
|
||||
}
|
||||
ChatController.appPrefs.notificationsMode.set(NotificationsMode.OFF)
|
||||
StartReceiver.toggleReceiver(false)
|
||||
MessagesFetcherWorker.cancelAll()
|
||||
safeStopService()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user