android: notification service and battery optimization on Android 12 (#656)

* Managing battery optimization on Android 12+ via in-app UI                                                                                                                                              
- in case of battery optimization is enabled a user will be asked to disable it if he wants to have a background service                                                                                
- when the service is enabled but the user don't want to disable the battery optimization, the service will be disabled with an alert for the user

* update service notice conditions

* android: update notification service logic

* update translations

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
This commit is contained in:
Evgeny Poberezkin
2022-05-16 07:59:41 +01:00
committed by GitHub
parent c000a1b924
commit 36ef6df9fb
7 changed files with 162 additions and 35 deletions

View File

@@ -16,6 +16,7 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application
android:name="SimplexApp"

View File

@@ -52,7 +52,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
} else {
chatController.startChat(user)
SimplexService.start(applicationContext)
chatController.showBackgroundServiceNotice()
chatController.showBackgroundServiceNoticeIfNeeded()
}
}
}
@@ -65,6 +65,10 @@ class SimplexApp: Application(), LifecycleEventObserver {
if (!chatController.getRunServiceInBackground()) SimplexService.stop(applicationContext)
Lifecycle.Event.ON_START ->
SimplexService.start(applicationContext)
Lifecycle.Event.ON_RESUME ->
if (chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete) {
chatController.showBackgroundServiceNoticeIfNeeded()
}
}
}
}

View File

@@ -1,9 +1,14 @@
package chat.simplex.app.model
import android.annotation.SuppressLint
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo
import android.content.Context
import android.content.SharedPreferences
import android.app.Application
import android.content.*
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import android.util.Log
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
@@ -13,6 +18,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat.startActivityForResult
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.views.call.*
@@ -526,38 +532,118 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt
chatModel.updateNetworkStatus(contact.id, Chat.NetworkStatus.Error(err))
}
fun showBackgroundServiceNotice() {
fun showBackgroundServiceNoticeIfNeeded() {
Log.d(TAG, "showBackgroundServiceNoticeIfNeeded")
if (!getBackgroundServiceNoticeShown()) {
AlertManager.shared.showAlert {
AlertDialog(
onDismissRequest = AlertManager.shared::hideAlert,
title = {
Row {
Icon(
Icons.Outlined.Bolt,
contentDescription = stringResource(R.string.icon_descr_instant_notifications),
)
Text(stringResource(R.string.private_instant_notifications), fontWeight = FontWeight.Bold)
}
},
text = {
Column {
Text(
annotatedStringResource(R.string.to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery),
Modifier.padding(bottom = 8.dp)
)
Text(annotatedStringResource(R.string.it_can_disabled_via_settings_notifications_still_shown))
}
},
confirmButton = {
Button(onClick = AlertManager.shared::hideAlert) { Text(stringResource(R.string.ok)) }
}
)
// the branch for the new users who has never seen service notice
if (isIgnoringBatteryOptimizations(appContext)) {
showBGServiceNotice()
} else {
showBGServiceNoticeIgnoreOptimization()
}
// set both flags, so that if the user doesn't allow ignoring optimizations, the service will be disabled without additional notice
setBackgroundServiceNoticeShown(true)
setBackgroundServiceBatteryNoticeShown(true)
} else if (!isIgnoringBatteryOptimizations(appContext) && getRunServiceInBackground()) {
// 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 (getBackgroundServiceBatteryNoticeShown()) {
// users have been presented with battery notice before - they did not allow ignoring optimizitions -> disable service
showDisablingServiceNotice()
setRunServiceInBackground(false)
chatModel.runServiceInBackground.value = false
} else {
// show battery optimization notice
showBGServiceNoticeIgnoreOptimization()
setBackgroundServiceBatteryNoticeShown(true)
}
setBackgroundServiceNoticeShown()
}
}
private fun showBGServiceNotice() = AlertManager.shared.showAlert {
AlertDialog(
onDismissRequest = AlertManager.shared::hideAlert,
title = {
Row {
Icon(
Icons.Outlined.Bolt,
contentDescription = stringResource(R.string.icon_descr_instant_notifications),
)
Text(stringResource(R.string.private_instant_notifications), fontWeight = FontWeight.Bold)
}
},
text = {
Column {
Text(
annotatedStringResource(R.string.to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery),
Modifier.padding(bottom = 8.dp)
)
Text(annotatedStringResource(R.string.it_can_disabled_via_settings_notifications_still_shown))
}
},
confirmButton = {
Button(onClick = AlertManager.shared::hideAlert) { Text(stringResource(R.string.ok)) }
}
)
}
private fun showBGServiceNoticeIgnoreOptimization() = AlertManager.shared.showAlert {
val ignoreOptimization = {
AlertManager.shared.hideAlert()
askAboutIgnoringBatteryOptimization(appContext)
}
AlertDialog(
onDismissRequest = ignoreOptimization,
title = {
Row {
Icon(
Icons.Outlined.Bolt,
contentDescription = stringResource(R.string.icon_descr_instant_notifications),
)
Text(stringResource(R.string.private_instant_notifications), fontWeight = FontWeight.Bold)
}
},
text = {
Column {
Text(
annotatedStringResource(R.string.to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery),
Modifier.padding(bottom = 8.dp)
)
Text(annotatedStringResource(R.string.turn_off_battery_optimization))
}
},
confirmButton = {
Button(onClick = ignoreOptimization) { Text(stringResource(R.string.ok)) }
}
)
}
private fun showDisablingServiceNotice() = AlertManager.shared.showAlert {
AlertDialog(
onDismissRequest = AlertManager.shared::hideAlert,
title = {
Row {
Icon(
Icons.Outlined.Bolt,
contentDescription = stringResource(R.string.icon_descr_instant_notifications),
)
Text(stringResource(R.string.private_instant_notifications_disabled), fontWeight = FontWeight.Bold)
}
},
text = {
Column {
Text(
annotatedStringResource(R.string.turning_off_background_service),
Modifier.padding(bottom = 8.dp)
)
}
},
confirmButton = {
Button(onClick = AlertManager.shared::hideAlert) { Text(stringResource(R.string.ok)) }
}
)
}
fun getAutoRestartWorkerVersion(): Int = sharedPreferences.getInt(SHARED_PREFS_AUTO_RESTART_WORKER_VERSION, 0)
fun setAutoRestartWorkerVersion(version: Int) =
@@ -572,18 +658,45 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt
.putBoolean(SHARED_PREFS_RUN_SERVICE_IN_BACKGROUND, runService)
.apply()
fun getBackgroundServiceNoticeShown(): Boolean = sharedPreferences.getBoolean(SHARED_PREFS_SERVICE_NOTICE_SHOWN, false)
private fun getBackgroundServiceNoticeShown(): Boolean = sharedPreferences.getBoolean(SHARED_PREFS_SERVICE_NOTICE_SHOWN, false)
fun setBackgroundServiceNoticeShown() =
fun setBackgroundServiceNoticeShown(shown: Boolean) =
sharedPreferences.edit()
.putBoolean(SHARED_PREFS_SERVICE_NOTICE_SHOWN, true)
.putBoolean(SHARED_PREFS_SERVICE_NOTICE_SHOWN, shown)
.apply()
private fun getBackgroundServiceBatteryNoticeShown(): Boolean = sharedPreferences.getBoolean(SHARED_PREFS_SERVICE_BATTERY_NOTICE_SHOWN, false)
fun setBackgroundServiceBatteryNoticeShown(shown: Boolean) =
sharedPreferences.edit()
.putBoolean(SHARED_PREFS_SERVICE_BATTERY_NOTICE_SHOWN, shown)
.apply()
fun isIgnoringBatteryOptimizations(context: Context): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return true
val powerManager = context.getSystemService(Application.POWER_SERVICE) as PowerManager
return powerManager.isIgnoringBatteryOptimizations(context.packageName)
}
private fun askAboutIgnoringBatteryOptimization(context: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return
Intent().apply {
@SuppressLint("BatteryLife")
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
data = Uri.parse("package:${context.packageName}")
// This flag is needed when you start a new activity from non-Activity context
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(this)
}
}
companion object {
private const val SHARED_PREFS_ID = "chat.simplex.app.SIMPLEX_APP_PREFS"
private const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion"
private const val SHARED_PREFS_RUN_SERVICE_IN_BACKGROUND = "RunServiceInBackground"
private const val SHARED_PREFS_SERVICE_NOTICE_SHOWN = "BackgroundServiceNoticeShown"
private const val SHARED_PREFS_SERVICE_BATTERY_NOTICE_SHOWN = "BackgroundServiceBatteryNoticeShown"
}
}

