Merge branch 'develop' into feature/bma/mediaForward

# Conflicts:
#	appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt
This commit is contained in:
Benoit Marty 2025-10-29 12:41:58 +01:00
commit cc050a1f26
43 changed files with 399 additions and 190 deletions

View file

@ -32,6 +32,11 @@ sealed interface AsyncAction<out T> {
data object ConfirmingNoParams : Confirming
/**
* User cancels the action, use this object to ask for confirmation.
*/
data object ConfirmingCancellation : Confirming
/**
* Represents an operation that is currently ongoing.
*/

View file

@ -17,4 +17,6 @@ sealed class QrLoginException : Exception() {
data object SlidingSyncNotAvailable : QrLoginException()
data object OtherDeviceNotSignedIn : QrLoginException()
data object Unknown : QrLoginException()
data object CheckCodeAlreadySent : QrLoginException()
data object CheckCodeCannotBeSent : QrLoginException()
}

View file

@ -289,10 +289,13 @@ class RustMatrixAuthenticationService(
qrCodeData = sdkQrCodeLoginData,
)
client.loginWithQrCode(
qrCodeData = qrCodeData.rustQrCodeData,
oidcConfiguration = oidcConfiguration,
progressListener = progressListener,
)
).use {
it.scan(
qrCodeData = qrCodeData.rustQrCodeData,
progressListener = progressListener,
)
}
// Ensure that the user is not already logged in with the same account
ensureNotAlreadyLoggedIn(client)
val sessionData = client.session()

View file

@ -42,5 +42,7 @@ object QrErrorMapper {
is RustHumanQrLoginException.Unknown -> QrLoginException.Unknown
is RustHumanQrLoginException.OidcMetadataInvalid -> QrLoginException.OidcMetadataInvalid
is RustHumanQrLoginException.SlidingSyncNotAvailable -> QrLoginException.SlidingSyncNotAvailable
is RustHumanQrLoginException.CheckCodeAlreadySent -> QrLoginException.CheckCodeAlreadySent
is RustHumanQrLoginException.CheckCodeCannotBeSent -> QrLoginException.CheckCodeCannotBeSent
}
}

View file

@ -17,7 +17,9 @@ internal fun ComposerDraft.into(): RustComposerDraft {
return RustComposerDraft(
plainText = plainText,
htmlText = htmlText,
draftType = draftType.into()
draftType = draftType.into(),
// TODO add media attachments to the draft
attachments = emptyList(),
)
}

View file

@ -31,10 +31,6 @@ object NotificationIdProvider {
return getOffset(sessionId) + FALLBACK_NOTIFICATION_ID
}
fun getCallNotificationId(sessionId: SessionId): Int {
return getOffset(sessionId) + ROOM_CALL_NOTIFICATION_ID
}
fun getForegroundServiceNotificationId(type: ForegroundServiceType): Int {
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_EVENT_NOTIFICATION_ID = 2
private const val ROOM_INVITATION_NOTIFICATION_ID = 3
private const val ROOM_CALL_NOTIFICATION_ID = 3
private const val FOREGROUND_SERVICE_NOTIFICATION_ID = 4
}

View file

@ -7,7 +7,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application>

View file

@ -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.WorkerDataConverter
import io.element.android.libraries.workmanager.api.WorkManagerScheduler
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
@ -49,6 +50,7 @@ class DefaultNotificationResolverQueue(
private val workManagerScheduler: WorkManagerScheduler,
private val featureFlagService: FeatureFlagService,
private val workerDataConverter: WorkerDataConverter,
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
) : NotificationResolverQueue {
companion object {
private const val BATCH_WINDOW_MS = 250L
@ -100,6 +102,7 @@ class DefaultNotificationResolverQueue(
sessionId = sessionId,
notificationEventRequests = requests,
workerDataConverter = workerDataConverter,
buildVersionSdkIntProvider = buildVersionSdkIntProvider,
)
)
}

View file

