diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml
index 5062b90ed6..9d3d94390d 100644
--- a/apps/android/app/src/main/AndroidManifest.xml
+++ b/apps/android/app/src/main/AndroidManifest.xml
@@ -16,6 +16,7 @@
+
SimplexService.start(applicationContext)
+ Lifecycle.Event.ON_RESUME ->
+ if (chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete) {
+ chatController.showBackgroundServiceNoticeIfNeeded()
+ }
}
}
}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt
index 9f2e2835e8..daa0e62041 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt
@@ -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"
}
}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt
index d26f142f35..6703df1aa0 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt
@@ -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
}
}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt
index 64f7610bc9..b09b6e7484 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt
@@ -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) } } },
diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml
index 9496535853..9667b24111 100644
--- a/apps/android/app/src/main/res/values-ru/strings.xml
+++ b/apps/android/app/src/main/res/values-ru/strings.xml
@@ -54,8 +54,11 @@
Приватные мгновенные уведомления!
+ Приватные уведомления выключены!
Чтобы защитить ваши личные данные, вместо уведомлений от сервера приложение запускает фоновый сервис SimpleX, который потребляет несколько процентов батареи в день.
Он может быть выключен через Настройки – вы продолжите получать уведомления о сообщениях пока приложение запущено.
+ Для использования приватных уведомлений, пожалуйста отключите оптимизацию батареи для SimpleX в слеующем диалоге. Иначе уведомления будут выключены.
+ Оптимизация батареи включена, поэтому приватные уведомления выключены. Вы можете снова включить их через Настройки.
SimpleX Chat сервис
diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml
index 5dce4ad6ea..7143c0e314 100644
--- a/apps/android/app/src/main/res/values/strings.xml
+++ b/apps/android/app/src/main/res/values/strings.xml
@@ -54,8 +54,11 @@
Private instant notifications!
+ Private notifications disabled!
To preserve your privacy, instead of push notifications the app has a SimpleX background service – it uses a few percent of the battery per day.
It can be disabled via settings – notifications will still be shown while the app is running.
+ In order to use it, please disable battery optimization for SimpleX in the next dialog. Otherwise, the notifications will be disabled.
+ Battery optimization is active, turning off Private notifications. You can re-enable them via settings.
SimpleX Chat service