Merge branch 'develop' of https://github.com/vector-im/element-x-android into langleyd/live_waveform
This commit is contained in:
commit
1db2adae7e
40 changed files with 608 additions and 106 deletions
|
|
@ -27,7 +27,7 @@ plugins {
|
||||||
alias(libs.plugins.anvil)
|
alias(libs.plugins.anvil)
|
||||||
alias(libs.plugins.ksp)
|
alias(libs.plugins.ksp)
|
||||||
alias(libs.plugins.kapt)
|
alias(libs.plugins.kapt)
|
||||||
id("com.google.firebase.appdistribution") version "4.0.0"
|
id("com.google.firebase.appdistribution") version "4.0.1"
|
||||||
id("org.jetbrains.kotlinx.knit") version "0.4.0"
|
id("org.jetbrains.kotlinx.knit") version "0.4.0"
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
// To be able to update the firebase.xml files, uncomment and build the project
|
// To be able to update the firebase.xml files, uncomment and build the project
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ class PinUnlockPresenter @Inject constructor(
|
||||||
|
|
||||||
private fun CoroutineScope.signOut(signOutAction: MutableState<Async<String?>>) = launch {
|
private fun CoroutineScope.signOut(signOutAction: MutableState<Async<String?>>) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
matrixClient.logout()
|
matrixClient.logout(ignoreSdkError = true)
|
||||||
}.runCatchingUpdatingState(signOutAction)
|
}.runCatchingUpdatingState(signOutAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ class DefaultLogoutPreferencePresenter @Inject constructor(private val matrixCli
|
||||||
|
|
||||||
private fun CoroutineScope.logout(logoutAction: MutableState<Async<String?>>) = launch {
|
private fun CoroutineScope.logout(logoutAction: MutableState<Async<String?>>) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
matrixClient.logout()
|
matrixClient.logout(false /* TODO */)
|
||||||
}.runCatchingUpdatingState(logoutAction)
|
}.runCatchingUpdatingState(logoutAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||||
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageEvents
|
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageEvents
|
||||||
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageState
|
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageState
|
||||||
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageStateProvider
|
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageStateProvider
|
||||||
import io.element.android.libraries.designsystem.components.media.Waveform
|
|
||||||
import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView
|
import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
@ -88,7 +87,7 @@ fun TimelineItemVoiceView(
|
||||||
WaveformPlaybackView(
|
WaveformPlaybackView(
|
||||||
showCursor = state.button == VoiceMessageState.Button.Pause,
|
showCursor = state.button == VoiceMessageState.Button.Pause,
|
||||||
playbackProgress = state.progress,
|
playbackProgress = state.progress,
|
||||||
waveform = Waveform(data = content.waveform),
|
waveform = content.waveform,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(34.dp)
|
.height(34.dp)
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
||||||
import io.element.android.features.messages.impl.timeline.util.FileExtensionExtractor
|
import io.element.android.features.messages.impl.timeline.util.FileExtensionExtractor
|
||||||
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
|
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
|
||||||
|
import io.element.android.features.messages.impl.voicemessages.fromMSC3246range
|
||||||
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
|
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
|
|
@ -117,7 +118,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||||
mediaSource = messageType.source,
|
mediaSource = messageType.source,
|
||||||
duration = messageType.info?.duration ?: Duration.ZERO,
|
duration = messageType.info?.duration ?: Duration.ZERO,
|
||||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||||
waveform = messageType.details?.waveform?.toImmutableList() ?: persistentListOf(),
|
waveform = messageType.details?.waveform?.fromMSC3246range()?.toImmutableList() ?: persistentListOf(),
|
||||||
)
|
)
|
||||||
else -> TimelineItemAudioContent(
|
else -> TimelineItemAudioContent(
|
||||||
body = messageType.body,
|
body = messageType.body,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ data class TimelineItemVoiceContent(
|
||||||
val duration: Duration,
|
val duration: Duration,
|
||||||
val mediaSource: MediaSource,
|
val mediaSource: MediaSource,
|
||||||
val mimeType: String,
|
val mimeType: String,
|
||||||
val waveform: ImmutableList<Int>,
|
val waveform: ImmutableList<Float>,
|
||||||
) : TimelineItemEventContent {
|
) : TimelineItemEventContent {
|
||||||
override val type: String = "TimelineItemAudioContent"
|
override val type: String = "TimelineItemAudioContent"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,11 +32,11 @@ open class TimelineItemVoiceContentProvider : PreviewParameterProvider<TimelineI
|
||||||
),
|
),
|
||||||
aTimelineItemVoiceContent(
|
aTimelineItemVoiceContent(
|
||||||
durationMs = 10_000,
|
durationMs = 10_000,
|
||||||
waveform = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0),
|
waveform = listOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 8f, 7f, 6f, 5f, 4f, 3f, 2f, 1f, 0f),
|
||||||
),
|
),
|
||||||
aTimelineItemVoiceContent(
|
aTimelineItemVoiceContent(
|
||||||
durationMs = 1_800_000, // 30 minutes
|
durationMs = 1_800_000, // 30 minutes
|
||||||
waveform = List(1024) { it },
|
waveform = List(1024) { it / 1024f },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +47,7 @@ fun aTimelineItemVoiceContent(
|
||||||
durationMs: Long = 61_000,
|
durationMs: Long = 61_000,
|
||||||
contentUri: String = "mxc://matrix.org/1234567890abcdefg",
|
contentUri: String = "mxc://matrix.org/1234567890abcdefg",
|
||||||
mimeType: String = MimeTypes.Ogg,
|
mimeType: String = MimeTypes.Ogg,
|
||||||
waveform: List<Int> = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0),
|
waveform: List<Float> = listOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 8f, 7f, 6f, 5f, 4f, 3f, 2f, 1f, 0f),
|
||||||
) = TimelineItemVoiceContent(
|
) = TimelineItemVoiceContent(
|
||||||
eventId = eventId?.let { EventId(it) },
|
eventId = eventId?.let { EventId(it) },
|
||||||
body = body,
|
body = body,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.features.messages.impl.voicemessages
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizes the given [0;1024] int list as per unstable MSC3246 spec
|
||||||
|
* to a [0;1] range float list to be used for waveform rendering.
|
||||||
|
*/
|
||||||
|
fun List<Int>.fromMSC3246range(): List<Float> = map { it / 1024f }
|
||||||
|
|
@ -70,7 +70,7 @@ android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref
|
||||||
android_desugar = "com.android.tools:desugar_jdk_libs:2.0.3"
|
android_desugar = "com.android.tools:desugar_jdk_libs:2.0.3"
|
||||||
kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||||
# https://firebase.google.com/docs/android/setup#available-libraries
|
# https://firebase.google.com/docs/android/setup#available-libraries
|
||||||
google_firebase_bom = "com.google.firebase:firebase-bom:32.4.0"
|
google_firebase_bom = "com.google.firebase:firebase-bom:32.4.1"
|
||||||
|
|
||||||
# AndroidX
|
# AndroidX
|
||||||
androidx_core = { module = "androidx.core:core", version.ref = "core" }
|
androidx_core = { module = "androidx.core:core", version.ref = "core" }
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2023 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 io.element.android.libraries.designsystem.components.media
|
|
||||||
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
data class Waveform (
|
|
||||||
val data: ImmutableList<Int>
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
private val dataRange = 0..1024
|
|
||||||
}
|
|
||||||
|
|
||||||
fun normalisedData(maxSamplesCount: Int): ImmutableList<Float> {
|
|
||||||
if(maxSamplesCount <= 0) {
|
|
||||||
return persistentListOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter the data to keep only the expected number of samples
|
|
||||||
val result = if (data.size > maxSamplesCount) {
|
|
||||||
(0..<maxSamplesCount)
|
|
||||||
.map { index ->
|
|
||||||
val targetIndex = (index.toDouble() * (data.count().toDouble() / maxSamplesCount.toDouble())).roundToInt()
|
|
||||||
data[targetIndex]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize the sample in the allowed range
|
|
||||||
return result.map { it.toFloat() / dataRange.last.toFloat() }.toPersistentList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -46,21 +46,24 @@ import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.theme.ElementTheme
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private const val DEFAULT_GRAPHICS_LAYER_ALPHA: Float = 0.99F
|
private const val DEFAULT_GRAPHICS_LAYER_ALPHA: Float = 0.99F
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun WaveformPlaybackView(
|
fun WaveformPlaybackView(
|
||||||
playbackProgress: Float,
|
playbackProgress: Float,
|
||||||
showCursor: Boolean,
|
showCursor: Boolean,
|
||||||
waveform: Waveform,
|
waveform: ImmutableList<Float>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onSeek: (progress: Float) -> Unit = {},
|
onSeek: (progress: Float) -> Unit = {},
|
||||||
brush: Brush = SolidColor(ElementTheme.colors.iconQuaternary),
|
brush: Brush = SolidColor(ElementTheme.colors.iconQuaternary),
|
||||||
progressBrush: Brush = SolidColor(ElementTheme.colors.iconSecondary),
|
progressBrush: Brush = SolidColor(ElementTheme.colors.iconSecondary),
|
||||||
cursorBrush: Brush = SolidColor(ElementTheme.colors.iconAccentTertiary),
|
cursorBrush: Brush = SolidColor(ElementTheme.colors.iconAccentTertiary),
|
||||||
lineWidth: Dp = 2.dp,
|
lineWidth: Dp = 2.dp,
|
||||||
linePadding: Dp = 2.dp,
|
linePadding: Dp = 2.dp,
|
||||||
) {
|
) {
|
||||||
|
|
@ -134,7 +137,7 @@ fun WaveformPlaybackView(
|
||||||
),
|
),
|
||||||
blendMode = BlendMode.SrcAtop
|
blendMode = BlendMode.SrcAtop
|
||||||
)
|
)
|
||||||
if(showCursor || seekProgress.value != null) {
|
if (showCursor || seekProgress.value != null) {
|
||||||
drawRoundRect(
|
drawRoundRect(
|
||||||
brush = cursorBrush,
|
brush = cursorBrush,
|
||||||
topLeft = Offset(
|
topLeft = Offset(
|
||||||
|
|
@ -155,24 +158,43 @@ fun WaveformPlaybackView(
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun WaveformPlaybackViewPreview() = ElementPreview {
|
internal fun WaveformPlaybackViewPreview() = ElementPreview {
|
||||||
Column{
|
Column {
|
||||||
WaveformPlaybackView(
|
WaveformPlaybackView(
|
||||||
modifier = Modifier.height(34.dp),
|
modifier = Modifier.height(34.dp),
|
||||||
showCursor = false,
|
showCursor = false,
|
||||||
playbackProgress = 0.5f,
|
playbackProgress = 0.5f,
|
||||||
waveform = Waveform(persistentListOf()),
|
waveform = persistentListOf(),
|
||||||
)
|
)
|
||||||
WaveformPlaybackView(
|
WaveformPlaybackView(
|
||||||
modifier = Modifier.height(34.dp),
|
modifier = Modifier.height(34.dp),
|
||||||
showCursor = false,
|
showCursor = false,
|
||||||
playbackProgress = 0.5f,
|
playbackProgress = 0.5f,
|
||||||
waveform = Waveform(persistentListOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)),
|
waveform = persistentListOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 8f, 7f, 6f, 5f, 4f, 3f, 2f, 1f, 0f),
|
||||||
)
|
)
|
||||||
WaveformPlaybackView(
|
WaveformPlaybackView(
|
||||||
modifier = Modifier.height(34.dp),
|
modifier = Modifier.height(34.dp),
|
||||||
showCursor = true,
|
showCursor = true,
|
||||||
playbackProgress = 0.5f,
|
playbackProgress = 0.5f,
|
||||||
waveform = Waveform(List(1024) { it }.toPersistentList()),
|
waveform = List(1024) { it / 1024f }.toPersistentList(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ImmutableList<Float>.normalisedData(maxSamplesCount: Int): ImmutableList<Float> {
|
||||||
|
if (maxSamplesCount <= 0) {
|
||||||
|
return persistentListOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter the data to keep only the expected number of samples
|
||||||
|
val result = if (this.size > maxSamplesCount) {
|
||||||
|
(0..<maxSamplesCount)
|
||||||
|
.map { index ->
|
||||||
|
val targetIndex = (index.toDouble() * (this.count().toDouble() / maxSamplesCount.toDouble())).roundToInt()
|
||||||
|
this[targetIndex]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toPersistentList()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||||
|
|
@ -55,6 +56,7 @@ interface MatrixClient : Closeable {
|
||||||
fun pushersService(): PushersService
|
fun pushersService(): PushersService
|
||||||
fun notificationService(): NotificationService
|
fun notificationService(): NotificationService
|
||||||
fun notificationSettingsService(): NotificationSettingsService
|
fun notificationSettingsService(): NotificationSettingsService
|
||||||
|
fun encryptionService(): EncryptionService
|
||||||
suspend fun getCacheSize(): Long
|
suspend fun getCacheSize(): Long
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -66,8 +68,9 @@ interface MatrixClient : Closeable {
|
||||||
* Logout the user.
|
* Logout the user.
|
||||||
* Returns an optional URL. When the URL is there, it should be presented to the user after logout for
|
* Returns an optional URL. When the URL is there, it should be presented to the user after logout for
|
||||||
* Relying Party (RP) initiated logout on their account page.
|
* Relying Party (RP) initiated logout on their account page.
|
||||||
|
* @param ignoreSdkError if true, the SDK will ignore any error and delete the session data anyway.
|
||||||
*/
|
*/
|
||||||
suspend fun logout(): String?
|
suspend fun logout(ignoreSdkError: Boolean): String?
|
||||||
suspend fun loadUserDisplayName(): Result<String>
|
suspend fun loadUserDisplayName(): Result<String>
|
||||||
suspend fun loadUserAvatarURLString(): Result<String?>
|
suspend fun loadUserAvatarURLString(): Result<String?>
|
||||||
suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?>
|
suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.encryption
|
||||||
|
|
||||||
|
sealed interface BackupUploadState {
|
||||||
|
data object Unknown : BackupUploadState
|
||||||
|
|
||||||
|
data class CheckingIfUploadNeeded(
|
||||||
|
val backedUpCount: Int,
|
||||||
|
val totalCount: Int,
|
||||||
|
) : BackupUploadState
|
||||||
|
|
||||||
|
data object Waiting : BackupUploadState
|
||||||
|
|
||||||
|
data class Uploading(
|
||||||
|
val backedUpCount: Int,
|
||||||
|
val totalCount: Int,
|
||||||
|
) : BackupUploadState
|
||||||
|
|
||||||
|
data object Done : BackupUploadState
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.encryption
|
||||||
|
|
||||||
|
sealed interface EnableRecoveryProgress {
|
||||||
|
data object Unknown : EnableRecoveryProgress
|
||||||
|
data object CreatingRecoveryKey : EnableRecoveryProgress
|
||||||
|
data object CreatingBackup : EnableRecoveryProgress
|
||||||
|
data class BackingUp(val backedUpCount: Int, val totalCount: Int) : EnableRecoveryProgress
|
||||||
|
data class Done(val recoveryKey: String) : EnableRecoveryProgress
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.encryption
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
interface EncryptionService {
|
||||||
|
val backupStateStateFlow: StateFlow<BackupState>
|
||||||
|
val recoveryStateStateFlow: StateFlow<RecoveryState>
|
||||||
|
val backupUploadStateStateFlow: StateFlow<BackupUploadState>
|
||||||
|
val enableRecoveryProgressStateFlow: StateFlow<EnableRecoveryProgress>
|
||||||
|
|
||||||
|
suspend fun enableBackups(): Result<Unit>
|
||||||
|
|
||||||
|
suspend fun isLastDevice(): Result<Boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable recovery. Observe enableProgressStateFlow to get progress and recovery key.
|
||||||
|
*/
|
||||||
|
suspend fun enableRecovery(waitForBackupsToUpload: Boolean): Result<Unit>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the recovery and return the new recovery key.
|
||||||
|
*/
|
||||||
|
suspend fun resetRecoveryKey(): Result<String>
|
||||||
|
|
||||||
|
suspend fun disableRecovery(): Result<Unit>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: accept bot recoveryKey and passphrase.
|
||||||
|
*/
|
||||||
|
suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe [backupUploadStateStateFlow] to get progress.
|
||||||
|
*/
|
||||||
|
suspend fun waitForBackupUploadSteadyState(): Result<Unit>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.encryption
|
||||||
|
|
||||||
|
enum class RecoveryState {
|
||||||
|
UNKNOWN,
|
||||||
|
ENABLED,
|
||||||
|
DISABLED,
|
||||||
|
INCOMPLETE,
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||||
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
||||||
import io.element.android.libraries.matrix.api.createroom.RoomVisibility
|
import io.element.android.libraries.matrix.api.createroom.RoomVisibility
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||||
|
|
@ -41,6 +42,7 @@ import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
|
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
|
||||||
|
import io.element.android.libraries.matrix.impl.encryption.RustEncryptionService
|
||||||
import io.element.android.libraries.matrix.impl.mapper.toSessionData
|
import io.element.android.libraries.matrix.impl.mapper.toSessionData
|
||||||
import io.element.android.libraries.matrix.impl.media.RustMediaLoader
|
import io.element.android.libraries.matrix.impl.media.RustMediaLoader
|
||||||
import io.element.android.libraries.matrix.impl.notification.RustNotificationService
|
import io.element.android.libraries.matrix.impl.notification.RustNotificationService
|
||||||
|
|
@ -117,6 +119,7 @@ class RustMatrixClient constructor(
|
||||||
private val notificationService = RustNotificationService(sessionId, notificationClient, dispatchers, clock)
|
private val notificationService = RustNotificationService(sessionId, notificationClient, dispatchers, clock)
|
||||||
private val notificationSettingsService = RustNotificationSettingsService(notificationSettings, dispatchers)
|
private val notificationSettingsService = RustNotificationSettingsService(notificationSettings, dispatchers)
|
||||||
private val roomSyncSubscriber = RoomSyncSubscriber(innerRoomListService, dispatchers)
|
private val roomSyncSubscriber = RoomSyncSubscriber(innerRoomListService, dispatchers)
|
||||||
|
private val encryptionService = RustEncryptionService(client, dispatchers).apply { start() }
|
||||||
|
|
||||||
private val isLoggingOut = AtomicBoolean(false)
|
private val isLoggingOut = AtomicBoolean(false)
|
||||||
|
|
||||||
|
|
@ -136,7 +139,7 @@ class RustMatrixClient constructor(
|
||||||
)
|
)
|
||||||
sessionStore.updateData(newData)
|
sessionStore.updateData(newData)
|
||||||
}
|
}
|
||||||
doLogout(doRequest = false, removeSession = false)
|
doLogout(doRequest = false, removeSession = false, ignoreSdkError = false)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Timber.v("didReceiveAuthError -> already cleaning up")
|
Timber.v("didReceiveAuthError -> already cleaning up")
|
||||||
|
|
@ -319,6 +322,8 @@ class RustMatrixClient constructor(
|
||||||
|
|
||||||
override fun notificationService(): NotificationService = notificationService
|
override fun notificationService(): NotificationService = notificationService
|
||||||
|
|
||||||
|
override fun encryptionService(): EncryptionService = encryptionService
|
||||||
|
|
||||||
override fun notificationSettingsService(): NotificationSettingsService = notificationSettingsService
|
override fun notificationSettingsService(): NotificationSettingsService = notificationSettingsService
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
|
|
@ -331,6 +336,7 @@ class RustMatrixClient constructor(
|
||||||
innerRoomListService.destroy()
|
innerRoomListService.destroy()
|
||||||
notificationClient.destroy()
|
notificationClient.destroy()
|
||||||
notificationProcessSetup.destroy()
|
notificationProcessSetup.destroy()
|
||||||
|
encryptionService.destroy()
|
||||||
client.destroy()
|
client.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -344,16 +350,29 @@ class RustMatrixClient constructor(
|
||||||
baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = false)
|
baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun logout(): String? = doLogout(doRequest = true, removeSession = true)
|
override suspend fun logout(ignoreSdkError: Boolean): String? = doLogout(
|
||||||
|
doRequest = true,
|
||||||
|
removeSession = true,
|
||||||
|
ignoreSdkError = ignoreSdkError,
|
||||||
|
)
|
||||||
|
|
||||||
private suspend fun doLogout(doRequest: Boolean, removeSession: Boolean): String? {
|
private suspend fun doLogout(
|
||||||
|
doRequest: Boolean,
|
||||||
|
removeSession: Boolean,
|
||||||
|
ignoreSdkError: Boolean,
|
||||||
|
): String? {
|
||||||
var result: String? = null
|
var result: String? = null
|
||||||
withContext(sessionDispatcher) {
|
withContext(sessionDispatcher) {
|
||||||
if (doRequest) {
|
if (doRequest) {
|
||||||
try {
|
try {
|
||||||
result = client.logout()
|
result = client.logout()
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
Timber.e(failure, "Fail to call logout on HS. Still delete local files.")
|
if (ignoreSdkError) {
|
||||||
|
Timber.e(failure, "Fail to call logout on HS. Still delete local files.")
|
||||||
|
} else {
|
||||||
|
Timber.e(failure, "Fail to call logout on HS.")
|
||||||
|
throw failure
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close()
|
close()
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||||
|
|
@ -50,6 +51,11 @@ object SessionMatrixModule {
|
||||||
return matrixClient.roomListService
|
return matrixClient.roomListService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun providesEncryptionService(matrixClient: MatrixClient): EncryptionService {
|
||||||
|
return matrixClient.encryptionService()
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideMediaLoader(matrixClient: MatrixClient): MatrixMediaLoader {
|
fun provideMediaLoader(matrixClient: MatrixClient): MatrixMediaLoader {
|
||||||
return matrixClient.mediaLoader
|
return matrixClient.mediaLoader
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.matrix.impl.encryption
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||||
|
import org.matrix.rustcomponents.sdk.BackupUploadState as RustBackupUploadState
|
||||||
|
|
||||||
|
class BackupUploadStateMapper {
|
||||||
|
fun map(rustEnableProgress: RustBackupUploadState): BackupUploadState {
|
||||||
|
return when (rustEnableProgress) {
|
||||||
|
is RustBackupUploadState.CheckingIfUploadNeeded ->
|
||||||
|
BackupUploadState.CheckingIfUploadNeeded(
|
||||||
|
backedUpCount = rustEnableProgress.backedUpCount.toInt(),
|
||||||
|
totalCount = rustEnableProgress.totalCount.toInt(),
|
||||||
|
)
|
||||||
|
RustBackupUploadState.Done ->
|
||||||
|
BackupUploadState.Done
|
||||||
|
is RustBackupUploadState.Uploading ->
|
||||||
|
BackupUploadState.Uploading(
|
||||||
|
backedUpCount = rustEnableProgress.backedUpCount.toInt(),
|
||||||
|
totalCount = rustEnableProgress.totalCount.toInt(),
|
||||||
|
)
|
||||||
|
RustBackupUploadState.Waiting ->
|
||||||
|
BackupUploadState.Waiting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.matrix.impl.encryption
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress
|
||||||
|
import org.matrix.rustcomponents.sdk.EnableRecoveryProgress as RustEnableRecoveryProgress
|
||||||
|
|
||||||
|
class EnableRecoveryProgressMapper {
|
||||||
|
fun map(rustEnableProgress: RustEnableRecoveryProgress): EnableRecoveryProgress {
|
||||||
|
return when (rustEnableProgress) {
|
||||||
|
is RustEnableRecoveryProgress.CreatingRecoveryKey -> EnableRecoveryProgress.CreatingRecoveryKey
|
||||||
|
is RustEnableRecoveryProgress.CreatingBackup -> EnableRecoveryProgress.CreatingBackup
|
||||||
|
is RustEnableRecoveryProgress.BackingUp -> EnableRecoveryProgress.BackingUp(
|
||||||
|
backedUpCount = rustEnableProgress.backedUpCount.toInt(),
|
||||||
|
totalCount = rustEnableProgress.totalCount.toInt(),
|
||||||
|
)
|
||||||
|
is RustEnableRecoveryProgress.Done -> EnableRecoveryProgress.Done(
|
||||||
|
recoveryKey = rustEnableProgress.recoveryKey
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.matrix.impl.encryption
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||||
|
import org.matrix.rustcomponents.sdk.RecoveryState as RustRecoveryState
|
||||||
|
|
||||||
|
class RecoveryStateMapper {
|
||||||
|
fun map(state: RustRecoveryState): RecoveryState {
|
||||||
|
return when (state) {
|
||||||
|
RustRecoveryState.UNKNOWN -> RecoveryState.UNKNOWN
|
||||||
|
RustRecoveryState.ENABLED -> RecoveryState.ENABLED
|
||||||
|
RustRecoveryState.DISABLED -> RecoveryState.DISABLED
|
||||||
|
RustRecoveryState.INCOMPLETE -> RecoveryState.INCOMPLETE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.matrix.impl.encryption
|
||||||
|
|
||||||
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.rustcomponents.sdk.BackupStateListener
|
||||||
|
import org.matrix.rustcomponents.sdk.BackupSteadyStateListener
|
||||||
|
import org.matrix.rustcomponents.sdk.Client
|
||||||
|
import org.matrix.rustcomponents.sdk.EnableRecoveryProgressListener
|
||||||
|
import org.matrix.rustcomponents.sdk.Encryption
|
||||||
|
import org.matrix.rustcomponents.sdk.RecoveryStateListener
|
||||||
|
import org.matrix.rustcomponents.sdk.BackupState as RustBackupState
|
||||||
|
import org.matrix.rustcomponents.sdk.BackupUploadState as RustBackupUploadState
|
||||||
|
import org.matrix.rustcomponents.sdk.EnableRecoveryProgress as RustEnableRecoveryProgress
|
||||||
|
import org.matrix.rustcomponents.sdk.RecoveryState as RustRecoveryState
|
||||||
|
|
||||||
|
internal class RustEncryptionService(
|
||||||
|
client: Client,
|
||||||
|
private val dispatchers: CoroutineDispatchers,
|
||||||
|
) : EncryptionService {
|
||||||
|
|
||||||
|
private val service: Encryption = client.encryption()
|
||||||
|
|
||||||
|
private val backupStateMapper = BackupStateMapper()
|
||||||
|
private val recoveryStateMapper = RecoveryStateMapper()
|
||||||
|
private val enableRecoveryProgressMapper = EnableRecoveryProgressMapper()
|
||||||
|
private val backupUploadStateMapper = BackupUploadStateMapper()
|
||||||
|
|
||||||
|
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(service.backupState().let(backupStateMapper::map))
|
||||||
|
override val recoveryStateStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(service.recoveryState().let(recoveryStateMapper::map))
|
||||||
|
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Unknown)
|
||||||
|
override val backupUploadStateStateFlow: MutableStateFlow<BackupUploadState> = MutableStateFlow(BackupUploadState.Unknown)
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
service.backupStateListener(object : BackupStateListener {
|
||||||
|
override fun onUpdate(status: RustBackupState) {
|
||||||
|
backupStateStateFlow.value = backupStateMapper.map(status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
service.recoveryStateListener(object : RecoveryStateListener {
|
||||||
|
override fun onUpdate(status: RustRecoveryState) {
|
||||||
|
recoveryStateStateFlow.value = recoveryStateMapper.map(status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun destroy() {
|
||||||
|
// No way to remove the listeners...
|
||||||
|
service.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun enableBackups(): Result<Unit> = withContext(dispatchers.io) {
|
||||||
|
runCatching {
|
||||||
|
service.enableBackups()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun enableRecovery(
|
||||||
|
waitForBackupsToUpload: Boolean,
|
||||||
|
): Result<Unit> = withContext(dispatchers.io) {
|
||||||
|
runCatching {
|
||||||
|
service.enableRecovery(
|
||||||
|
waitForBackupsToUpload = waitForBackupsToUpload,
|
||||||
|
progressListener = object : EnableRecoveryProgressListener {
|
||||||
|
override fun onUpdate(status: RustEnableRecoveryProgress) {
|
||||||
|
enableRecoveryProgressStateFlow.value = enableRecoveryProgressMapper.map(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// enableRecovery returns the encryption key, but we read it from the state flow
|
||||||
|
.let { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun waitForBackupUploadSteadyState(
|
||||||
|
): Result<Unit> = withContext(dispatchers.io) {
|
||||||
|
runCatching {
|
||||||
|
service.waitForBackupUploadSteadyState(
|
||||||
|
progressListener = object : BackupSteadyStateListener {
|
||||||
|
override fun onUpdate(status: RustBackupUploadState) {
|
||||||
|
backupUploadStateStateFlow.value = backupUploadStateMapper.map(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun disableRecovery(): Result<Unit> = withContext(dispatchers.io) {
|
||||||
|
runCatching {
|
||||||
|
service.disableRecovery()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun isLastDevice(): Result<Boolean> = withContext(dispatchers.io) {
|
||||||
|
runCatching {
|
||||||
|
service.isLastDevice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun resetRecoveryKey(): Result<String> = withContext(dispatchers.io) {
|
||||||
|
runCatching {
|
||||||
|
service.resetRecoveryKey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit> = withContext(dispatchers.io) {
|
||||||
|
runCatching {
|
||||||
|
service.fixRecoveryIssues(recoveryKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,9 +33,8 @@ import org.matrix.rustcomponents.sdk.SessionVerificationController
|
||||||
import org.matrix.rustcomponents.sdk.SessionVerificationControllerDelegate
|
import org.matrix.rustcomponents.sdk.SessionVerificationControllerDelegate
|
||||||
import org.matrix.rustcomponents.sdk.SessionVerificationControllerInterface
|
import org.matrix.rustcomponents.sdk.SessionVerificationControllerInterface
|
||||||
import org.matrix.rustcomponents.sdk.SessionVerificationEmoji
|
import org.matrix.rustcomponents.sdk.SessionVerificationEmoji
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class RustSessionVerificationService @Inject constructor(
|
class RustSessionVerificationService(
|
||||||
private val syncService: RustSyncService,
|
private val syncService: RustSyncService,
|
||||||
private val sessionCoroutineScope: CoroutineScope,
|
private val sessionCoroutineScope: CoroutineScope,
|
||||||
) : SessionVerificationService, SessionVerificationControllerDelegate {
|
) : SessionVerificationService, SessionVerificationControllerDelegate {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||||
|
|
@ -33,6 +34,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
|
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||||
import io.element.android.libraries.matrix.test.media.FakeMediaLoader
|
import io.element.android.libraries.matrix.test.media.FakeMediaLoader
|
||||||
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
|
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
|
||||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||||
|
|
@ -55,6 +57,7 @@ class FakeMatrixClient(
|
||||||
private val notificationService: FakeNotificationService = FakeNotificationService(),
|
private val notificationService: FakeNotificationService = FakeNotificationService(),
|
||||||
private val notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
private val notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
||||||
private val syncService: FakeSyncService = FakeSyncService(),
|
private val syncService: FakeSyncService = FakeSyncService(),
|
||||||
|
private val encryptionService: FakeEncryptionService = FakeEncryptionService(),
|
||||||
private val accountManagementUrlString: Result<String?> = Result.success(null),
|
private val accountManagementUrlString: Result<String?> = Result.success(null),
|
||||||
) : MatrixClient {
|
) : MatrixClient {
|
||||||
|
|
||||||
|
|
@ -124,9 +127,11 @@ class FakeMatrixClient(
|
||||||
override suspend fun clearCache() {
|
override suspend fun clearCache() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun logout(): String? {
|
override suspend fun logout(ignoreSdkError: Boolean): String? {
|
||||||
delay(100)
|
delay(100)
|
||||||
logoutFailure?.let { throw it }
|
if (ignoreSdkError.not()) {
|
||||||
|
logoutFailure?.let { throw it }
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,6 +178,7 @@ class FakeMatrixClient(
|
||||||
|
|
||||||
override fun notificationService(): NotificationService = notificationService
|
override fun notificationService(): NotificationService = notificationService
|
||||||
override fun notificationSettingsService(): NotificationSettingsService = notificationSettingsService
|
override fun notificationSettingsService(): NotificationSettingsService = notificationSettingsService
|
||||||
|
override fun encryptionService(): EncryptionService = encryptionService
|
||||||
|
|
||||||
override fun roomMembershipObserver(): RoomMembershipObserver {
|
override fun roomMembershipObserver(): RoomMembershipObserver {
|
||||||
return RoomMembershipObserver()
|
return RoomMembershipObserver()
|
||||||
|
|
|
||||||
|
|
@ -72,3 +72,5 @@ const val A_FAILURE_REASON = "There has been a failure"
|
||||||
|
|
||||||
val A_THROWABLE = Throwable(A_FAILURE_REASON)
|
val A_THROWABLE = Throwable(A_FAILURE_REASON)
|
||||||
val AN_EXCEPTION = Exception(A_FAILURE_REASON)
|
val AN_EXCEPTION = Exception(A_FAILURE_REASON)
|
||||||
|
|
||||||
|
const val A_RECOVERY_KEY = "1234 5678"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.matrix.test.encryption
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||||
|
import io.element.android.tests.testutils.simulateLongTask
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
|
class FakeEncryptionService : EncryptionService {
|
||||||
|
private var disableRecoveryFailure: Exception? = null
|
||||||
|
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN)
|
||||||
|
override val recoveryStateStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(RecoveryState.UNKNOWN)
|
||||||
|
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Unknown)
|
||||||
|
override val backupUploadStateStateFlow: MutableStateFlow<BackupUploadState> = MutableStateFlow(BackupUploadState.Unknown)
|
||||||
|
|
||||||
|
private var fixRecoveryIssuesFailure: Exception? = null
|
||||||
|
|
||||||
|
override suspend fun enableBackups(): Result<Unit> = simulateLongTask {
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenDisableRecoveryFailure(exception: Exception) {
|
||||||
|
disableRecoveryFailure = exception
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenFixRecoveryIssuesFailure(exception: Exception?) {
|
||||||
|
fixRecoveryIssuesFailure = exception
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun disableRecovery(): Result<Unit> = simulateLongTask {
|
||||||
|
disableRecoveryFailure?.let { return Result.failure(it) }
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit> = simulateLongTask {
|
||||||
|
fixRecoveryIssuesFailure?.let { return Result.failure(it) }
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isLastDevice = false
|
||||||
|
|
||||||
|
fun givenIsLastDevice(isLastDevice: Boolean) {
|
||||||
|
this.isLastDevice = isLastDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun isLastDevice(): Result<Boolean> {
|
||||||
|
return Result.success(isLastDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun resetRecoveryKey(): Result<String> = simulateLongTask {
|
||||||
|
return Result.success(fakeRecoveryKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun enableRecovery(waitForBackupsToUpload: Boolean): Result<Unit> = simulateLongTask {
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun waitForBackupUploadSteadyState(): Result<Unit> {
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun emitBackupUploadState(state: BackupUploadState) {
|
||||||
|
backupUploadStateStateFlow.emit(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun emitBackupState(state: BackupState) {
|
||||||
|
backupStateStateFlow.emit(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun emitEnableRecoveryProgress(state: EnableRecoveryProgress) {
|
||||||
|
enableRecoveryProgressStateFlow.emit(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val fakeRecoveryKey = "fake"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:18378d89af53ac54931d3feb3129ff90e0e45c707144322de3d8740fd3ce3645
|
oid sha256:e9ea30725d03749028f907e341ee09bb98a67cc6bda76757f7b3d266fed3722f
|
||||||
size 6436
|
size 7177
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d95ae8cc103ad50397c804ba2fe16c3b4fb380456f2b3be6f99ec62877b36758
|
oid sha256:52ae85fdd9eeb0e3d81336c38147d8251079e2ae61364ca7f7cd573676dc2c57
|
||||||
size 6429
|
size 7085
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:10058823be0d5f0a23e1712d4523188729dd7579005a48a63e8e553baea105c0
|
oid sha256:da00e2729bcd8ac55de08a2ff2e101e6746a64a5fdc07c8ddbb8297543dc493f
|
||||||
size 5936
|
size 6663
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:12eacc1babdc55ec2faed630889157ea0c9e6dca8845ecc93bc72bd645f23a9e
|
oid sha256:d44ed3c1ff4607171e21c580f337ca0a5664992fb90d2072b511af8e72ac8353
|
||||||
size 6481
|
size 7070
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2e4ee91fc8599e4c3e1d093587a9688c36df971a94bb832e6662c86572fb08c7
|
oid sha256:7eb5e7f99047da383a6bc5a45eb76bd902c21fcede196626fc4f6f5507097d51
|
||||||
size 6436
|
size 7333
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:a2649d3974b096f17a6e80d728b08409c0f1b28ed649f33c7798f6223f55ca29
|
oid sha256:d4a2824fdc49adb0e3fc155e7da319414544a87dc083bdece78e85ba72669d78
|
||||||
size 6343
|
size 7155
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7841691e0bbcfa0655304ac6a7c0ea24353aae9bfc264dc7978c2a4526781ee3
|
oid sha256:3817e348ba2775528e3805ad6f5eed044f82b241f73692180c0638c95fb79fa2
|
||||||
size 6357
|
size 7076
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d1a1ad6a3c02f24a8b811197f6999d93db57d3c1e9b867638c88f4759e9f1b87
|
oid sha256:fd21f1c5ad2616b698a386d78d5efe0ab40aa1959382ed2a568c10441d093a8f
|
||||||
size 5926
|
size 6692
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0adbfdd9131913c11d7e4777b4bdfbda84b3a9c6f1784781698d9208023e9c39
|
oid sha256:07f52161378dfab92ffb76b860680390ccb5dfcc05347c5fd99504c806dd3210
|
||||||
size 6400
|
size 6930
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:cf18143a576ef0a136ca6e07c51521f5d41b6b5f381d168ff311cdf099752550
|
oid sha256:4adce5249b20bdc9ed6f4d5f066140e8c9b0f62d04ebf953704a106a6ca2ff53
|
||||||
size 6349
|
size 7178
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:216e36042ad1ef11331d8610ab32383112bde38e314ae0d8c42d23931c7874a2
|
oid sha256:5c52825ef36b00ee13496c23db981c226a3f71c0d1755508835b4ab3a140cff6
|
||||||
size 43748
|
size 46402
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2542c95c067d4d65e76ea606801dc3dbfc196d8128a8547ed3e1bd93b84d4ab9
|
oid sha256:ceb0cfa1b7ac95de658e339d29b4e18d8e4673d1a269db3d46bc6fd97381e9c2
|
||||||
size 42513
|
size 45460
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:efc60fcfb39718bfce62f16e82a0c85249b71bf850b2d996bb1234a882261bc9
|
oid sha256:dbe3214d0b8c497f563957c9df870aa7b6cbf981e174a7f9481cc1555af50533
|
||||||
size 10021
|
size 10247
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:f6f5b1b3ce84e262eec2f5fea04e4d8d15d0ec4dabd589b73b8b0c0b41afb514
|
oid sha256:876ac7c1ac9f3a188bd4ebcc4faef6fd363e166b1ec3d5a844872f7a0cf676aa
|
||||||
size 9754
|
size 9969
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue