Merge branch 'develop' into feature/valere/timeline_active_call_rendering

This commit is contained in:
Valere
2026-07-23 13:40:38 +02:00
82 changed files with 359 additions and 141 deletions
@@ -7,9 +7,11 @@
package io.element.android.libraries.matrix.impl.scanner
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.scanner.ContentScanner
import io.element.android.libraries.matrix.impl.exception.mapClientException
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.NoHandle
import org.matrix.rustcomponents.sdk.ContentScanner as RustScanner
@@ -22,6 +24,8 @@ class RustContentScanner(
override suspend fun scan(mediaSource: MediaSource): Result<Boolean> {
return runCatchingExceptions {
rustScanner.scan(client, mediaSource.toRustMediaSource()).clean
}.mapFailure {
it.mapClientException()
}
}
}
@@ -11,11 +11,17 @@ package io.element.android.libraries.matrix.impl.fixtures.factories
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.test.A_USER_ID
import org.matrix.rustcomponents.sdk.RoomHero
import org.matrix.rustcomponents.sdk.UserCall
import org.matrix.rustcomponents.sdk.UserStatus
internal fun aRustRoomHero(
userId: UserId = A_USER_ID,
status: UserStatus? = null,
call: UserCall? = null,
) = RoomHero(
userId = userId.value,
displayName = "displayName",
avatarUrl = "avatarUrl",
status = status,
call = call,
)
@@ -8,6 +8,8 @@
<resources>
<string name="action_mark_as_read">"Mark as read"</string>
<string name="a11y_jump_to_unread_messages">"Jump to first unread message"</string>
<string name="a11y_state_expanded">"Expanded"</string>
<string name="a11y_state_collapsed">"Collapsed"</string>
<string name="screen_local_network_opt_in_title">Allow access to local network</string>
<string name="screen_local_network_opt_in_subtitle">Your homeserver is on your local network. To connect, Element needs permission to reach other devices on this network.</string>
<string name="a11y_active">Active</string>
@@ -0,0 +1,12 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.wellknown.api
fun interface ElementWellKnownParser {
fun parse(json: String): Result<ElementWellKnown>
}
@@ -34,6 +34,7 @@ dependencies {
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.network)
implementation(projects.libraries.cachestore.api)
implementation(projects.features.enterprise.api)
implementation(projects.services.toolbox.api)
testCommonDependencies(libs)
@@ -41,5 +42,6 @@ dependencies {
testImplementation(projects.libraries.cachestore.test)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.wellknown.test)
testImplementation(projects.features.enterprise.test)
testImplementation(projects.services.toolbox.test)
}
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.wellknown.impl
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.androidutils.json.JsonProvider
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.wellknown.api.ElementWellKnown
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
@ContributesBinding(AppScope::class)
class DefaultElementWellKnownParser(
private val jsonProvider: JsonProvider,
) : ElementWellKnownParser {
override fun parse(json: String): Result<ElementWellKnown> {
return runCatchingExceptions {
val decoder = jsonProvider()
decoder.decodeFromString<InternalElementWellKnown>(json).map()
}
}
}
@@ -9,13 +9,14 @@
package io.element.android.libraries.wellknown.impl
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.androidutils.json.JsonProvider
import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.libraries.core.extensions.mapCatchingExceptions
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.exception.ClientException
import io.element.android.libraries.wellknown.api.ElementWellKnown
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
import io.element.android.libraries.wellknown.api.ElementWellknownStore
import io.element.android.libraries.wellknown.api.SessionWellknownRetriever
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
@@ -26,14 +27,20 @@ import timber.log.Timber
@ContributesBinding(SessionScope::class)
class DefaultSessionWellknownRetriever(
private val matrixClient: MatrixClient,
private val json: JsonProvider,
@SessionCoroutineScope
private val sessionCoroutineScope: CoroutineScope,
private val elementWellknownStore: ElementWellknownStore,
private val elementWellKnownParser: ElementWellKnownParser,
private val enterpriseService: EnterpriseService,
) : SessionWellknownRetriever {
private val domain by lazy { matrixClient.userIdServerName() }
override suspend fun getElementWellKnown(): WellknownRetrieverResult<ElementWellKnown> {
val overriddenElementWellKnown = enterpriseService.overriddenElementWellKnown()
if (overriddenElementWellKnown != null) {
return WellknownRetrieverResult.Success(overriddenElementWellKnown)
}
val cacheData = elementWellknownStore.get(domain)
return when (cacheData) {
is WellknownRetrieverResult.Success -> {
@@ -69,7 +76,7 @@ class DefaultSessionWellknownRetriever(
.getUrl(url)
.mapCatchingExceptions {
val data = String(it)
val parsed = json().decodeFromString<InternalElementWellKnown>(data).map()
val parsed = elementWellKnownParser.parse(data).getOrThrow()
// Also store in cache, if valid
elementWellknownStore.update(domain, data)
.onFailure { exception ->
@@ -9,11 +9,11 @@ package io.element.android.libraries.wellknown.impl
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.androidutils.json.JsonProvider
import io.element.android.libraries.cachestore.api.CacheData
import io.element.android.libraries.cachestore.api.CacheStore
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.wellknown.api.ElementWellKnown
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
import io.element.android.libraries.wellknown.api.ElementWellknownStore
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
import io.element.android.services.toolbox.api.systemclock.SystemClock
@@ -21,14 +21,14 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock
@ContributesBinding(AppScope::class)
class DefaultWellKnownStore(
private val cacheStore: CacheStore,
private val json: JsonProvider,
private val elementWellKnownParser: ElementWellKnownParser,
private val systemClock: SystemClock,
) : ElementWellknownStore {
override suspend fun get(domain: String): WellknownRetrieverResult<ElementWellKnown> {
return runCatchingExceptions {
val cachedData = cacheStore.getData(key(domain))
if (cachedData != null) {
val data = json().decodeFromString<InternalElementWellKnown>(cachedData.value).map()
val data = elementWellKnownParser.parse(cachedData.value).getOrThrow()
if (systemClock.epochMillis() > cachedData.updatedAt + CACHE_VALIDITY_MILLIS) {
WellknownRetrieverResult.Outdated(data)
} else {
@@ -10,6 +10,7 @@ package io.element.android.libraries.wellknown.impl
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.libraries.androidutils.json.JsonProvider
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.core.uri.ensureProtocol
@@ -28,8 +29,14 @@ class DefaultWellknownRetriever(
private val retrofitFactory: RetrofitFactory,
private val elementWellknownStore: ElementWellknownStore,
private val jsonProvider: JsonProvider,
private val enterpriseService: EnterpriseService,
) : WellknownRetriever {
override suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult<ElementWellKnown> {
val overriddenElementWellKnown = enterpriseService.overriddenElementWellKnown()
if (overriddenElementWellKnown != null) {
return WellknownRetrieverResult.Success(overriddenElementWellKnown)
}
val domain = URL(baseUrl).host
return buildWellknownApi(baseUrl)
.map { wellknownApi ->
@@ -11,15 +11,16 @@
package io.element.android.libraries.wellknown.impl
import com.google.common.truth.Truth.assertThat
import io.element.android.features.enterprise.test.FakeEnterpriseService
import io.element.android.features.wellknown.test.FakeElementWellknownStore
import io.element.android.features.wellknown.test.anElementWellKnown
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
import io.element.android.libraries.androidutils.json.JsonProvider
import io.element.android.libraries.matrix.api.exception.ClientException
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
import io.element.android.libraries.wellknown.api.ElementWellKnown
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.lambda.lambdaRecorder
@@ -276,7 +277,7 @@ class DefaultSessionWellknownRetrieverTest {
Result.success("{}".toByteArray())
},
cacheStore = cacheStore,
jsonProvider = JsonProvider { error("Failed to parse JSON") }
elementWellKnownParser = { Result.failure(IllegalStateException("Failed to parse JSON")) }
)
assertThat(sut.getElementWellKnown()).isInstanceOf(WellknownRetrieverResult.Error::class.java)
// Ensure that the cache is deleted after the failure to parse it
@@ -320,20 +321,43 @@ class DefaultSessionWellknownRetrieverTest {
)
}
@Test
fun `get element wellknown was overridden`() = runTest {
val getLambda = lambdaRecorder<String, Result<ByteArray>> { Result.failure(IllegalStateException("BOOM")) }
val wellKnown = anElementWellKnown()
val sut = createDefaultSessionWellknownRetriever(
getUrlLambda = getLambda,
enterpriseService = FakeEnterpriseService(
overrideWellKnownResult = { wellKnown }
)
)
// The overridden value is returned
assertThat(sut.getElementWellKnown()).isEqualTo(
WellknownRetrieverResult.Success(wellKnown)
)
// And the endpoint is never hit
getLambda.assertions().isNeverCalled()
}
private fun parsedWellKnownContent() = DefaultJsonProvider().invoke().decodeFromString<InternalElementWellKnown>(WELLKNOWN_CONTENT).map()
private fun TestScope.createDefaultSessionWellknownRetriever(
cacheStore: FakeElementWellknownStore = FakeElementWellknownStore(),
getUrlLambda: (String) -> Result<ByteArray>,
jsonProvider: JsonProvider = DefaultJsonProvider(),
elementWellKnownParser: ElementWellKnownParser = DefaultElementWellKnownParser(DefaultJsonProvider()),
enterpriseService: FakeEnterpriseService = FakeEnterpriseService(overrideWellKnownResult = { null }),
) = DefaultSessionWellknownRetriever(
matrixClient = FakeMatrixClient(
userIdServerNameLambda = { "user.domain.org" },
getUrlLambda = getUrlLambda,
),
json = jsonProvider,
sessionCoroutineScope = backgroundScope,
elementWellknownStore = cacheStore,
elementWellKnownParser = elementWellKnownParser,
enterpriseService = enterpriseService,
)
companion object {
@@ -13,6 +13,7 @@ import io.element.android.libraries.androidutils.json.DefaultJsonProvider
import io.element.android.libraries.cachestore.api.CacheData
import io.element.android.libraries.sessionstorage.test.InMemoryCacheStore
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
@@ -150,9 +151,10 @@ class DefaultWellKnownStoreTest {
private fun createDefaultWellKnownStore(
cacheStore: InMemoryCacheStore = InMemoryCacheStore(),
systemClock: FakeSystemClock = FakeSystemClock(),
elementWellKnownParser: ElementWellKnownParser = DefaultElementWellKnownParser(jsonProvider),
) = DefaultWellKnownStore(
cacheStore = cacheStore,
json = jsonProvider,
systemClock = systemClock,
elementWellKnownParser = elementWellKnownParser,
)
}
@@ -11,6 +11,7 @@
package io.element.android.libraries.wellknown.impl
import com.google.common.truth.Truth.assertThat
import io.element.android.features.enterprise.test.FakeEnterpriseService
import io.element.android.features.wellknown.test.FakeElementWellknownStore
import io.element.android.features.wellknown.test.anElementWellKnown
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
@@ -19,6 +20,7 @@ import io.element.android.libraries.network.RetrofitFactory
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
import io.element.android.libraries.wellknown.api.ElementWellKnown
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
@@ -236,6 +238,27 @@ class DefaultWellknownRetrieverTest {
assertThat(cacheStore.get(WELLKNOWN_URL)).isEqualTo(WellknownRetrieverResult.NotFound)
}
@Test
fun `get element wellknown was overridden`() = runTest {
val getLambda = lambdaRecorder<Request, Call> { mockCall() }
val wellKnown = anElementWellKnown()
val sut = createDefaultWellknownRetriever(
callFactory = getLambda,
enterpriseService = FakeEnterpriseService(
overrideWellKnownResult = { wellKnown }
)
)
// The overridden value is returned
assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isEqualTo(
WellknownRetrieverResult.Success(wellKnown)
)
// And the endpoint is never hit
getLambda.assertions().isNeverCalled()
}
private fun defaultResponse(
body: String = WELLKNOWN_CONTENT,
status: Int = 200,
@@ -251,6 +274,7 @@ class DefaultWellknownRetrieverTest {
callFactory: Call.Factory = Call.Factory { _: Request -> mockCall(defaultResponse()) },
cacheStore: FakeElementWellknownStore = FakeElementWellknownStore(),
jsonProvider: JsonProvider = DefaultJsonProvider(),
enterpriseService: FakeEnterpriseService = FakeEnterpriseService(overrideWellKnownResult = { null }),
) = DefaultWellknownRetriever(
retrofitFactory = RetrofitFactory(
callFactory = { callFactory },
@@ -258,6 +282,7 @@ class DefaultWellknownRetrieverTest {
),
jsonProvider = jsonProvider,
elementWellknownStore = cacheStore,
enterpriseService = enterpriseService,
)
companion object {