From 71b27bfd5d310648b6fe2f2f237c4c80ac686b9e Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 21 Oct 2021 19:14:33 +0100 Subject: [PATCH] making the guard service a noop, lifeline scheduling will be brought back if we confirm the service by itself is not enough - reuses the sync foreground notification for the guard foreground service --- .../java/im/vector/app/di/FlavorModule.kt | 40 +++++ .../app/fdroid/BackgroundSyncStarter.kt | 15 -- .../service/FDroidGuardServiceStarter.kt | 49 +++++++ .../vector/app/fdroid/service/GuardService.kt | 138 ++---------------- .../java/im/vector/app/di/FlavorModule.kt | 37 +++++ .../java/im/vector/app/VectorApplication.kt | 3 + .../im/vector/app/core/di/SingletonModule.kt | 1 + .../app/core/services/GuardServiceStarter.kt | 22 +++ ...rSettingsNotificationPreferenceFragment.kt | 13 +- 9 files changed, 178 insertions(+), 140 deletions(-) create mode 100644 vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt create mode 100644 vector/src/fdroid/java/im/vector/app/fdroid/service/FDroidGuardServiceStarter.kt create mode 100644 vector/src/gplay/java/im/vector/app/di/FlavorModule.kt create mode 100644 vector/src/main/java/im/vector/app/core/services/GuardServiceStarter.kt diff --git a/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt b/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt new file mode 100644 index 0000000000..39b5c4a976 --- /dev/null +++ b/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.di + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import im.vector.app.core.services.GuardServiceStarter +import im.vector.app.fdroid.service.FDroidGuardServiceStarter +import im.vector.app.features.settings.VectorPreferences + +@Module +@InstallIn(SingletonComponent::class) +abstract class FlavorModule { + + companion object { + + @Provides + @JvmStatic + fun provideGuardServiceStarter(preferences: VectorPreferences, appContext: Context): GuardServiceStarter { + return FDroidGuardServiceStarter(preferences, appContext) + } + } +} diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt b/vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt index 1e2ed58a17..f952fe6abe 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt +++ b/vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt @@ -31,21 +31,6 @@ object BackgroundSyncStarter { fun start(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) { if (vectorPreferences.areNotificationEnabledForDevice()) { val activeSession = activeSessionHolder.getSafeActiveSession() ?: return - - val intent = Intent(context, GuardService::class.java) - if (vectorPreferences.getFdroidSyncBackgroundMode() == BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME) { - intent.putExtra(SyncService.EXTRA_SESSION_ID, activeSession.sessionId) - } else { - intent.putExtra(SyncService.EXTRA_SESSION_ID, "") // this assures the GuardService runs, but will not start VectorSyncService - } - intent.putExtra(SyncService.EXTRA_DELAY_SECONDS, vectorPreferences.backgroundSyncDelay()) - try { - Timber.i("## Sync: starting GuardService") - ContextCompat.startForegroundService(context, intent) - } catch (ex: Throwable) { - Timber.e("## Sync: ERROR starting GuardService") - } - when (vectorPreferences.getFdroidSyncBackgroundMode()) { BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY -> { // we rely on periodic worker diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/service/FDroidGuardServiceStarter.kt b/vector/src/fdroid/java/im/vector/app/fdroid/service/FDroidGuardServiceStarter.kt new file mode 100644 index 0000000000..7a1c4dbe0b --- /dev/null +++ b/vector/src/fdroid/java/im/vector/app/fdroid/service/FDroidGuardServiceStarter.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.fdroid.service + +import android.content.Context +import android.content.Intent +import androidx.core.content.ContextCompat +import im.vector.app.core.services.GuardServiceStarter +import im.vector.app.features.settings.VectorPreferences +import timber.log.Timber +import javax.inject.Inject + +class FDroidGuardServiceStarter @Inject constructor( + private val preferences: VectorPreferences, + private val appContext: Context +) : GuardServiceStarter { + + override fun start() { + if (preferences.isBackgroundSyncEnabled()) { + try { + Timber.i("## Sync: starting GuardService") + val intent = Intent(appContext, GuardService::class.java) + ContextCompat.startForegroundService(appContext, intent) + } catch (ex: Throwable) { + Timber.e("## Sync: ERROR starting GuardService") + } + } + } + + override fun stop() { + val intent = Intent(appContext, GuardService::class.java) + appContext.stopService(intent) + } +} + diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/service/GuardService.kt b/vector/src/fdroid/java/im/vector/app/fdroid/service/GuardService.kt index 16f8331586..631efdbc51 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/service/GuardService.kt +++ b/vector/src/fdroid/java/im/vector/app/fdroid/service/GuardService.kt @@ -18,140 +18,34 @@ package im.vector.app.fdroid.service import android.app.Service import android.content.Intent import android.os.IBinder -import androidx.core.content.getSystemService -import timber.log.Timber +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.extensions.vectorComponent import im.vector.app.features.notifications.NotificationUtils -import java.util.Timer -import java.util.TimerTask -import im.vector.app.core.services.VectorSyncService -import androidx.core.content.ContextCompat -import org.matrix.android.sdk.internal.session.sync.job.SyncService -import android.app.AlarmManager -import android.os.Build -import android.app.PendingIntent -import im.vector.app.features.settings.BackgroundSyncMode +import timber.log.Timber +import javax.inject.Inject /** + * This no-op foreground service acts as a deterrent for the system + * to avoid eagerly killing the app process. + * Keeping the app process alive avoids some OEMs ignoring scheduled WorkManager and AlarmManager tasks + * when the app is not in the foreground. */ +@AndroidEntryPoint class GuardService : Service() { - private var timer = Timer() - private var sessionId: String? = null - private var running: Boolean = false + @Inject lateinit var notificationUtils: NotificationUtils - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Timber.i("## Sync: onStartCommand GuardService running:$running") - if (running) { - if (intent != null) { - val lifeLine = intent.getBooleanExtra(EXTRA_LIFELINE, false) - if (lifeLine) { - // called from lifeLine? - scheduleLifeLine() - return START_STICKY - } - } - } - try { - timer.cancel() - } catch (ex: Exception) { - } - val notificationSubtitleRes = R.string.notification_listening_for_events - val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false) - startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE + 1, notification) - try { - val sharedPref = getSharedPreferences(PREF_NAME_SESSION_ID, 0) - var delayInSeconds = BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS - if (intent != null) { - sessionId = intent.getStringExtra(SyncService.EXTRA_SESSION_ID) - delayInSeconds = intent.getIntExtra(SyncService.EXTRA_DELAY_SECONDS, BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS) - val lifeLine = intent.getBooleanExtra(EXTRA_LIFELINE, false) - if (lifeLine) { - Timber.i("## Sync: GuardService restarted by lifeLine") - } - if (sessionId.isNullOrEmpty()) { - Timber.i("## Sync: GuardService getting sessionId from sharedPreferences") - sessionId = sharedPref.getString(PREF_NAME_SESSION_ID, null) - } else { - Timber.i("## Sync: GuardService saving sessionId to sharedPreferences") - val editor = sharedPref.edit() - editor.putString(PREF_NAME_SESSION_ID, sessionId) - editor.apply() - } - } else { - Timber.i("## Sync: GuardService intent is null in GuardService, getting sessionId from sharedPreferences") - sessionId = sharedPref.getString(PREF_NAME_SESSION_ID, null) - } - timer = Timer() - timer.scheduleAtFixedRate(object : TimerTask() { - override fun run() { - if (sessionId.isNullOrEmpty()) { - Timber.i("## Sync: timer still alive GuardService sessionId:nullOrEmpty") - } else { - Timber.i("## Sync: timer still alive GuardService sessionId:$sessionId") - try { - val syncIntent = Intent(applicationContext, VectorSyncService::class.java) - syncIntent.putExtra(SyncService.EXTRA_SESSION_ID, sessionId) - syncIntent.putExtra(SyncService.EXTRA_PERIODIC, true) - ContextCompat.startForegroundService(applicationContext, syncIntent) - } catch (ex: Throwable) { - } - } - } - }, delayInSeconds * 1000L, delayInSeconds * 1000L) - } catch (ex: Exception) { - Timber.e("## Sync: error in GuardService when creating timer") - } - if (!running) { - scheduleLifeLine() - } - running = true - return START_STICKY - } - - private lateinit var notificationUtils: NotificationUtils + override fun onBind(intent: Intent?): IBinder? = null override fun onCreate() { - Timber.i("## Sync: onCreate GuardService") super.onCreate() - notificationUtils = vectorComponent().notificationUtils() + Timber.i("## Sync: onCreate GuardService") } - override fun onDestroy() { - Timber.i("## Sync: onDestroy GuardService") - try { - timer.cancel() - } catch (ex: Exception) { - } - super.onDestroy() - } - - override fun onBind(intent: Intent?): IBinder? { - return null - } - - private fun scheduleLifeLine() { - Timber.d("## Sync: GuardService scheduleLifeLine") - val intent = Intent(applicationContext, GuardService::class.java) - intent.putExtra(EXTRA_LIFELINE, true) - - val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - PendingIntent.getForegroundService(this, 0, intent, 0) - } else { - PendingIntent.getService(this, 0, intent, 0) - } - val firstMillis = System.currentTimeMillis() + 60 * 1000L - val alarmMgr = getSystemService()!! - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) - } else { - alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) - } - } - - companion object { - const val PREF_NAME_SESSION_ID = "GuardServiceLastActiveSessionId" - const val EXTRA_LIFELINE = "GuardServiceLifeLine" + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + val notificationSubtitleRes = R.string.notification_listening_for_events + val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false) + startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification) + return START_STICKY } } diff --git a/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt b/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt new file mode 100644 index 0000000000..61fd476c87 --- /dev/null +++ b/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import im.vector.app.core.services.GuardServiceStarter + +@InstallIn(SingletonComponent::class) +@Module +abstract class FlavorModule { + + companion object { + + @Provides + @JvmStatic + fun provideGuardServiceStarter(): GuardServiceStarter { + return object : GuardServiceStarter {} + } + } +} diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index d9027231da..9b3db9d4cf 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -44,6 +44,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.configureAndStart import im.vector.app.core.extensions.startSyncing import im.vector.app.core.rx.RxConfig +import im.vector.app.core.services.GuardServiceStarter import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.configuration.VectorConfiguration import im.vector.app.features.disclaimer.doNotShowDisclaimerDialog @@ -99,6 +100,7 @@ class VectorApplication : @Inject lateinit var callManager: WebRtcCallManager @Inject lateinit var invitesAcceptor: InvitesAcceptor @Inject lateinit var vectorFileLogger: VectorFileLogger + @Inject lateinit var guardServiceStarter: GuardServiceStarter // font thread handler private var fontThreadHandler: Handler? = null @@ -164,6 +166,7 @@ class VectorApplication : if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!! activeSessionHolder.setActiveSession(lastAuthenticatedSession) + guardServiceStarter.start() lastAuthenticatedSession.configureAndStart(applicationContext, startSyncing = false) } diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt index e89a060022..b4e0700d5f 100644 --- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt @@ -29,6 +29,7 @@ import dagger.hilt.components.SingletonComponent import im.vector.app.core.dispatchers.CoroutineDispatchers import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.ErrorFormatter +import im.vector.app.di.FlavorModule import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.CompileTimeAutoAcceptInvites import im.vector.app.features.navigation.DefaultNavigator diff --git a/vector/src/main/java/im/vector/app/core/services/GuardServiceStarter.kt b/vector/src/main/java/im/vector/app/core/services/GuardServiceStarter.kt new file mode 100644 index 0000000000..71c6816cea --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/services/GuardServiceStarter.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.services + +interface GuardServiceStarter { + fun start() {} + fun stop() {} +} diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt index b014b3d2dc..4199bd1753 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt @@ -37,6 +37,7 @@ import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.core.pushers.PushersManager +import im.vector.app.core.services.GuardServiceStarter import im.vector.app.core.utils.isIgnoringBatteryOptimizations import im.vector.app.core.utils.requestDisablingBatteryOptimization import im.vector.app.features.notifications.NotificationUtils @@ -61,7 +62,8 @@ import javax.inject.Inject class VectorSettingsNotificationPreferenceFragment @Inject constructor( private val pushManager: PushersManager, private val activeSessionHolder: ActiveSessionHolder, - private val vectorPreferences: VectorPreferences + private val vectorPreferences: VectorPreferences, + private val guardServiceStarter: GuardServiceStarter ) : VectorSettingsBaseFragment(), BackgroundSyncModeChooserDialog.InteractionListener { @@ -216,14 +218,19 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( it.isVisible = !FcmHelper.isPushSupported() } + val backgroundSyncEnabled = vectorPreferences.isBackgroundSyncEnabled() findPreference(VectorPreferences.SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY)?.let { - it.isEnabled = vectorPreferences.isBackgroundSyncEnabled() + it.isEnabled = backgroundSyncEnabled it.summary = secondsToText(vectorPreferences.backgroundSyncTimeOut()) } findPreference(VectorPreferences.SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY)?.let { - it.isEnabled = vectorPreferences.isBackgroundSyncEnabled() + it.isEnabled = backgroundSyncEnabled it.summary = secondsToText(vectorPreferences.backgroundSyncDelay()) } + when { + backgroundSyncEnabled -> guardServiceStarter.start() + else -> guardServiceStarter.stop() + } } /**