mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 18:35:40 +03:00
Merge pull request #4325 from vector-im/feature/bca/auto_uisi
Auto report unable to decrypt errors via lab option
This commit is contained in:
commit
30aae3f07a
26 changed files with 860 additions and 24 deletions
|
@ -36,6 +36,7 @@
|
|||
<w>ssss</w>
|
||||
<w>sygnal</w>
|
||||
<w>threepid</w>
|
||||
<w>uisi</w>
|
||||
<w>unpublish</w>
|
||||
<w>unwedging</w>
|
||||
<w>vctr</w>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.api.session
|
||||
|
||||
interface EventStreamService {
|
||||
|
||||
fun addEventStreamListener(streamListener: LiveEventListener)
|
||||
|
||||
fun removeEventStreamListener(streamListener: LiveEventListener)
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.api.session
|
||||
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
|
||||
interface LiveEventListener {
|
||||
|
||||
fun onLiveEvent(roomId: String, event: Event)
|
||||
|
||||
fun onPaginatedEvent(roomId: String, event: Event)
|
||||
|
||||
fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict)
|
||||
|
||||
fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable)
|
||||
|
||||
fun onLiveToDeviceEvent(event: Event)
|
||||
|
||||
// Maybe later add more, like onJoin, onLeave..
|
||||
}
|
|
@ -84,7 +84,9 @@ interface Session :
|
|||
SyncStatusService,
|
||||
HomeServerCapabilitiesService,
|
||||
SecureStorageService,
|
||||
AccountService {
|
||||
AccountService,
|
||||
ToDeviceService,
|
||||
EventStreamService {
|
||||
|
||||
val coroutineDispatchers: MatrixCoroutineDispatchers
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.api.session
|
||||
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||
import java.util.UUID
|
||||
|
||||
interface ToDeviceService {
|
||||
|
||||
/**
|
||||
* Send an event to a specific list of devices
|
||||
*/
|
||||
suspend fun sendToDevice(eventType: String, contentMap: MXUsersDevicesMap<Any>, txnId: String? = UUID.randomUUID().toString())
|
||||
|
||||
suspend fun sendToDevice(eventType: String, userId: String, deviceId: String, content: Content, txnId: String? = UUID.randomUUID().toString()) {
|
||||
sendToDevice(eventType, mapOf(userId to listOf(deviceId)), content, txnId)
|
||||
}
|
||||
|
||||
suspend fun sendToDevice(eventType: String, targets: Map<String, List<String>>, content: Content, txnId: String? = UUID.randomUUID().toString())
|
||||
|
||||
suspend fun sendEncryptedToDevice(eventType: String, targets: Map<String, List<String>>, content: Content, txnId: String? = UUID.randomUUID().toString())
|
||||
}
|
|
@ -90,6 +90,7 @@ import org.matrix.android.sdk.internal.di.MoshiProvider
|
|||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.extensions.foldToCallback
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.task.TaskThread
|
||||
|
@ -168,7 +169,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val eventDecryptor: EventDecryptor
|
||||
private val eventDecryptor: EventDecryptor,
|
||||
private val liveEventManager: Lazy<StreamEventsManager>
|
||||
) : CryptoService {
|
||||
|
||||
private val isStarting = AtomicBoolean(false)
|
||||
|
@ -782,6 +784,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
liveEventManager.get().dispatchOnLiveToDevice(event)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto.algorithms.megolm
|
||||
|
||||
import dagger.Lazy
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
|
@ -43,6 +44,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent
|
|||
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
||||
import timber.log.Timber
|
||||
|
||||
private val loggerTag = LoggerTag("MXMegolmDecryption", LoggerTag.CRYPTO)
|
||||
|
@ -56,7 +58,8 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
private val cryptoStore: IMXCryptoStore,
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val cryptoCoroutineScope: CoroutineScope
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val liveEventManager: Lazy<StreamEventsManager>
|
||||
) : IMXDecrypting, IMXWithHeldExtension {
|
||||
|
||||
var newSessionListener: NewSessionListener? = null
|
||||
|
@ -108,12 +111,15 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
|
||||
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
|
||||
.orEmpty()
|
||||
)
|
||||
).also {
|
||||
liveEventManager.get().dispatchLiveEventDecrypted(event, it)
|
||||
}
|
||||
} else {
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||
}
|
||||
},
|
||||
{ throwable ->
|
||||
liveEventManager.get().dispatchLiveEventDecryptionFailed(event, throwable)
|
||||
if (throwable is MXCryptoError.OlmError) {
|
||||
// TODO Check the value of .message
|
||||
if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto.algorithms.megolm
|
||||
|
||||
import dagger.Lazy
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||
|
@ -26,6 +27,7 @@ import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
|||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class MXMegolmDecryptionFactory @Inject constructor(
|
||||
|
@ -38,7 +40,8 @@ internal class MXMegolmDecryptionFactory @Inject constructor(
|
|||
private val cryptoStore: IMXCryptoStore,
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val cryptoCoroutineScope: CoroutineScope
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val eventsManager: Lazy<StreamEventsManager>
|
||||
) {
|
||||
|
||||
fun create(): MXMegolmDecryption {
|
||||
|
@ -52,6 +55,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor(
|
|||
cryptoStore,
|
||||
sendToDeviceTask,
|
||||
coroutineDispatchers,
|
||||
cryptoCoroutineScope)
|
||||
cryptoCoroutineScope,
|
||||
eventsManager)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.session
|
||||
|
||||
import org.matrix.android.sdk.api.session.EventStreamService
|
||||
import org.matrix.android.sdk.api.session.LiveEventListener
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultEventStreamService @Inject constructor(
|
||||
private val streamEventsManager: StreamEventsManager
|
||||
) : EventStreamService {
|
||||
|
||||
override fun addEventStreamListener(streamListener: LiveEventListener) {
|
||||
streamEventsManager.addLiveEventListener(streamListener)
|
||||
}
|
||||
|
||||
override fun removeEventStreamListener(streamListener: LiveEventListener) {
|
||||
streamEventsManager.removeLiveEventListener(streamListener)
|
||||
}
|
||||
}
|
|
@ -27,8 +27,10 @@ import org.matrix.android.sdk.api.auth.data.SessionParams
|
|||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
import org.matrix.android.sdk.api.federation.FederationService
|
||||
import org.matrix.android.sdk.api.pushrules.PushRuleService
|
||||
import org.matrix.android.sdk.api.session.EventStreamService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||
import org.matrix.android.sdk.api.session.ToDeviceService
|
||||
import org.matrix.android.sdk.api.session.account.AccountService
|
||||
import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
|
||||
import org.matrix.android.sdk.api.session.cache.CacheService
|
||||
|
@ -133,6 +135,8 @@ internal class DefaultSession @Inject constructor(
|
|||
private val spaceService: Lazy<SpaceService>,
|
||||
private val openIdService: Lazy<OpenIdService>,
|
||||
private val presenceService: Lazy<PresenceService>,
|
||||
private val toDeviceService: Lazy<ToDeviceService>,
|
||||
private val eventStreamService: Lazy<EventStreamService>,
|
||||
@UnauthenticatedWithCertificate
|
||||
private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>
|
||||
) : Session,
|
||||
|
@ -152,7 +156,9 @@ internal class DefaultSession @Inject constructor(
|
|||
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
|
||||
ProfileService by profileService.get(),
|
||||
PresenceService by presenceService.get(),
|
||||
AccountService by accountService.get() {
|
||||
AccountService by accountService.get(),
|
||||
ToDeviceService by toDeviceService.get(),
|
||||
EventStreamService by eventStreamService.get() {
|
||||
|
||||
override val sharedSecretStorageService: SharedSecretStorageService
|
||||
get() = _sharedSecretStorageService.get()
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.session
|
||||
|
||||
import org.matrix.android.sdk.api.session.ToDeviceService
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultToDeviceService @Inject constructor(
|
||||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val messageEncrypter: MessageEncrypter,
|
||||
private val cryptoStore: IMXCryptoStore
|
||||
) : ToDeviceService {
|
||||
|
||||
override suspend fun sendToDevice(eventType: String, targets: Map<String, List<String>>, content: Content, txnId: String?) {
|
||||
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
||||
targets.forEach { (userId, deviceIdList) ->
|
||||
deviceIdList.forEach { deviceId ->
|
||||
sendToDeviceMap.setObject(userId, deviceId, content)
|
||||
}
|
||||
}
|
||||
sendToDevice(eventType, sendToDeviceMap, txnId)
|
||||
}
|
||||
|
||||
override suspend fun sendToDevice(eventType: String, contentMap: MXUsersDevicesMap<Any>, txnId: String?) {
|
||||
sendToDeviceTask.executeRetry(
|
||||
SendToDeviceTask.Params(
|
||||
eventType = eventType,
|
||||
contentMap = contentMap,
|
||||
transactionId = txnId
|
||||
),
|
||||
3
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun sendEncryptedToDevice(eventType: String, targets: Map<String, List<String>>, content: Content, txnId: String?) {
|
||||
val payloadJson = mapOf(
|
||||
"type" to eventType,
|
||||
"content" to content
|
||||
)
|
||||
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
||||
|
||||
// Should I do an ensure olm session?
|
||||
targets.forEach { (userId, deviceIdList) ->
|
||||
deviceIdList.forEach { deviceId ->
|
||||
cryptoStore.getUserDevice(userId, deviceId)?.let { deviceInfo ->
|
||||
sendToDeviceMap.setObject(userId, deviceId, messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendToDevice(EventType.ENCRYPTED, sendToDeviceMap, txnId)
|
||||
}
|
||||
}
|
|
@ -32,8 +32,10 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
|||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||
import org.matrix.android.sdk.api.auth.data.sessionId
|
||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||
import org.matrix.android.sdk.api.session.EventStreamService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||
import org.matrix.android.sdk.api.session.ToDeviceService
|
||||
import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
|
||||
import org.matrix.android.sdk.api.session.events.EventService
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||
|
@ -374,6 +376,12 @@ internal abstract class SessionModule {
|
|||
@Binds
|
||||
abstract fun bindOpenIdTokenService(service: DefaultOpenIdService): OpenIdService
|
||||
|
||||
@Binds
|
||||
abstract fun bindToDeviceService(service: DefaultToDeviceService): ToDeviceService
|
||||
|
||||
@Binds
|
||||
abstract fun bindEventStreamService(service: DefaultEventStreamService): EventStreamService
|
||||
|
||||
@Binds
|
||||
abstract fun bindTypingUsersTracker(tracker: DefaultTypingUsersTracker): TypingUsersTracker
|
||||
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.session
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.LiveEventListener
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
internal class StreamEventsManager @Inject constructor() {
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
|
||||
private val listeners = mutableListOf<LiveEventListener>()
|
||||
|
||||
fun addLiveEventListener(listener: LiveEventListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
fun removeLiveEventListener(listener: LiveEventListener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
fun dispatchLiveEventReceived(event: Event, roomId: String, initialSync: Boolean) {
|
||||
Timber.v("## dispatchLiveEventReceived ${event.eventId}")
|
||||
coroutineScope.launch {
|
||||
if (!initialSync) {
|
||||
listeners.forEach {
|
||||
tryOrNull {
|
||||
it.onLiveEvent(roomId, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun dispatchPaginatedEventReceived(event: Event, roomId: String) {
|
||||
Timber.v("## dispatchPaginatedEventReceived ${event.eventId}")
|
||||
coroutineScope.launch {
|
||||
listeners.forEach {
|
||||
tryOrNull {
|
||||
it.onPaginatedEvent(roomId, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun dispatchLiveEventDecrypted(event: Event, result: MXEventDecryptionResult) {
|
||||
Timber.v("## dispatchLiveEventDecrypted ${event.eventId}")
|
||||
coroutineScope.launch {
|
||||
listeners.forEach {
|
||||
tryOrNull {
|
||||
it.onEventDecrypted(event.eventId ?: "", event.roomId ?: "", result.clearEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun dispatchLiveEventDecryptionFailed(event: Event, error: Throwable) {
|
||||
Timber.v("## dispatchLiveEventDecryptionFailed ${event.eventId}")
|
||||
coroutineScope.launch {
|
||||
listeners.forEach {
|
||||
tryOrNull {
|
||||
it.onEventDecryptionError(event.eventId ?: "", event.roomId ?: "", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun dispatchOnLiveToDevice(event: Event) {
|
||||
Timber.v("## dispatchOnLiveToDevice ${event.eventId}")
|
||||
coroutineScope.launch {
|
||||
listeners.forEach {
|
||||
tryOrNull {
|
||||
it.onLiveToDeviceEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package org.matrix.android.sdk.internal.session.room.timeline
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import dagger.Lazy
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
|
@ -35,6 +36,7 @@ import org.matrix.android.sdk.internal.database.query.create
|
|||
import org.matrix.android.sdk.internal.database.query.find
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
@ -42,7 +44,9 @@ import javax.inject.Inject
|
|||
/**
|
||||
* Insert Chunk in DB, and eventually link next and previous chunk in db.
|
||||
*/
|
||||
internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase private val monarchy: Monarchy) {
|
||||
internal class TokenChunkEventPersistor @Inject constructor(
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val liveEventManager: Lazy<StreamEventsManager>) {
|
||||
|
||||
enum class Result {
|
||||
SHOULD_FETCH_MORE,
|
||||
|
@ -170,6 +174,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
|
|||
}
|
||||
roomMemberContentsByUser[event.stateKey] = contentToUse.toModel<RoomMemberContent>()
|
||||
}
|
||||
liveEventManager.get().dispatchPaginatedEventReceived(event, roomId)
|
||||
currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.session.sync.handler.room
|
||||
|
||||
import dagger.Lazy
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
|
@ -52,6 +53,7 @@ import org.matrix.android.sdk.internal.database.query.where
|
|||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.extensions.clearWith
|
||||
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
||||
import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent
|
||||
import org.matrix.android.sdk.internal.session.initsync.ProgressReporter
|
||||
import org.matrix.android.sdk.internal.session.initsync.mapWithProgress
|
||||
|
@ -79,7 +81,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
private val threadsAwarenessHandler: ThreadsAwarenessHandler,
|
||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||
@UserId private val userId: String,
|
||||
private val timelineInput: TimelineInput) {
|
||||
private val timelineInput: TimelineInput,
|
||||
private val liveEventService: Lazy<StreamEventsManager>) {
|
||||
|
||||
sealed class HandlingStrategy {
|
||||
data class JOINED(val data: Map<String, RoomSync>) : HandlingStrategy()
|
||||
|
@ -364,6 +367,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
continue
|
||||
}
|
||||
eventIds.add(event.eventId)
|
||||
liveEventService.get().dispatchLiveEventReceived(event, roomId, insertType == EventInsertType.INITIAL_SYNC)
|
||||
|
||||
val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1
|
|||
# android\.text\.TextUtils
|
||||
|
||||
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
||||
enum class===118
|
||||
enum class===119
|
||||
|
||||
### Do not import temporary legacy classes
|
||||
import org.matrix.android.sdk.internal.legacy.riot===3
|
||||
|
|
273
vector/src/main/java/im/vector/app/AutoRageShaker.kt
Normal file
273
vector/src/main/java/im/vector/app/AutoRageShaker.kt
Normal file
|
@ -0,0 +1,273 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.features.rageshake.BugReporter
|
||||
import im.vector.app.features.rageshake.ReportType
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
const val AUTO_RS_REQUEST = "im.vector.auto_rs_request"
|
||||
|
||||
@Singleton
|
||||
class AutoRageShaker @Inject constructor(
|
||||
private val sessionDataSource: ActiveSessionDataSource,
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val bugReporter: BugReporter,
|
||||
private val context: Context,
|
||||
private val vectorPreferences: VectorPreferences
|
||||
) : Session.Listener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private val activeSessionIds = mutableSetOf<String>()
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
private var currentActiveSessionId: String? = null
|
||||
|
||||
// Simple in memory cache of already sent report
|
||||
private data class ReportInfo(
|
||||
val roomId: String,
|
||||
val sessionId: String
|
||||
)
|
||||
|
||||
private val alreadyReportedUisi = mutableListOf<ReportInfo>()
|
||||
|
||||
private val e2eDetectedFlow = MutableSharedFlow<E2EMessageDetected>(replay = 0)
|
||||
private val matchingRSRequestFlow = MutableSharedFlow<Event>(replay = 0)
|
||||
|
||||
fun initialize() {
|
||||
observeActiveSession()
|
||||
// It's a singleton...
|
||||
vectorPreferences.subscribeToChanges(this)
|
||||
|
||||
// Simple rate limit, notice that order is not
|
||||
// necessarily preserved
|
||||
e2eDetectedFlow
|
||||
.onEach {
|
||||
sendRageShake(it)
|
||||
delay(2_000)
|
||||
}
|
||||
.catch { cause ->
|
||||
Timber.w(cause, "Failed to RS")
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
|
||||
matchingRSRequestFlow
|
||||
.onEach {
|
||||
sendMatchingRageShake(it)
|
||||
delay(2_000)
|
||||
}
|
||||
.catch { cause ->
|
||||
Timber.w(cause, "Failed to send matching rageshake")
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
enable(vectorPreferences.labsAutoReportUISI())
|
||||
}
|
||||
|
||||
var _enabled = false
|
||||
fun enable(enabled: Boolean) {
|
||||
if (enabled == _enabled) return
|
||||
_enabled = enabled
|
||||
detector.enabled = enabled
|
||||
}
|
||||
|
||||
private fun observeActiveSession() {
|
||||
sessionDataSource.stream()
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
it.orNull()?.let { session ->
|
||||
onSessionActive(session)
|
||||
}
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
fun decryptionErrorDetected(target: E2EMessageDetected) {
|
||||
if (target.source == UISIEventSource.INITIAL_SYNC) return
|
||||
if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return
|
||||
val shouldSendRS = synchronized(alreadyReportedUisi) {
|
||||
val reportInfo = ReportInfo(target.roomId, target.sessionId)
|
||||
val alreadySent = alreadyReportedUisi.contains(reportInfo)
|
||||
if (!alreadySent) {
|
||||
alreadyReportedUisi.add(reportInfo)
|
||||
}
|
||||
!alreadySent
|
||||
}
|
||||
if (shouldSendRS) {
|
||||
coroutineScope.launch {
|
||||
e2eDetectedFlow.emit(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendRageShake(target: E2EMessageDetected) {
|
||||
bugReporter.sendBugReport(
|
||||
context = context,
|
||||
reportType = ReportType.AUTO_UISI,
|
||||
withDevicesLogs = true,
|
||||
withCrashLogs = true,
|
||||
withKeyRequestHistory = true,
|
||||
withScreenshot = false,
|
||||
theBugDescription = "UISI detected",
|
||||
serverVersion = "",
|
||||
canContact = false,
|
||||
customFields = mapOf("auto-uisi" to buildString {
|
||||
append("\neventId: ${target.eventId}")
|
||||
append("\nroomId: ${target.roomId}")
|
||||
append("\nsenderKey: ${target.senderKey}")
|
||||
append("\nsource: ${target.source}")
|
||||
append("\ndeviceId: ${target.senderDeviceId}")
|
||||
append("\nuserId: ${target.senderUserId}")
|
||||
append("\nsessionId: ${target.sessionId}")
|
||||
}),
|
||||
listener = object : BugReporter.IMXBugReportListener {
|
||||
override fun onUploadCancelled() {
|
||||
synchronized(alreadyReportedUisi) {
|
||||
alreadyReportedUisi.remove(ReportInfo(target.roomId, target.sessionId))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUploadFailed(reason: String?) {
|
||||
synchronized(alreadyReportedUisi) {
|
||||
alreadyReportedUisi.remove(ReportInfo(target.roomId, target.sessionId))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onProgress(progress: Int) {
|
||||
}
|
||||
|
||||
override fun onUploadSucceed(reportUrl: String?) {
|
||||
// we need to send the toDevice message to the sender
|
||||
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
activeSessionHolder.getSafeActiveSession()?.sendToDevice(
|
||||
eventType = AUTO_RS_REQUEST,
|
||||
userId = target.senderUserId,
|
||||
deviceId = target.senderDeviceId,
|
||||
content = mapOf(
|
||||
"event_id" to target.eventId,
|
||||
"room_id" to target.roomId,
|
||||
"session_id" to target.sessionId,
|
||||
"device_id" to target.senderDeviceId,
|
||||
"user_id" to target.senderUserId,
|
||||
"sender_key" to target.senderKey,
|
||||
"recipient_rageshake" to reportUrl
|
||||
).toContent()
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.w("failed to send auto-uisi to device")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun remoteAutoUISIRequest(event: Event) {
|
||||
if (event.type != AUTO_RS_REQUEST) return
|
||||
if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return
|
||||
|
||||
coroutineScope.launch {
|
||||
matchingRSRequestFlow.emit(event)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendMatchingRageShake(event: Event) {
|
||||
val eventId = event.content?.get("event_id")
|
||||
val roomId = event.content?.get("room_id")
|
||||
val sessionId = event.content?.get("session_id")
|
||||
val deviceId = event.content?.get("device_id")
|
||||
val userId = event.content?.get("user_id")
|
||||
val senderKey = event.content?.get("sender_key")
|
||||
val matchingIssue = event.content?.get("recipient_rageshake")?.toString() ?: ""
|
||||
|
||||
bugReporter.sendBugReport(
|
||||
context = context,
|
||||
reportType = ReportType.AUTO_UISI_SENDER,
|
||||
withDevicesLogs = true,
|
||||
withCrashLogs = true,
|
||||
withKeyRequestHistory = true,
|
||||
withScreenshot = false,
|
||||
theBugDescription = "UISI detected $matchingIssue",
|
||||
serverVersion = "",
|
||||
canContact = false,
|
||||
customFields = mapOf(
|
||||
"auto-uisi" to buildString {
|
||||
append("\neventId: $eventId")
|
||||
append("\nroomId: $roomId")
|
||||
append("\nsenderKey: $senderKey")
|
||||
append("\ndeviceId: $deviceId")
|
||||
append("\nuserId: $userId")
|
||||
append("\nsessionId: $sessionId")
|
||||
},
|
||||
"recipient_rageshake" to matchingIssue
|
||||
),
|
||||
listener = null
|
||||
)
|
||||
}
|
||||
|
||||
private val detector = UISIDetector().apply {
|
||||
callback = object : UISIDetector.UISIDetectorCallback {
|
||||
override val reciprocateToDeviceEventType: String
|
||||
get() = AUTO_RS_REQUEST
|
||||
|
||||
override fun uisiDetected(source: E2EMessageDetected) {
|
||||
decryptionErrorDetected(source)
|
||||
}
|
||||
|
||||
override fun uisiReciprocateRequest(source: Event) {
|
||||
remoteAutoUISIRequest(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onSessionActive(session: Session) {
|
||||
val sessionId = session.sessionId
|
||||
if (sessionId == currentActiveSessionId) {
|
||||
return
|
||||
}
|
||||
this.currentActiveSessionId = sessionId
|
||||
this.detector.enabled = _enabled
|
||||
activeSessionIds.add(sessionId)
|
||||
session.addListener(this)
|
||||
session.addEventStreamListener(detector)
|
||||
}
|
||||
|
||||
override fun onSessionStopped(session: Session) {
|
||||
session.removeEventStreamListener(detector)
|
||||
activeSessionIds.remove(session.sessionId)
|
||||
}
|
||||
}
|
160
vector/src/main/java/im/vector/app/UISIDetector.kt
Normal file
160
vector/src/main/java/im/vector/app/UISIDetector.kt
Normal file
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app
|
||||
|
||||
import org.matrix.android.sdk.api.session.LiveEventListener
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
||||
import timber.log.Timber
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
enum class UISIEventSource {
|
||||
INITIAL_SYNC,
|
||||
INCREMENTAL_SYNC,
|
||||
PAGINATION
|
||||
}
|
||||
|
||||
data class E2EMessageDetected(
|
||||
val eventId: String,
|
||||
val roomId: String,
|
||||
val senderUserId: String,
|
||||
val senderDeviceId: String,
|
||||
val senderKey: String,
|
||||
val sessionId: String,
|
||||
val source: UISIEventSource) {
|
||||
|
||||
companion object {
|
||||
fun fromEvent(event: Event, roomId: String, source: UISIEventSource): E2EMessageDetected {
|
||||
val encryptedContent = event.content.toModel<EncryptedEventContent>()
|
||||
|
||||
return E2EMessageDetected(
|
||||
eventId = event.eventId ?: "",
|
||||
roomId = roomId,
|
||||
senderUserId = event.senderId ?: "",
|
||||
senderDeviceId = encryptedContent?.deviceId ?: "",
|
||||
senderKey = encryptedContent?.senderKey ?: "",
|
||||
sessionId = encryptedContent?.sessionId ?: "",
|
||||
source = source
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UISIDetector : LiveEventListener {
|
||||
|
||||
interface UISIDetectorCallback {
|
||||
val reciprocateToDeviceEventType: String
|
||||
fun uisiDetected(source: E2EMessageDetected)
|
||||
fun uisiReciprocateRequest(source: Event)
|
||||
}
|
||||
|
||||
var callback: UISIDetectorCallback? = null
|
||||
|
||||
private val trackedEvents = mutableListOf<Pair<E2EMessageDetected, TimerTask>>()
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
private val timer = Timer()
|
||||
private val timeoutMillis = 30_000L
|
||||
var enabled = false
|
||||
|
||||
override fun onLiveEvent(roomId: String, event: Event) {
|
||||
if (!enabled) return
|
||||
if (!event.isEncrypted()) return
|
||||
executor.execute {
|
||||
handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.INCREMENTAL_SYNC))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPaginatedEvent(roomId: String, event: Event) {
|
||||
if (!enabled) return
|
||||
if (!event.isEncrypted()) return
|
||||
executor.execute {
|
||||
handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.PAGINATION))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict) {
|
||||
if (!enabled) return
|
||||
executor.execute {
|
||||
unTrack(eventId, roomId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLiveToDeviceEvent(event: Event) {
|
||||
if (!enabled) return
|
||||
if (event.type == callback?.reciprocateToDeviceEventType) {
|
||||
callback?.uisiReciprocateRequest(event)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable) {
|
||||
if (!enabled) return
|
||||
executor.execute {
|
||||
unTrack(eventId, roomId)?.let {
|
||||
triggerUISI(it)
|
||||
}
|
||||
// if (throwable is MXCryptoError.OlmError) {
|
||||
// if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
|
||||
// unTrack(eventId, roomId)?.let {
|
||||
// triggerUISI(it)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleEventReceived(detectorEvent: E2EMessageDetected) {
|
||||
if (!enabled) return
|
||||
if (trackedEvents.any { it.first == detectorEvent }) {
|
||||
Timber.w("## UISIDetector: Event ${detectorEvent.eventId} is already tracked")
|
||||
} else {
|
||||
// track it and start timer
|
||||
val timeoutTask = object : TimerTask() {
|
||||
override fun run() {
|
||||
executor.execute {
|
||||
unTrack(detectorEvent.eventId, detectorEvent.roomId)
|
||||
Timber.v("## UISIDetector: Timeout on ${detectorEvent.eventId} ")
|
||||
triggerUISI(detectorEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
trackedEvents.add(detectorEvent to timeoutTask)
|
||||
timer.schedule(timeoutTask, timeoutMillis)
|
||||
}
|
||||
}
|
||||
|
||||
private fun triggerUISI(source: E2EMessageDetected) {
|
||||
if (!enabled) return
|
||||
Timber.i("## UISIDetector: Unable To Decrypt $source")
|
||||
callback?.uisiDetected(source)
|
||||
}
|
||||
|
||||
private fun unTrack(eventId: String, roomId: String): E2EMessageDetected? {
|
||||
val index = trackedEvents.indexOfFirst { it.first.eventId == eventId && it.first.roomId == roomId }
|
||||
return if (index != -1) {
|
||||
trackedEvents.removeAt(index).let {
|
||||
it.second.cancel()
|
||||
it.first
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -96,6 +96,7 @@ class VectorApplication :
|
|||
@Inject lateinit var pinLocker: PinLocker
|
||||
@Inject lateinit var callManager: WebRtcCallManager
|
||||
@Inject lateinit var invitesAcceptor: InvitesAcceptor
|
||||
@Inject lateinit var autoRageShaker: AutoRageShaker
|
||||
@Inject lateinit var vectorFileLogger: VectorFileLogger
|
||||
@Inject lateinit var vectorAnalytics: VectorAnalytics
|
||||
|
||||
|
@ -117,6 +118,7 @@ class VectorApplication :
|
|||
appContext = this
|
||||
vectorAnalytics.init()
|
||||
invitesAcceptor.initialize()
|
||||
autoRageShaker.initialize()
|
||||
vectorUncaughtExceptionHandler.activate(this)
|
||||
|
||||
// Remove Log handler statically added by Jitsi
|
||||
|
|
|
@ -62,11 +62,11 @@ class BugReportActivity : VectorBaseActivity<ActivityBugReportBinding>() {
|
|||
|
||||
// Default screen is for bug report, so modify it for suggestion
|
||||
when (reportType) {
|
||||
ReportType.BUG_REPORT -> {
|
||||
ReportType.BUG_REPORT -> {
|
||||
supportActionBar?.setTitle(R.string.title_activity_bug_report)
|
||||
views.bugReportButtonContactMe.isVisible = true
|
||||
}
|
||||
ReportType.SUGGESTION -> {
|
||||
ReportType.SUGGESTION -> {
|
||||
supportActionBar?.setTitle(R.string.send_suggestion)
|
||||
|
||||
views.bugReportFirstText.setText(R.string.send_suggestion_content)
|
||||
|
@ -84,6 +84,9 @@ class BugReportActivity : VectorBaseActivity<ActivityBugReportBinding>() {
|
|||
|
||||
hideBugReportOptions()
|
||||
}
|
||||
else -> {
|
||||
// other types not supported here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,6 +159,7 @@ class BugReportActivity : VectorBaseActivity<ActivityBugReportBinding>() {
|
|||
views.bugReportEditText.text.toString(),
|
||||
state.serverVersion,
|
||||
views.bugReportButtonContactMe.isChecked,
|
||||
null,
|
||||
object : BugReporter.IMXBugReportListener {
|
||||
override fun onUploadFailed(reason: String?) {
|
||||
try {
|
||||
|
@ -173,6 +177,9 @@ class BugReportActivity : VectorBaseActivity<ActivityBugReportBinding>() {
|
|||
Toast.makeText(this@BugReportActivity,
|
||||
getString(R.string.feedback_failed, reason), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
else -> {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
@ -198,7 +205,7 @@ class BugReportActivity : VectorBaseActivity<ActivityBugReportBinding>() {
|
|||
views.bugReportProgressTextView.text = getString(R.string.send_bug_report_progress, myProgress.toString())
|
||||
}
|
||||
|
||||
override fun onUploadSucceed() {
|
||||
override fun onUploadSucceed(reportUrl: String?) {
|
||||
try {
|
||||
when (reportType) {
|
||||
ReportType.BUG_REPORT -> {
|
||||
|
@ -210,6 +217,9 @@ class BugReportActivity : VectorBaseActivity<ActivityBugReportBinding>() {
|
|||
ReportType.SPACE_BETA_FEEDBACK -> {
|
||||
Toast.makeText(this@BugReportActivity, R.string.feedback_sent, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
else -> {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## onUploadSucceed() : failed to dismiss the toast")
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.os.Build
|
|||
import android.view.View
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.squareup.moshi.Types
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
|
@ -49,7 +50,9 @@ import okhttp3.Response
|
|||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.api.util.MimeTypes
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
@ -93,6 +96,9 @@ class BugReporter @Inject constructor(
|
|||
// boolean to cancel the bug report
|
||||
private val mIsCancelled = false
|
||||
|
||||
val adapter = MoshiProvider.providesMoshi()
|
||||
.adapter<JsonDict>(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
|
||||
|
||||
/**
|
||||
* Get current Screenshot
|
||||
*
|
||||
|
@ -141,7 +147,7 @@ class BugReporter @Inject constructor(
|
|||
/**
|
||||
* The bug report upload succeeded.
|
||||
*/
|
||||
fun onUploadSucceed()
|
||||
fun onUploadSucceed(reportUrl: String?)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -166,12 +172,14 @@ class BugReporter @Inject constructor(
|
|||
theBugDescription: String,
|
||||
serverVersion: String,
|
||||
canContact: Boolean = false,
|
||||
customFields: Map<String, String>? = null,
|
||||
listener: IMXBugReportListener?) {
|
||||
// enumerate files to delete
|
||||
val mBugReportFiles: MutableList<File> = ArrayList()
|
||||
|
||||
coroutineScope.launch {
|
||||
var serverError: String? = null
|
||||
var reportURL: String? = null
|
||||
withContext(Dispatchers.IO) {
|
||||
var bugDescription = theBugDescription
|
||||
val crashCallStack = getCrashDescription(context)
|
||||
|
@ -247,9 +255,11 @@ class BugReporter @Inject constructor(
|
|||
|
||||
if (!mIsCancelled) {
|
||||
val text = when (reportType) {
|
||||
ReportType.BUG_REPORT -> "[Element] $bugDescription"
|
||||
ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription"
|
||||
ReportType.BUG_REPORT -> "[Element] $bugDescription"
|
||||
ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription"
|
||||
ReportType.SPACE_BETA_FEEDBACK -> "[Element] [spaces-feedback] $bugDescription"
|
||||
ReportType.AUTO_UISI_SENDER,
|
||||
ReportType.AUTO_UISI -> "[AutoUISI] $bugDescription"
|
||||
}
|
||||
|
||||
// build the multi part request
|
||||
|
@ -273,7 +283,11 @@ class BugReporter @Inject constructor(
|
|||
.addFormDataPart("app_language", VectorLocale.applicationLocale.toString())
|
||||
.addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString())
|
||||
.addFormDataPart("theme", ThemeUtils.getApplicationTheme(context))
|
||||
.addFormDataPart("server_version", serverVersion)
|
||||
.addFormDataPart("server_version", serverVersion).apply {
|
||||
customFields?.forEach { (name, value) ->
|
||||
addFormDataPart(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
val buildNumber = context.getString(R.string.build_number)
|
||||
if (buildNumber.isNotEmpty() && buildNumber != "0") {
|
||||
|
@ -321,11 +335,15 @@ class BugReporter @Inject constructor(
|
|||
builder.addFormDataPart("label", "[Element]")
|
||||
|
||||
when (reportType) {
|
||||
ReportType.BUG_REPORT -> {
|
||||
ReportType.BUG_REPORT -> {
|
||||
/* nop */
|
||||
}
|
||||
ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]")
|
||||
ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]")
|
||||
ReportType.SPACE_BETA_FEEDBACK -> builder.addFormDataPart("label", "spaces-feedback")
|
||||
ReportType.AUTO_UISI,
|
||||
ReportType.AUTO_UISI_SENDER -> {
|
||||
builder.addFormDataPart("label", "Z-UISI")
|
||||
}
|
||||
}
|
||||
|
||||
if (getCrashFile(context).exists()) {
|
||||
|
@ -417,6 +435,10 @@ class BugReporter @Inject constructor(
|
|||
Timber.e(e, "## sendBugReport() : failed to parse error")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reportURL = response?.body?.string()?.let { stringBody ->
|
||||
adapter.fromJson(stringBody)?.get("report_url")?.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -434,7 +456,7 @@ class BugReporter @Inject constructor(
|
|||
if (mIsCancelled) {
|
||||
listener.onUploadCancelled()
|
||||
} else if (null == serverError) {
|
||||
listener.onUploadSucceed()
|
||||
listener.onUploadSucceed(reportURL)
|
||||
} else {
|
||||
listener.onUploadFailed(serverError)
|
||||
}
|
||||
|
|
|
@ -19,5 +19,7 @@ package im.vector.app.features.rageshake
|
|||
enum class ReportType {
|
||||
BUG_REPORT,
|
||||
SUGGESTION,
|
||||
SPACE_BETA_FEEDBACK
|
||||
SPACE_BETA_FEEDBACK,
|
||||
AUTO_UISI,
|
||||
AUTO_UISI_SENDER,
|
||||
}
|
||||
|
|
|
@ -152,6 +152,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
|||
const val SETTINGS_LABS_ALLOW_EXTENDED_LOGS = "SETTINGS_LABS_ALLOW_EXTENDED_LOGS"
|
||||
const val SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE = "SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE"
|
||||
const val SETTINGS_LABS_SPACES_HOME_AS_ORPHAN = "SETTINGS_LABS_SPACES_HOME_AS_ORPHAN"
|
||||
const val SETTINGS_LABS_AUTO_REPORT_UISI = "SETTINGS_LABS_AUTO_REPORT_UISI"
|
||||
const val SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME = "SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME"
|
||||
|
||||
private const val SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY"
|
||||
|
@ -245,7 +246,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
|||
SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY,
|
||||
SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY,
|
||||
SETTINGS_LABS_ALLOW_EXTENDED_LOGS,
|
||||
SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE,
|
||||
// SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE,
|
||||
SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY,
|
||||
|
||||
SETTINGS_USE_RAGE_SHAKE_KEY,
|
||||
|
@ -974,6 +975,10 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
|||
return defaultPrefs.getBoolean(SETTINGS_LABS_SPACES_HOME_AS_ORPHAN, false)
|
||||
}
|
||||
|
||||
fun labsAutoReportUISI(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_LABS_AUTO_REPORT_UISI, false)
|
||||
}
|
||||
|
||||
fun prefSpacesShowAllRoomInHome(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME,
|
||||
// migration of old property
|
||||
|
|
|
@ -17,12 +17,20 @@
|
|||
package im.vector.app.features.settings
|
||||
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.preference.VectorSwitchPreference
|
||||
import javax.inject.Inject
|
||||
|
||||
class VectorSettingsLabsFragment @Inject constructor() : VectorSettingsBaseFragment() {
|
||||
class VectorSettingsLabsFragment @Inject constructor(
|
||||
private val vectorPreferences: VectorPreferences
|
||||
) : VectorSettingsBaseFragment() {
|
||||
|
||||
override var titleRes = R.string.room_settings_labs_pref_title
|
||||
override val preferenceXmlRes = R.xml.vector_settings_labs
|
||||
|
||||
override fun bindPref() {}
|
||||
override fun bindPref() {
|
||||
findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_LABS_AUTO_REPORT_UISI)?.let { pref ->
|
||||
// ensure correct default
|
||||
pref.isChecked = vectorPreferences.labsAutoReportUISI()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3565,6 +3565,11 @@
|
|||
|
||||
<string name="labs_use_restricted_join_rule">Experimental Space - Restricted Room.</string>
|
||||
<string name="labs_use_restricted_join_rule_desc">Warning requires server support and experimental room version</string>
|
||||
|
||||
|
||||
<string name="labs_auto_report_uisi">Auto Report Decryption Errors.</string>
|
||||
<string name="labs_auto_report_uisi_desc">Your system will automatically send logs when an unable to decrypt error occurs</string>
|
||||
|
||||
<string name="user_invites_you">%s invites you</string>
|
||||
|
||||
<string name="looking_for_someone_not_in_space">Looking for someone not in %s?</string>
|
||||
|
|
|
@ -63,4 +63,10 @@
|
|||
android:title="@string/labs_enable_polls" />
|
||||
|
||||
|
||||
<im.vector.app.core.preference.VectorSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="SETTINGS_LABS_AUTO_REPORT_UISI"
|
||||
android:title="@string/labs_auto_report_uisi"
|
||||
android:summary="@string/labs_auto_report_uisi_desc"/>
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
Loading…
Reference in a new issue