diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt index 5d71b4f17b..163d09a8af 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt @@ -70,7 +70,7 @@ class MainActivity: FragmentActivity(), LifecycleEventObserver { } } } - schedulePeriodicServiceRestartWorker() + SimplexApp.context.schedulePeriodicServiceRestartWorker() } override fun onNewIntent(intent: Intent?) { @@ -131,24 +131,6 @@ class MainActivity: FragmentActivity(), LifecycleEventObserver { } } - private fun schedulePeriodicServiceRestartWorker() { - val workerVersion = chatController.appPrefs.autoRestartWorkerVersion.get() - val workPolicy = if (workerVersion == SimplexService.SERVICE_START_WORKER_VERSION) { - Log.d(TAG, "ServiceStartWorker version matches: choosing KEEP as existing work policy") - ExistingPeriodicWorkPolicy.KEEP - } else { - Log.d(TAG, "ServiceStartWorker version DOES NOT MATCH: choosing REPLACE as existing work policy") - chatController.appPrefs.autoRestartWorkerVersion.set(SimplexService.SERVICE_START_WORKER_VERSION) - ExistingPeriodicWorkPolicy.REPLACE - } - val work = PeriodicWorkRequestBuilder(SimplexService.SERVICE_START_WORKER_INTERVAL_MINUTES, TimeUnit.MINUTES) - .addTag(SimplexService.TAG) - .addTag(SimplexService.SERVICE_START_WORKER_WORK_NAME_PERIODIC) - .build() - Log.d(TAG, "ServiceStartWorker: Scheduling period work every ${SimplexService.SERVICE_START_WORKER_INTERVAL_MINUTES} minutes") - WorkManager.getInstance(this)?.enqueueUniquePeriodicWork(SimplexService.SERVICE_START_WORKER_WORK_NAME_PERIODIC, workPolicy, work) - } - private fun setPerformLA(on: Boolean) { vm.chatModel.controller.appPrefs.laNoticeShown.set(true) if (on) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt index 7d0e1620ec..39a4806add 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt @@ -4,14 +4,17 @@ import android.app.Application import android.net.LocalServerSocket import android.util.Log import androidx.lifecycle.* +import androidx.work.* import chat.simplex.app.model.* import chat.simplex.app.views.helpers.getFilesDirectory import chat.simplex.app.views.helpers.withApi import chat.simplex.app.views.onboarding.OnboardingStage +import kotlinx.coroutines.* import java.io.BufferedReader import java.io.InputStreamReader import java.util.* import java.util.concurrent.Semaphore +import java.util.concurrent.TimeUnit import kotlin.concurrent.thread const val TAG = "SIMPLEX" @@ -57,8 +60,6 @@ class SimplexApp: Application(), LifecycleEventObserver { chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo } else { chatController.startChat(user) - SimplexService.start(applicationContext) - chatController.showBackgroundServiceNoticeIfNeeded() } } } @@ -80,6 +81,34 @@ class SimplexApp: Application(), LifecycleEventObserver { } } + fun allowToStartServiceAfterAppExit() = with(chatModel.controller) { + appPrefs.runServiceInBackground.get() && isIgnoringBatteryOptimizations(chatModel.controller.appContext) + } + + /* + * It takes 1-10 milliseconds to process this function. Better to do it in a background thread + * */ + fun schedulePeriodicServiceRestartWorker() = CoroutineScope(Dispatchers.Default).launch { + if (!allowToStartServiceAfterAppExit()) { + return@launch + } + val workerVersion = chatController.appPrefs.autoRestartWorkerVersion.get() + val workPolicy = if (workerVersion == SimplexService.SERVICE_START_WORKER_VERSION) { + Log.d(TAG, "ServiceStartWorker version matches: choosing KEEP as existing work policy") + ExistingPeriodicWorkPolicy.KEEP + } else { + Log.d(TAG, "ServiceStartWorker version DOES NOT MATCH: choosing REPLACE as existing work policy") + chatController.appPrefs.autoRestartWorkerVersion.set(SimplexService.SERVICE_START_WORKER_VERSION) + ExistingPeriodicWorkPolicy.REPLACE + } + val work = PeriodicWorkRequestBuilder(SimplexService.SERVICE_START_WORKER_INTERVAL_MINUTES, TimeUnit.MINUTES) + .addTag(SimplexService.TAG) + .addTag(SimplexService.SERVICE_START_WORKER_WORK_NAME_PERIODIC) + .build() + Log.d(TAG, "ServiceStartWorker: Scheduling period work every ${SimplexService.SERVICE_START_WORKER_INTERVAL_MINUTES} minutes") + WorkManager.getInstance(context)?.enqueueUniquePeriodicWork(SimplexService.SERVICE_START_WORKER_WORK_NAME_PERIODIC, workPolicy, work) + } + companion object { lateinit var context: SimplexApp private set diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexService.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexService.kt index a9426a062e..134c1591ef 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/SimplexService.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexService.kt @@ -2,6 +2,7 @@ package chat.simplex.app import android.app.* import android.content.* +import android.content.pm.PackageManager import android.os.* import android.provider.Settings import android.util.Log @@ -54,7 +55,10 @@ class SimplexService: Service() { override fun onDestroy() { Log.d(TAG, "Simplex service destroyed") stopService() - sendBroadcast(Intent(this, AutoRestartReceiver::class.java)) // Restart if necessary! + + // If private notifications are enabled and battery optimization is disabled, restart the service + if (SimplexApp.context.allowToStartServiceAfterAppExit()) + sendBroadcast(Intent(this, AutoRestartReceiver::class.java)) super.onDestroy() } @@ -147,6 +151,11 @@ class SimplexService: Service() { // re-schedules the task when "Clear recent apps" is pressed override fun onTaskRemoved(rootIntent: Intent) { + // If private notifications aren't enabled or battery optimization isn't disabled, we shouldn't restart the service + if (!SimplexApp.context.allowToStartServiceAfterAppExit()) { + return + } + val restartServiceIntent = Intent(applicationContext, SimplexService::class.java).also { it.setPackage(packageName) }; @@ -162,6 +171,17 @@ class SimplexService: Service() { Log.d(TAG, "StartReceiver: onReceive called") scheduleStart(context) } + companion object { + fun toggleReceiver(enable: Boolean) { + Log.d(TAG, "StartReceiver: toggleReceiver enabled: $enable") + val component = ComponentName(BuildConfig.APPLICATION_ID, StartReceiver::class.java.name) + SimplexApp.context.packageManager.setComponentEnabledSetting( + component, + if (enable) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP + ) + } + } } // restart on destruction 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 138d452083..7700bd8ef0 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 @@ -5,6 +5,7 @@ import android.app.ActivityManager import android.app.ActivityManager.RunningAppProcessInfo import android.app.Application import android.content.* +import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.PowerManager @@ -890,7 +891,7 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager fun showBackgroundServiceNoticeIfNeeded() { Log.d(TAG, "showBackgroundServiceNoticeIfNeeded") if (!appPrefs.backgroundServiceNoticeShown.get()) { - // the branch for the new users who has never seen service notice + // the branch for the new users who have never seen service notice if (isIgnoringBatteryOptimizations(appContext)) { showBGServiceNotice() } else { @@ -903,15 +904,19 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager // 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 optimizitions -> disable service + // users have been presented with battery notice before - they did not allow ignoring optimizations -> disable service showDisablingServiceNotice() appPrefs.runServiceInBackground.set(false) chatModel.runServiceInBackground.value = false + SimplexService.StartReceiver.toggleReceiver(false) } else { // show battery optimization notice showBGServiceNoticeIgnoreOptimization() appPrefs.backgroundServiceBatteryNoticeShown.set(true) } + } else { + // service is allowed and battery optimization is disabled + SimplexApp.context.schedulePeriodicServiceRestartWorker() } } 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 74d8970a20..8d5358c52b 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 @@ -23,7 +23,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.* -import chat.simplex.app.BuildConfig +import chat.simplex.app.* import chat.simplex.app.R import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* @@ -44,6 +44,7 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) { } chatModel.controller.showBackgroundServiceNoticeIfNeeded() chatModel.runServiceInBackground.value = on + SimplexService.StartReceiver.toggleReceiver(on) } if (user != null) {