mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +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()
|
fun requireBackgroundSync()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launches infinite periodic background syncs
|
* Launches infinite self rescheduling background syncs via the WorkManager
|
||||||
* This does not work in doze mode :/
|
*
|
||||||
* If battery optimization is on it can work in app standby but that's all :/
|
* 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)
|
fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long)
|
||||||
|
|
||||||
|
|
|
@ -105,9 +105,8 @@ abstract class SyncService : Service() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Attempt to continue scheduling syncs after killed service is restarted
|
||||||
// It's ok to be not sticky because we will explicitly start it again on the next alarm?
|
return START_REDELIVER_INTENT
|
||||||
return START_NOT_STICKY
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|
|
@ -121,9 +121,9 @@ internal class SyncWorker(context: Context,
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
|
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
|
||||||
.setInitialDelay(delayInSeconds, TimeUnit.SECONDS)
|
.setInitialDelay(delayInSeconds, TimeUnit.SECONDS)
|
||||||
.build()
|
.build()
|
||||||
|
// Avoid risking multiple chains of syncs by replacing the existing chain
|
||||||
workManagerProvider.workManager
|
workManagerProvider.workManager
|
||||||
.enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
|
.enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopAnyBackgroundSync(workManagerProvider: WorkManagerProvider) {
|
fun stopAnyBackgroundSync(workManagerProvider: WorkManagerProvider) {
|
||||||
|
|
|
@ -29,7 +29,7 @@ import javax.inject.Inject
|
||||||
@MatrixScope
|
@MatrixScope
|
||||||
internal class BackgroundDetectionObserver @Inject constructor() : LifecycleObserver {
|
internal class BackgroundDetectionObserver @Inject constructor() : LifecycleObserver {
|
||||||
|
|
||||||
var isInBackground: Boolean = false
|
var isInBackground: Boolean = true
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="im.vector.app">
|
package="im.vector.app">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<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.
|
Required for long polling account synchronisation in background.
|
||||||
If not present ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent action won't work
|
If not present ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent action won't work
|
||||||
|
@ -24,6 +26,11 @@
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".fdroid.service.GuardService"
|
||||||
|
android:exported="false"
|
||||||
|
tools:ignore="Instantiatable" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</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,7 +27,6 @@ object BackgroundSyncStarter {
|
||||||
fun start(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) {
|
fun start(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) {
|
||||||
if (vectorPreferences.areNotificationEnabledForDevice()) {
|
if (vectorPreferences.areNotificationEnabledForDevice()) {
|
||||||
val activeSession = activeSessionHolder.getSafeActiveSession() ?: return
|
val activeSession = activeSessionHolder.getSafeActiveSession() ?: return
|
||||||
|
|
||||||
when (vectorPreferences.getFdroidSyncBackgroundMode()) {
|
when (vectorPreferences.getFdroidSyncBackgroundMode()) {
|
||||||
BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY -> {
|
BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY -> {
|
||||||
// we rely on periodic worker
|
// we rely on periodic worker
|
||||||
|
|
|
@ -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")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) {
|
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 arrow.core.Option
|
||||||
import im.vector.app.ActiveSessionDataSource
|
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.call.webrtc.WebRtcCallManager
|
||||||
import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
|
import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
|
||||||
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
|
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 callManager: WebRtcCallManager,
|
||||||
private val pushRuleTriggerListener: PushRuleTriggerListener,
|
private val pushRuleTriggerListener: PushRuleTriggerListener,
|
||||||
private val sessionListener: SessionListener,
|
private val sessionListener: SessionListener,
|
||||||
private val imageManager: ImageManager
|
private val imageManager: ImageManager,
|
||||||
|
private val guardServiceStarter: GuardServiceStarter
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var activeSession: AtomicReference<Session?> = AtomicReference()
|
private var activeSession: AtomicReference<Session?> = AtomicReference()
|
||||||
|
@ -52,6 +54,7 @@ class ActiveSessionHolder @Inject constructor(private val sessionObservableStore
|
||||||
pushRuleTriggerListener.startWithSession(session)
|
pushRuleTriggerListener.startWithSession(session)
|
||||||
session.callSignalingService().addCallListener(callManager)
|
session.callSignalingService().addCallListener(callManager)
|
||||||
imageManager.onSessionStarted(session)
|
imageManager.onSessionStarted(session)
|
||||||
|
guardServiceStarter.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearActiveSession() {
|
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) {
|
val notificationSubtitleRes = if (isInitialSync) {
|
||||||
R.string.notification_initial_sync
|
R.string.notification_initial_sync
|
||||||
} else {
|
} else {
|
||||||
R.string.notification_listening_for_events
|
R.string.notification_listening_for_notifications
|
||||||
}
|
}
|
||||||
val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false)
|
val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false)
|
||||||
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
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.VectorPreferenceCategory
|
||||||
import im.vector.app.core.preference.VectorSwitchPreference
|
import im.vector.app.core.preference.VectorSwitchPreference
|
||||||
import im.vector.app.core.pushers.PushersManager
|
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.isIgnoringBatteryOptimizations
|
||||||
import im.vector.app.core.utils.requestDisablingBatteryOptimization
|
import im.vector.app.core.utils.requestDisablingBatteryOptimization
|
||||||
import im.vector.app.features.notifications.NotificationUtils
|
import im.vector.app.features.notifications.NotificationUtils
|
||||||
|
@ -61,7 +62,8 @@ import javax.inject.Inject
|
||||||
class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
||||||
private val pushManager: PushersManager,
|
private val pushManager: PushersManager,
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val vectorPreferences: VectorPreferences
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
private val guardServiceStarter: GuardServiceStarter
|
||||||
) : VectorSettingsBaseFragment(),
|
) : VectorSettingsBaseFragment(),
|
||||||
BackgroundSyncModeChooserDialog.InteractionListener {
|
BackgroundSyncModeChooserDialog.InteractionListener {
|
||||||
|
|
||||||
|
@ -216,14 +218,19 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
||||||
it.isVisible = !FcmHelper.isPushSupported()
|
it.isVisible = !FcmHelper.isPushSupported()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val backgroundSyncEnabled = vectorPreferences.isBackgroundSyncEnabled()
|
||||||
findPreference<VectorEditTextPreference>(VectorPreferences.SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY)?.let {
|
findPreference<VectorEditTextPreference>(VectorPreferences.SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY)?.let {
|
||||||
it.isEnabled = vectorPreferences.isBackgroundSyncEnabled()
|
it.isEnabled = backgroundSyncEnabled
|
||||||
it.summary = secondsToText(vectorPreferences.backgroundSyncTimeOut())
|
it.summary = secondsToText(vectorPreferences.backgroundSyncTimeOut())
|
||||||
}
|
}
|
||||||
findPreference<VectorEditTextPreference>(VectorPreferences.SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY)?.let {
|
findPreference<VectorEditTextPreference>(VectorPreferences.SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY)?.let {
|
||||||
it.isEnabled = vectorPreferences.isBackgroundSyncEnabled()
|
it.isEnabled = backgroundSyncEnabled
|
||||||
it.summary = secondsToText(vectorPreferences.backgroundSyncDelay())
|
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_init">Initializing service</string>
|
||||||
<string name="notification_sync_in_progress">Synchronising…</string>
|
<string name="notification_sync_in_progress">Synchronising…</string>
|
||||||
<string name="notification_listening_for_events">Listening for events</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_noisy_notifications">Noisy notifications</string>
|
||||||
<string name="notification_silent_notifications">Silent notifications</string>
|
<string name="notification_silent_notifications">Silent notifications</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue