mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 18:35:40 +03:00
Merge pull request #4314 from vector-im/feature/adm/fdroid-notification-reliability
F-Droid variant permanent notification to fix missing notifications
This commit is contained in:
commit
6f58cbd6c5
17 changed files with 226 additions and 18 deletions
1
changelog.d/4298.bugfix
Normal file
1
changelog.d/4298.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fixing missing F-Droid notifications when in background due to background syncs not triggering
|
|
@ -120,9 +120,11 @@ interface Session :
|
|||
fun requireBackgroundSync()
|
||||
|
||||
/**
|
||||
* Launches infinite periodic background syncs
|
||||
* This does not work in doze mode :/
|
||||
* If battery optimization is on it can work in app standby but that's all :/
|
||||
* Launches infinite self rescheduling background syncs via the WorkManager
|
||||
*
|
||||
* While dozing, syncs will only occur during maintenance windows
|
||||
* For reliability it's recommended to also start a long running foreground service
|
||||
* along with disabling battery optimizations
|
||||
*/
|
||||
fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long)
|
||||
|
||||
|
|
|
@ -105,9 +105,8 @@ abstract class SyncService : Service() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// It's ok to be not sticky because we will explicitly start it again on the next alarm?
|
||||
return START_NOT_STICKY
|
||||
// Attempt to continue scheduling syncs after killed service is restarted
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
@ -121,9 +121,9 @@ internal class SyncWorker(context: Context,
|
|||
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
|
||||
.setInitialDelay(delayInSeconds, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
// Avoid risking multiple chains of syncs by replacing the existing chain
|
||||
workManagerProvider.workManager
|
||||
.enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
|
||||
.enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest)
|
||||
}
|
||||
|
||||
fun stopAnyBackgroundSync(workManagerProvider: WorkManagerProvider) {
|
||||
|
|
|
@ -29,7 +29,7 @@ import javax.inject.Inject
|
|||
@MatrixScope
|
||||
internal class BackgroundDetectionObserver @Inject constructor() : LifecycleObserver {
|
||||
|
||||
var isInBackground: Boolean = false
|
||||
var isInBackground: Boolean = true
|
||||
private set
|
||||
|
||||
private
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="im.vector.app">
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<!--
|
||||
Required for long polling account synchronisation in background.
|
||||
If not present ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent action won't work
|
||||
|
@ -24,6 +26,11 @@
|
|||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".fdroid.service.GuardService"
|
||||
android:exported="false"
|
||||
tools:ignore="Instantiatable" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
40
vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt
Normal file
40
vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,9 +27,8 @@ object BackgroundSyncStarter {
|
|||
fun start(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) {
|
||||
if (vectorPreferences.areNotificationEnabledForDevice()) {
|
||||
val activeSession = activeSessionHolder.getSafeActiveSession() ?: return
|
||||
|
||||
when (vectorPreferences.getFdroidSyncBackgroundMode()) {
|
||||
BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY -> {
|
||||
BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY -> {
|
||||
// we rely on periodic worker
|
||||
Timber.i("## Sync: Work scheduled to periodically sync in ${vectorPreferences.backgroundSyncDelay()}s")
|
||||
activeSession.startAutomaticBackgroundSync(
|
||||
|
@ -42,7 +41,7 @@ object BackgroundSyncStarter {
|
|||
AlarmSyncBroadcastReceiver.scheduleAlarm(context, activeSession.sessionId, vectorPreferences.backgroundSyncDelay())
|
||||
Timber.i("## Sync: Alarm scheduled to start syncing")
|
||||
}
|
||||
BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED -> {
|
||||
BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED -> {
|
||||
// we do nothing
|
||||
Timber.i("## Sync: background sync is disabled")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.Intent
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.services.VectorService
|
||||
import im.vector.app.features.notifications.NotificationUtils
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This no-op foreground service acts as a deterrent to the system 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 : VectorService() {
|
||||
|
||||
@Inject lateinit var notificationUtils: NotificationUtils
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val notificationSubtitleRes = R.string.notification_listening_for_notifications
|
||||
val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false)
|
||||
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
||||
return START_STICKY
|
||||
}
|
||||
}
|
37
vector/src/gplay/java/im/vector/app/di/FlavorModule.kt
Normal file
37
vector/src/gplay/java/im/vector/app/di/FlavorModule.kt
Normal file
|
@ -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 {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -108,6 +108,6 @@ object FcmHelper {
|
|||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) {
|
||||
// TODO FCM fallback
|
||||
// No op
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.core.di
|
|||
|
||||
import arrow.core.Option
|
||||
import im.vector.app.ActiveSessionDataSource
|
||||
import im.vector.app.core.services.GuardServiceStarter
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
|
||||
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
|
||||
|
@ -36,7 +37,8 @@ class ActiveSessionHolder @Inject constructor(private val sessionObservableStore
|
|||
private val callManager: WebRtcCallManager,
|
||||
private val pushRuleTriggerListener: PushRuleTriggerListener,
|
||||
private val sessionListener: SessionListener,
|
||||
private val imageManager: ImageManager
|
||||
private val imageManager: ImageManager,
|
||||
private val guardServiceStarter: GuardServiceStarter
|
||||
) {
|
||||
|
||||
private var activeSession: AtomicReference<Session?> = AtomicReference()
|
||||
|
@ -52,6 +54,7 @@ class ActiveSessionHolder @Inject constructor(private val sessionObservableStore
|
|||
pushRuleTriggerListener.startWithSession(session)
|
||||
session.callSignalingService().addCallListener(callManager)
|
||||
imageManager.onSessionStarted(session)
|
||||
guardServiceStarter.start()
|
||||
}
|
||||
|
||||
fun clearActiveSession() {
|
||||
|
|
|
@ -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() {}
|
||||
}
|
|
@ -83,7 +83,7 @@ class VectorSyncService : SyncService() {
|
|||
val notificationSubtitleRes = if (isInitialSync) {
|
||||
R.string.notification_initial_sync
|
||||
} else {
|
||||
R.string.notification_listening_for_events
|
||||
R.string.notification_listening_for_notifications
|
||||
}
|
||||
val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false)
|
||||
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
||||
|
|
|
@ -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<VectorEditTextPreference>(VectorPreferences.SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY)?.let {
|
||||
it.isEnabled = vectorPreferences.isBackgroundSyncEnabled()
|
||||
it.isEnabled = backgroundSyncEnabled
|
||||
it.summary = secondsToText(vectorPreferences.backgroundSyncTimeOut())
|
||||
}
|
||||
findPreference<VectorEditTextPreference>(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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -322,6 +322,7 @@
|
|||
<string name="notification_sync_init">Initializing service</string>
|
||||
<string name="notification_sync_in_progress">Synchronising…</string>
|
||||
<string name="notification_listening_for_events">Listening for events</string>
|
||||
<string name="notification_listening_for_notifications">Listening for notifications</string>
|
||||
<string name="notification_noisy_notifications">Noisy notifications</string>
|
||||
<string name="notification_silent_notifications">Silent notifications</string>
|
||||
|
||||
|
|
Loading…
Reference in a new issue