@ -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.di.MetroWorkerFactory
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.first
import kotlinx.coroutines.withContext
@ -39,7 +40,7 @@ import kotlin.time.Duration.Companion.seconds
@AssistedInject
class FetchNotificationsWorker(
@Assisted workerParams: WorkerParameters,
@ApplicationContext context: Context,
@ApplicationContext private val context: Context,
private val networkMonitor: NetworkMonitor,
private val eventResolver: NotifiableEventResolver,
private val queue: NotificationResolverQueue,
@ -47,6 +48,7 @@ class FetchNotificationsWorker(
private val syncOnNotifiableEvent: SyncOnNotifiableEvent,
private val coroutineDispatchers: CoroutineDispatchers,
private val workerDataConverter: WorkerDataConverter,
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result = withContext(coroutineDispatchers.io) {
Timber.d("FetchNotificationsWorker started")
@ -88,6 +90,7 @@ class FetchNotificationsWorker(
sessionId = failedSessionId,
notificationEventRequests = requestsToRetry,
workerDataConverter = workerDataConverter,
buildVersionSdkIntProvider = buildVersionSdkIntProvider,
)
)
}

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.push.impl.workmanager
import android.os.Build
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
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.WorkManagerRequestType
import io.element.android.libraries.workmanager.api.workManagerTag
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import timber.log.Timber
@ -24,6 +26,7 @@ class SyncNotificationWorkManagerRequest(
private val sessionId: SessionId,
private val notificationEventRequests: List<NotificationEventRequest>,
private val workerDataConverter: WorkerDataConverter,
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
) : WorkManagerRequest {
override fun build(): Result<WorkRequest> {
if (notificationEventRequests.isEmpty()) {
@ -36,7 +39,14 @@ class SyncNotificationWorkManagerRequest(
return Result.success(
OneTimeWorkRequestBuilder<FetchNotificationsWorker>()
.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))
// TODO investigate using this instead of the resolver queue
// .setInputMerger()

View file

@ -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.workmanager.api.WorkManagerRequest
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.systemclock.FakeSystemClock
import io.element.android.tests.testutils.lambda.any
@ -717,6 +718,7 @@ class DefaultPushHandlerTest {
workManagerScheduler = workManagerScheduler,
featureFlagService = featureFlagService,
workerDataConverter = WorkerDataConverter(DefaultJsonProvider()),
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(33),
),
appCoroutineScope = backgroundScope,
fallbackNotificationFactory = FallbackNotificationFactory(

View file

@ -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.di.MetroWorkerFactory
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.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -176,6 +177,7 @@ class FetchNotificationWorkerTest {
syncOnNotifiableEvent = syncOnNotifiableEvent,
coroutineDispatchers = testCoroutineDispatchers(),
workerDataConverter = WorkerDataConverter(DefaultJsonProvider()),
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(33),
)
private fun TestScope.createWorkerParams(

View file

@ -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.workmanager.api.WorkManagerRequestType
import io.element.android.libraries.workmanager.api.workManagerTag
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
import kotlinx.coroutines.test.runTest
import org.junit.Test
class SyncNotificationWorkManagerRequestTest {
@Test
fun `build - success`() = runTest {
fun `build - success API 33`() = runTest {
val request = createSyncNotificationWorkManagerRequest(
sessionId = A_SESSION_ID,
notificationEventRequests = listOf(aNotificationEventRequest())
notificationEventRequests = listOf(aNotificationEventRequest()),
sdkVersion = 33,
)
val result = request.build()
@ -33,11 +35,31 @@ class SyncNotificationWorkManagerRequestTest {
result.getOrNull()!!.run {
assertThat(this).isInstanceOf(OneTimeWorkRequest::class.java)
assertThat(workSpec.input.hasKeyWithValueOfType<String>("requests")).isTrue()
// True in API 33+
assertThat(workSpec.expedited).isTrue()
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
fun `build - empty list of requests fails`() = runTest {
val request = createSyncNotificationWorkManagerRequest(
@ -64,9 +86,11 @@ class SyncNotificationWorkManagerRequestTest {
private fun createSyncNotificationWorkManagerRequest(
sessionId: SessionId,
notificationEventRequests: List<NotificationEventRequest>,
workerDataConverter: WorkerDataConverter = WorkerDataConverter(DefaultJsonProvider())
workerDataConverter: WorkerDataConverter = WorkerDataConverter(DefaultJsonProvider()),
sdkVersion: Int = 33,
) = SyncNotificationWorkManagerRequest(
sessionId = sessionId,
notificationEventRequests = notificationEventRequests,
workerDataConverter = workerDataConverter,
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(sdkVersion),
)