View File

@@ -115,8 +115,7 @@ fun createProfile(chatModel: ChatModel, displayName: String, fullName: String) {
)
chatModel.controller.startChat(user)
SimplexService.start(chatModel.controller.appContext)
// TODO show it later?
chatModel.controller.showBackgroundServiceNotice()
chatModel.controller.showBackgroundServiceNoticeIfNeeded()
chatModel.onboardingStage.value = OnboardingStage.OnboardingComplete
}
}

View File

@@ -37,6 +37,10 @@ fun SettingsView(chatModel: ChatModel) {
runServiceInBackground = chatModel.runServiceInBackground,
setRunServiceInBackground = { on ->
chatModel.controller.setRunServiceInBackground(on)
if (on && !chatModel.controller.isIgnoringBatteryOptimizations(chatModel.controller.appContext)) {
chatModel.controller.setBackgroundServiceNoticeShown(false)
}
chatModel.controller.showBackgroundServiceNoticeIfNeeded()
chatModel.runServiceInBackground.value = on
},
showModal = { modalView -> { ModalManager.shared.showModal { modalView(chatModel) } } },

View File

@@ -54,8 +54,11 @@
<!-- background service notice - SimpleXAPI.kt -->
<string name="private_instant_notifications">Приватные мгновенные уведомления!</string>
<string name="private_instant_notifications_disabled">Приватные уведомления выключены!</string>
<string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery">Чтобы защитить ваши личные данные, вместо уведомлений от сервера приложение запускает <b>фоновый сервис <xliff:g id="appName">SimpleX</xliff:g></b>, который потребляет несколько процентов батареи в день.</string>
<string name="it_can_disabled_via_settings_notifications_still_shown"><b>Он может быть выключен через Настройки</b> вы продолжите получать уведомления о сообщениях пока приложение запущено.</string>
<string name="turn_off_battery_optimization">Для использования приватных уведомлений, пожалуйста отключите оптимизацию батареи для <xliff:g id="appName">SimpleX</xliff:g> в слеующем диалоге. Иначе уведомления будут выключены.</string>
<string name="turning_off_background_service">Оптимизация батареи включена, поэтому приватные уведомления выключены. Вы можете снова включить их через Настройки.</string>
<!-- SimpleX Chat foreground Service -->
<string name="simplex_service_notification_title"><xliff:g id="appNameFull">SimpleX Chat</xliff:g> сервис</string>

View File

@@ -54,8 +54,11 @@
<!-- background service notice - SimpleXAPI.kt -->
<string name="private_instant_notifications">Private instant notifications!</string>
<string name="private_instant_notifications_disabled">Private notifications disabled!</string>
<string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery">To preserve your privacy, instead of push notifications the app has a <b><xliff:g id="appName">SimpleX</xliff:g> background service</b> it uses a few percent of the battery per day.</string>
<string name="it_can_disabled_via_settings_notifications_still_shown"><b>It can be disabled via settings</b> notifications will still be shown while the app is running.</string>
<string name="turn_off_battery_optimization">In order to use it, please <b>disable battery optimization</b> for <xliff:g id="appName">SimpleX</xliff:g> in the next dialog. Otherwise, the notifications will be disabled.</string>
<string name="turning_off_background_service">Battery optimization is active, turning off Private notifications. You can re-enable them via settings.</string>
<!-- SimpleX Chat foreground Service -->
<string name="simplex_service_notification_title"><xliff:g id="appNameFull">SimpleX Chat</xliff:g> service</string>