Fix issues with WorkManager on Android 12 and below (#5606)
* Add `getForegroundInfo` implementation to try to fix issues with WorkManager on Android 12 and below This may be a MIUI-only issue as I couldn't reproduce it with several emulators on Android 11, 12 and 13. * Use `setExpedited` only on Android 13 or higher, it's not needed on older versions * Use an actual string resource, fix tests * Fix review comments * Fix broken test with Element Pro: Instead of using Robolectric with API < 33 (since Pro uses minSdk 33) use a `BuildVersionSdkIntProvider` * Remove `getForegroundInfo` and the associated permission, as we expect it to be dead code * Fix lint issues * Cleanup NotificationIdProvider --------- Co-authored-by: Benoit Marty <benoit@matrix.org>
This commit is contained in:
parent
08f75dda4c
commit
7ed888af83
8 changed files with 49 additions and 11 deletions
|
|
@ -31,10 +31,6 @@ object NotificationIdProvider {
|
||||||
return getOffset(sessionId) + FALLBACK_NOTIFICATION_ID
|
return getOffset(sessionId) + FALLBACK_NOTIFICATION_ID
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCallNotificationId(sessionId: SessionId): Int {
|
|
||||||
return getOffset(sessionId) + ROOM_CALL_NOTIFICATION_ID
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getForegroundServiceNotificationId(type: ForegroundServiceType): Int {
|
fun getForegroundServiceNotificationId(type: ForegroundServiceType): Int {
|
||||||
return type.id * 10 + FOREGROUND_SERVICE_NOTIFICATION_ID
|
return type.id * 10 + FOREGROUND_SERVICE_NOTIFICATION_ID
|
||||||
}
|
}
|
||||||
|
|
@ -49,7 +45,6 @@ object NotificationIdProvider {
|
||||||
private const val ROOM_MESSAGES_NOTIFICATION_ID = 1
|
private const val ROOM_MESSAGES_NOTIFICATION_ID = 1
|
||||||
private const val ROOM_EVENT_NOTIFICATION_ID = 2
|
private const val ROOM_EVENT_NOTIFICATION_ID = 2
|
||||||
private const val ROOM_INVITATION_NOTIFICATION_ID = 3
|
private const val ROOM_INVITATION_NOTIFICATION_ID = 3
|
||||||
private const val ROOM_CALL_NOTIFICATION_ID = 3
|
|
||||||
|
|
||||||
private const val FOREGROUND_SERVICE_NOTIFICATION_ID = 4
|
private const val FOREGROUND_SERVICE_NOTIFICATION_ID = 4
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
|
||||||
<application>
|
<application>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEv
|
||||||
import io.element.android.libraries.push.impl.workmanager.SyncNotificationWorkManagerRequest
|
import io.element.android.libraries.push.impl.workmanager.SyncNotificationWorkManagerRequest
|
||||||
import io.element.android.libraries.push.impl.workmanager.WorkerDataConverter
|
import io.element.android.libraries.push.impl.workmanager.WorkerDataConverter
|
||||||
import io.element.android.libraries.workmanager.api.WorkManagerScheduler
|
import io.element.android.libraries.workmanager.api.WorkManagerScheduler
|
||||||
|
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
@ -49,6 +50,7 @@ class DefaultNotificationResolverQueue(
|
||||||
private val workManagerScheduler: WorkManagerScheduler,
|
private val workManagerScheduler: WorkManagerScheduler,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
private val workerDataConverter: WorkerDataConverter,
|
private val workerDataConverter: WorkerDataConverter,
|
||||||
|
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
|
||||||
) : NotificationResolverQueue {
|
) : NotificationResolverQueue {
|
||||||
companion object {
|
companion object {
|
||||||
private const val BATCH_WINDOW_MS = 250L
|
private const val BATCH_WINDOW_MS = 250L
|
||||||
|
|
@ -100,6 +102,7 @@ class DefaultNotificationResolverQueue(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
notificationEventRequests = requests,
|
notificationEventRequests = requests,
|
||||||
workerDataConverter = workerDataConverter,
|
workerDataConverter = workerDataConverter,
|
||||||
|
buildVersionSdkIntProvider = buildVersionSdkIntProvider,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import io.element.android.libraries.push.impl.notifications.NotificationResolver
|
||||||
import io.element.android.libraries.workmanager.api.WorkManagerScheduler
|
import io.element.android.libraries.workmanager.api.WorkManagerScheduler
|
||||||
import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory
|
import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory
|
||||||
import io.element.android.libraries.workmanager.api.di.WorkerKey
|
import io.element.android.libraries.workmanager.api.di.WorkerKey
|
||||||
|
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -39,7 +40,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
class FetchNotificationsWorker(
|
class FetchNotificationsWorker(
|
||||||
@Assisted workerParams: WorkerParameters,
|
@Assisted workerParams: WorkerParameters,
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val networkMonitor: NetworkMonitor,
|
private val networkMonitor: NetworkMonitor,
|
||||||
private val eventResolver: NotifiableEventResolver,
|
private val eventResolver: NotifiableEventResolver,
|
||||||
private val queue: NotificationResolverQueue,
|
private val queue: NotificationResolverQueue,
|
||||||
|
|
@ -47,6 +48,7 @@ class FetchNotificationsWorker(
|
||||||
private val syncOnNotifiableEvent: SyncOnNotifiableEvent,
|
private val syncOnNotifiableEvent: SyncOnNotifiableEvent,
|
||||||
private val coroutineDispatchers: CoroutineDispatchers,
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
private val workerDataConverter: WorkerDataConverter,
|
private val workerDataConverter: WorkerDataConverter,
|
||||||
|
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
|
||||||
) : CoroutineWorker(context, workerParams) {
|
) : CoroutineWorker(context, workerParams) {
|
||||||
override suspend fun doWork(): Result = withContext(coroutineDispatchers.io) {
|
override suspend fun doWork(): Result = withContext(coroutineDispatchers.io) {
|
||||||
Timber.d("FetchNotificationsWorker started")
|
Timber.d("FetchNotificationsWorker started")
|
||||||
|
|
@ -88,6 +90,7 @@ class FetchNotificationsWorker(
|
||||||
sessionId = failedSessionId,
|
sessionId = failedSessionId,
|
||||||
notificationEventRequests = requestsToRetry,
|
notificationEventRequests = requestsToRetry,
|
||||||
workerDataConverter = workerDataConverter,
|
workerDataConverter = workerDataConverter,
|
||||||
|
buildVersionSdkIntProvider = buildVersionSdkIntProvider,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
package io.element.android.libraries.push.impl.workmanager
|
package io.element.android.libraries.push.impl.workmanager
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.OutOfQuotaPolicy
|
import androidx.work.OutOfQuotaPolicy
|
||||||
import androidx.work.WorkRequest
|
import androidx.work.WorkRequest
|
||||||
|
|
@ -15,6 +16,7 @@ import io.element.android.libraries.push.api.push.NotificationEventRequest
|
||||||
import io.element.android.libraries.workmanager.api.WorkManagerRequest
|
import io.element.android.libraries.workmanager.api.WorkManagerRequest
|
||||||
import io.element.android.libraries.workmanager.api.WorkManagerRequestType
|
import io.element.android.libraries.workmanager.api.WorkManagerRequestType
|
||||||
import io.element.android.libraries.workmanager.api.workManagerTag
|
import io.element.android.libraries.workmanager.api.workManagerTag
|
||||||
|
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -24,6 +26,7 @@ class SyncNotificationWorkManagerRequest(
|
||||||
private val sessionId: SessionId,
|
private val sessionId: SessionId,
|
||||||
private val notificationEventRequests: List<NotificationEventRequest>,
|
private val notificationEventRequests: List<NotificationEventRequest>,
|
||||||
private val workerDataConverter: WorkerDataConverter,
|
private val workerDataConverter: WorkerDataConverter,
|
||||||
|
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
|
||||||
) : WorkManagerRequest {
|
) : WorkManagerRequest {
|
||||||
override fun build(): Result<WorkRequest> {
|
override fun build(): Result<WorkRequest> {
|
||||||
if (notificationEventRequests.isEmpty()) {
|
if (notificationEventRequests.isEmpty()) {
|
||||||
|
|
@ -36,7 +39,14 @@ class SyncNotificationWorkManagerRequest(
|
||||||
return Result.success(
|
return Result.success(
|
||||||
OneTimeWorkRequestBuilder<FetchNotificationsWorker>()
|
OneTimeWorkRequestBuilder<FetchNotificationsWorker>()
|
||||||
.setInputData(data)
|
.setInputData(data)
|
||||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
.apply {
|
||||||
|
// Expedited workers aren't needed on Android 12 or lower:
|
||||||
|
// They force displaying a foreground sync notification for no good reason, since they sync almost immediately anyway
|
||||||
|
// See https://developer.android.com/develop/background-work/background-tasks/persistent/getting-started/define-work#backwards-compat
|
||||||
|
if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
|
||||||
|
setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||||
|
}
|
||||||
|
}
|
||||||
.setTraceTag(workManagerTag(sessionId, WorkManagerRequestType.NOTIFICATION_SYNC))
|
.setTraceTag(workManagerTag(sessionId, WorkManagerRequestType.NOTIFICATION_SYNC))
|
||||||
// TODO investigate using this instead of the resolver queue
|
// TODO investigate using this instead of the resolver queue
|
||||||
// .setInputMerger()
|
// .setInputMerger()
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushSto
|
||||||
import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret
|
import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret
|
||||||
import io.element.android.libraries.workmanager.api.WorkManagerRequest
|
import io.element.android.libraries.workmanager.api.WorkManagerRequest
|
||||||
import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler
|
import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler
|
||||||
|
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||||
import io.element.android.tests.testutils.lambda.any
|
import io.element.android.tests.testutils.lambda.any
|
||||||
|
|
@ -717,6 +718,7 @@ class DefaultPushHandlerTest {
|
||||||
workManagerScheduler = workManagerScheduler,
|
workManagerScheduler = workManagerScheduler,
|
||||||
featureFlagService = featureFlagService,
|
featureFlagService = featureFlagService,
|
||||||
workerDataConverter = WorkerDataConverter(DefaultJsonProvider()),
|
workerDataConverter = WorkerDataConverter(DefaultJsonProvider()),
|
||||||
|
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(33),
|
||||||
),
|
),
|
||||||
appCoroutineScope = backgroundScope,
|
appCoroutineScope = backgroundScope,
|
||||||
fallbackNotificationFactory = FallbackNotificationFactory(
|
fallbackNotificationFactory = FallbackNotificationFactory(
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import io.element.android.libraries.push.test.notifications.FakeNotificationReso
|
||||||
import io.element.android.libraries.workmanager.api.WorkManagerRequest
|
import io.element.android.libraries.workmanager.api.WorkManagerRequest
|
||||||
import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory
|
import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory
|
||||||
import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler
|
import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler
|
||||||
|
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
|
@ -176,6 +177,7 @@ class FetchNotificationWorkerTest {
|
||||||
syncOnNotifiableEvent = syncOnNotifiableEvent,
|
syncOnNotifiableEvent = syncOnNotifiableEvent,
|
||||||
coroutineDispatchers = testCoroutineDispatchers(),
|
coroutineDispatchers = testCoroutineDispatchers(),
|
||||||
workerDataConverter = WorkerDataConverter(DefaultJsonProvider()),
|
workerDataConverter = WorkerDataConverter(DefaultJsonProvider()),
|
||||||
|
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(33),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun TestScope.createWorkerParams(
|
private fun TestScope.createWorkerParams(
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,17 @@ import io.element.android.libraries.push.api.push.NotificationEventRequest
|
||||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotificationEventRequest
|
import io.element.android.libraries.push.impl.notifications.fixtures.aNotificationEventRequest
|
||||||
import io.element.android.libraries.workmanager.api.WorkManagerRequestType
|
import io.element.android.libraries.workmanager.api.WorkManagerRequestType
|
||||||
import io.element.android.libraries.workmanager.api.workManagerTag
|
import io.element.android.libraries.workmanager.api.workManagerTag
|
||||||
|
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class SyncNotificationWorkManagerRequestTest {
|
class SyncNotificationWorkManagerRequestTest {
|
||||||
@Test
|
@Test
|
||||||
fun `build - success`() = runTest {
|
fun `build - success API 33`() = runTest {
|
||||||
val request = createSyncNotificationWorkManagerRequest(
|
val request = createSyncNotificationWorkManagerRequest(
|
||||||
sessionId = A_SESSION_ID,
|
sessionId = A_SESSION_ID,
|
||||||
notificationEventRequests = listOf(aNotificationEventRequest())
|
notificationEventRequests = listOf(aNotificationEventRequest()),
|
||||||
|
sdkVersion = 33,
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = request.build()
|
val result = request.build()
|
||||||
|
|
@ -33,11 +35,31 @@ class SyncNotificationWorkManagerRequestTest {
|
||||||
result.getOrNull()!!.run {
|
result.getOrNull()!!.run {
|
||||||
assertThat(this).isInstanceOf(OneTimeWorkRequest::class.java)
|
assertThat(this).isInstanceOf(OneTimeWorkRequest::class.java)
|
||||||
assertThat(workSpec.input.hasKeyWithValueOfType<String>("requests")).isTrue()
|
assertThat(workSpec.input.hasKeyWithValueOfType<String>("requests")).isTrue()
|
||||||
|
// True in API 33+
|
||||||
assertThat(workSpec.expedited).isTrue()
|
assertThat(workSpec.expedited).isTrue()
|
||||||
assertThat(workSpec.traceTag).isEqualTo(workManagerTag(A_SESSION_ID, WorkManagerRequestType.NOTIFICATION_SYNC))
|
assertThat(workSpec.traceTag).isEqualTo(workManagerTag(A_SESSION_ID, WorkManagerRequestType.NOTIFICATION_SYNC))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `build - success API 32 and lower`() = runTest {
|
||||||
|
val request = createSyncNotificationWorkManagerRequest(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
notificationEventRequests = listOf(aNotificationEventRequest()),
|
||||||
|
sdkVersion = 32,
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = request.build()
|
||||||
|
assertThat(result.isSuccess).isTrue()
|
||||||
|
result.getOrNull()!!.run {
|
||||||
|
assertThat(this).isInstanceOf(OneTimeWorkRequest::class.java)
|
||||||
|
assertThat(workSpec.input.hasKeyWithValueOfType<String>("requests")).isTrue()
|
||||||
|
// False before API 33
|
||||||
|
assertThat(workSpec.expedited).isFalse()
|
||||||
|
assertThat(workSpec.traceTag).isEqualTo(workManagerTag(A_SESSION_ID, WorkManagerRequestType.NOTIFICATION_SYNC))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `build - empty list of requests fails`() = runTest {
|
fun `build - empty list of requests fails`() = runTest {
|
||||||
val request = createSyncNotificationWorkManagerRequest(
|
val request = createSyncNotificationWorkManagerRequest(
|
||||||
|
|
@ -64,9 +86,11 @@ class SyncNotificationWorkManagerRequestTest {
|
||||||
private fun createSyncNotificationWorkManagerRequest(
|
private fun createSyncNotificationWorkManagerRequest(
|
||||||
sessionId: SessionId,
|
sessionId: SessionId,
|
||||||
notificationEventRequests: List<NotificationEventRequest>,
|
notificationEventRequests: List<NotificationEventRequest>,
|
||||||
workerDataConverter: WorkerDataConverter = WorkerDataConverter(DefaultJsonProvider())
|
workerDataConverter: WorkerDataConverter = WorkerDataConverter(DefaultJsonProvider()),
|
||||||
|
sdkVersion: Int = 33,
|
||||||
) = SyncNotificationWorkManagerRequest(
|
) = SyncNotificationWorkManagerRequest(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
notificationEventRequests = notificationEventRequests,
|
notificationEventRequests = notificationEventRequests,
|
||||||
workerDataConverter = workerDataConverter,
|
workerDataConverter = workerDataConverter,
|
||||||
|
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(sdkVersion),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue