mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-09 00:29:00 +01:00
Fix audit freeze, add export, and buffer gossip saves
This commit is contained in:
parent
5a111af2fe
commit
c2027be0ee
@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.crypto
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||||
@ -40,6 +41,7 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
|||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
|
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
|
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
import kotlin.jvm.Throws
|
||||||
|
|
||||||
interface CryptoService {
|
interface CryptoService {
|
||||||
|
|
||||||
@ -142,10 +144,13 @@ interface CryptoService {
|
|||||||
fun removeSessionListener(listener: NewSessionListener)
|
fun removeSessionListener(listener: NewSessionListener)
|
||||||
|
|
||||||
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>
|
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>
|
||||||
|
fun getOutgoingRoomKeyRequestsPaged(): LiveData<PagedList<OutgoingRoomKeyRequest>>
|
||||||
|
|
||||||
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
||||||
|
fun getIncomingRoomKeyRequestsPaged(): LiveData<PagedList<IncomingRoomKeyRequest>>
|
||||||
|
|
||||||
fun getGossipingEventsTrail(): List<Event>
|
fun getGossipingEventsTrail(): LiveData<PagedList<Event>>
|
||||||
|
fun getGossipingEvents(): List<Event>
|
||||||
|
|
||||||
// For testing shared session
|
// For testing shared session
|
||||||
fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int>
|
fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int>
|
||||||
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
@ -185,6 +186,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val gossipingBuffer = mutableListOf<Event>()
|
||||||
|
|
||||||
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
|
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
|
||||||
setDeviceNameTask
|
setDeviceNameTask
|
||||||
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
|
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
|
||||||
@ -428,6 +431,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
incomingGossipingRequestManager.processReceivedGossipingRequests()
|
incomingGossipingRequestManager.processReceivedGossipingRequests()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tryOrNull {
|
||||||
|
gossipingBuffer.toList().let {
|
||||||
|
cryptoStore.saveGossipingEvents(it)
|
||||||
|
}
|
||||||
|
gossipingBuffer.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -721,19 +731,19 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
when (event.getClearType()) {
|
when (event.getClearType()) {
|
||||||
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
|
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
|
||||||
cryptoStore.saveGossipingEvent(event)
|
gossipingBuffer.add(event)
|
||||||
// Keys are imported directly, not waiting for end of sync
|
// Keys are imported directly, not waiting for end of sync
|
||||||
onRoomKeyEvent(event)
|
onRoomKeyEvent(event)
|
||||||
}
|
}
|
||||||
EventType.REQUEST_SECRET,
|
EventType.REQUEST_SECRET,
|
||||||
EventType.ROOM_KEY_REQUEST -> {
|
EventType.ROOM_KEY_REQUEST -> {
|
||||||
// save audit trail
|
// save audit trail
|
||||||
cryptoStore.saveGossipingEvent(event)
|
gossipingBuffer.add(event)
|
||||||
// Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete)
|
// Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete)
|
||||||
incomingGossipingRequestManager.onGossipingRequestEvent(event)
|
incomingGossipingRequestManager.onGossipingRequestEvent(event)
|
||||||
}
|
}
|
||||||
EventType.SEND_SECRET -> {
|
EventType.SEND_SECRET -> {
|
||||||
cryptoStore.saveGossipingEvent(event)
|
gossipingBuffer.add(event)
|
||||||
onSecretSendReceived(event)
|
onSecretSendReceived(event)
|
||||||
}
|
}
|
||||||
EventType.ROOM_KEY_WITHHELD -> {
|
EventType.ROOM_KEY_WITHHELD -> {
|
||||||
@ -1254,14 +1264,26 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
return cryptoStore.getOutgoingRoomKeyRequests()
|
return cryptoStore.getOutgoingRoomKeyRequests()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getOutgoingRoomKeyRequestsPaged(): LiveData<PagedList<OutgoingRoomKeyRequest>> {
|
||||||
|
return cryptoStore.getOutgoingRoomKeyRequestsPaged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIncomingRoomKeyRequestsPaged(): LiveData<PagedList<IncomingRoomKeyRequest>> {
|
||||||
|
return cryptoStore.getIncomingRoomKeyRequestsPaged()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest> {
|
override fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest> {
|
||||||
return cryptoStore.getIncomingRoomKeyRequests()
|
return cryptoStore.getIncomingRoomKeyRequests()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getGossipingEventsTrail(): List<Event> {
|
override fun getGossipingEventsTrail(): LiveData<PagedList<Event>> {
|
||||||
return cryptoStore.getGossipingEventsTrail()
|
return cryptoStore.getGossipingEventsTrail()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getGossipingEvents(): List<Event> {
|
||||||
|
return cryptoStore.getGossipingEvents()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int> {
|
override fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int> {
|
||||||
return cryptoStore.getSharedWithInfo(roomId, sessionId)
|
return cryptoStore.getSharedWithInfo(roomId, sessionId)
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.MatrixCallback
|
|||||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
@ -255,6 +256,15 @@ internal class MXMegolmEncryption(
|
|||||||
for ((userId, devicesToShareWith) in devicesByUser) {
|
for ((userId, devicesToShareWith) in devicesByUser) {
|
||||||
for ((deviceId) in devicesToShareWith) {
|
for ((deviceId) in devicesToShareWith) {
|
||||||
session.sharedWithHelper.markedSessionAsShared(userId, deviceId, chainIndex)
|
session.sharedWithHelper.markedSessionAsShared(userId, deviceId, chainIndex)
|
||||||
|
cryptoStore.saveGossipingEvent(Event(
|
||||||
|
type = EventType.ROOM_KEY,
|
||||||
|
senderId = credentials.userId,
|
||||||
|
content = submap.apply {
|
||||||
|
this["session_key"] = ""
|
||||||
|
// we add a fake key for trail
|
||||||
|
this["_dest"] = "$userId|$deviceId"
|
||||||
|
}
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package org.matrix.android.sdk.internal.crypto.store
|
package org.matrix.android.sdk.internal.crypto.store
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
@ -365,6 +366,7 @@ internal interface IMXCryptoStore {
|
|||||||
fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map<String, List<String>>): OutgoingSecretRequest?
|
fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map<String, List<String>>): OutgoingSecretRequest?
|
||||||
|
|
||||||
fun saveGossipingEvent(event: Event)
|
fun saveGossipingEvent(event: Event)
|
||||||
|
fun saveGossipingEvents(events: List<Event>)
|
||||||
|
|
||||||
fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) {
|
fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) {
|
||||||
updateGossipingRequestState(
|
updateGossipingRequestState(
|
||||||
@ -442,10 +444,13 @@ internal interface IMXCryptoStore {
|
|||||||
// Dev tools
|
// Dev tools
|
||||||
|
|
||||||
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>
|
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>
|
||||||
|
fun getOutgoingRoomKeyRequestsPaged(): LiveData<PagedList<OutgoingRoomKeyRequest>>
|
||||||
fun getOutgoingSecretKeyRequests(): List<OutgoingSecretRequest>
|
fun getOutgoingSecretKeyRequests(): List<OutgoingSecretRequest>
|
||||||
fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest?
|
fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest?
|
||||||
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
||||||
fun getGossipingEventsTrail(): List<Event>
|
fun getIncomingRoomKeyRequestsPaged(): LiveData<PagedList<IncomingRoomKeyRequest>>
|
||||||
|
fun getGossipingEventsTrail(): LiveData<PagedList<Event>>
|
||||||
|
fun getGossipingEvents(): List<Event>
|
||||||
|
|
||||||
fun setDeviceKeysUploaded(uploaded: Boolean)
|
fun setDeviceKeysUploaded(uploaded: Boolean)
|
||||||
fun getDeviceKeysUploaded(): Boolean
|
fun getDeviceKeysUploaded(): Boolean
|
||||||
|
@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.crypto.store.db
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
|
import androidx.paging.LivePagedListBuilder
|
||||||
|
import androidx.paging.PagedList
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
@ -62,6 +64,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFie
|
|||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity
|
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
|
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntity
|
import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntity
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntityFields
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntity
|
import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntity
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields
|
import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
|
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
|
||||||
@ -998,7 +1001,50 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getGossipingEventsTrail(): List<Event> {
|
override fun getIncomingRoomKeyRequestsPaged(): LiveData<PagedList<IncomingRoomKeyRequest>> {
|
||||||
|
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||||
|
realm.where<IncomingGossipingRequestEntity>()
|
||||||
|
.equalTo(IncomingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name)
|
||||||
|
.sort(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, Sort.DESCENDING)
|
||||||
|
}
|
||||||
|
val dataSourceFactory = realmDataSourceFactory.map {
|
||||||
|
it.toIncomingGossipingRequest() as? IncomingRoomKeyRequest
|
||||||
|
?: IncomingRoomKeyRequest(
|
||||||
|
requestBody = null,
|
||||||
|
deviceId = "",
|
||||||
|
userId = "",
|
||||||
|
requestId = "",
|
||||||
|
state = GossipingRequestState.NONE,
|
||||||
|
localCreationTimestamp = 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return monarchy.findAllPagedWithChanges(realmDataSourceFactory,
|
||||||
|
LivePagedListBuilder(dataSourceFactory,
|
||||||
|
PagedList.Config.Builder()
|
||||||
|
.setPageSize(20)
|
||||||
|
.setEnablePlaceholders(false)
|
||||||
|
.setPrefetchDistance(1)
|
||||||
|
.build())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getGossipingEventsTrail(): LiveData<PagedList<Event>> {
|
||||||
|
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||||
|
realm.where<GossipingEventEntity>().sort(GossipingEventEntityFields.AGE_LOCAL_TS, Sort.DESCENDING)
|
||||||
|
}
|
||||||
|
val dataSourceFactory = realmDataSourceFactory.map { it.toModel() }
|
||||||
|
val trail = monarchy.findAllPagedWithChanges(realmDataSourceFactory,
|
||||||
|
LivePagedListBuilder(dataSourceFactory,
|
||||||
|
PagedList.Config.Builder()
|
||||||
|
.setPageSize(20)
|
||||||
|
.setEnablePlaceholders(false)
|
||||||
|
.setPrefetchDistance(1)
|
||||||
|
.build())
|
||||||
|
)
|
||||||
|
return trail
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getGossipingEvents(): List<Event> {
|
||||||
return monarchy.fetchAllCopiedSync { realm ->
|
return monarchy.fetchAllCopiedSync { realm ->
|
||||||
realm.where<GossipingEventEntity>()
|
realm.where<GossipingEventEntity>()
|
||||||
}.map {
|
}.map {
|
||||||
@ -1066,24 +1112,43 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveGossipingEvent(event: Event) {
|
override fun saveGossipingEvents(events: List<Event>) {
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now
|
monarchy.writeAsync { realm ->
|
||||||
val entity = GossipingEventEntity(
|
events.forEach { event ->
|
||||||
type = event.type,
|
val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now
|
||||||
sender = event.senderId,
|
val entity = GossipingEventEntity(
|
||||||
ageLocalTs = ageLocalTs,
|
type = event.type,
|
||||||
content = ContentMapper.map(event.content)
|
sender = event.senderId,
|
||||||
).apply {
|
ageLocalTs = ageLocalTs,
|
||||||
sendState = SendState.SYNCED
|
content = ContentMapper.map(event.content)
|
||||||
decryptionResultJson = MoshiProvider.providesMoshi().adapter<OlmDecryptionResult>(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult)
|
).apply {
|
||||||
decryptionErrorCode = event.mCryptoError?.name
|
sendState = SendState.SYNCED
|
||||||
}
|
decryptionResultJson = MoshiProvider.providesMoshi().adapter<OlmDecryptionResult>(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult)
|
||||||
doRealmTransaction(realmConfiguration) { realm ->
|
decryptionErrorCode = event.mCryptoError?.name
|
||||||
realm.insertOrUpdate(entity)
|
}
|
||||||
|
realm.insertOrUpdate(entity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun saveGossipingEvent(event: Event) {
|
||||||
|
monarchy.writeAsync { realm ->
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now
|
||||||
|
val entity = GossipingEventEntity(
|
||||||
|
type = event.type,
|
||||||
|
sender = event.senderId,
|
||||||
|
ageLocalTs = ageLocalTs,
|
||||||
|
content = ContentMapper.map(event.content)
|
||||||
|
).apply {
|
||||||
|
sendState = SendState.SYNCED
|
||||||
|
decryptionResultJson = MoshiProvider.providesMoshi().adapter<OlmDecryptionResult>(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult)
|
||||||
|
decryptionErrorCode = event.mCryptoError?.name
|
||||||
|
}
|
||||||
|
realm.insertOrUpdate(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
// override fun getOutgoingRoomKeyRequestByState(states: Set<ShareRequestState>): OutgoingRoomKeyRequest? {
|
// override fun getOutgoingRoomKeyRequestByState(states: Set<ShareRequestState>): OutgoingRoomKeyRequest? {
|
||||||
// val statesIndex = states.map { it.ordinal }.toTypedArray()
|
// val statesIndex = states.map { it.ordinal }.toTypedArray()
|
||||||
// return doRealmQueryAndCopy(realmConfiguration) { realm ->
|
// return doRealmQueryAndCopy(realmConfiguration) { realm ->
|
||||||
@ -1439,6 +1504,27 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getOutgoingRoomKeyRequestsPaged(): LiveData<PagedList<OutgoingRoomKeyRequest>> {
|
||||||
|
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||||
|
realm
|
||||||
|
.where(OutgoingGossipingRequestEntity::class.java)
|
||||||
|
.equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name)
|
||||||
|
}
|
||||||
|
val dataSourceFactory = realmDataSourceFactory.map {
|
||||||
|
it.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest
|
||||||
|
?: OutgoingRoomKeyRequest(requestBody = null, requestId = "?", recipients = emptyMap(), state = OutgoingGossipingRequestState.CANCELLED)
|
||||||
|
}
|
||||||
|
val trail = monarchy.findAllPagedWithChanges(realmDataSourceFactory,
|
||||||
|
LivePagedListBuilder(dataSourceFactory,
|
||||||
|
PagedList.Config.Builder()
|
||||||
|
.setPageSize(20)
|
||||||
|
.setEnablePlaceholders(false)
|
||||||
|
.setPrefetchDistance(1)
|
||||||
|
.build())
|
||||||
|
)
|
||||||
|
return trail
|
||||||
|
}
|
||||||
|
|
||||||
override fun getCrossSigningInfo(userId: String): MXCrossSigningInfo? {
|
override fun getCrossSigningInfo(userId: String): MXCrossSigningInfo? {
|
||||||
return doWithRealm(realmConfiguration) { realm ->
|
return doWithRealm(realmConfiguration) { realm ->
|
||||||
val crossSigningInfo = realm.where(CrossSigningInfoEntity::class.java)
|
val crossSigningInfo = realm.where(CrossSigningInfoEntity::class.java)
|
||||||
|
@ -1,235 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2020 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.features.settings.devtools
|
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
|
||||||
import com.airbnb.mvrx.Fail
|
|
||||||
import com.airbnb.mvrx.Loading
|
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import com.airbnb.mvrx.Uninitialized
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.core.date.DateFormatKind
|
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
|
||||||
import im.vector.app.core.epoxy.loadingItem
|
|
||||||
import im.vector.app.core.extensions.exhaustive
|
|
||||||
import im.vector.app.core.resources.ColorProvider
|
|
||||||
import im.vector.app.core.resources.StringProvider
|
|
||||||
import im.vector.app.core.ui.list.GenericItem
|
|
||||||
import im.vector.app.core.ui.list.genericFooterItem
|
|
||||||
import im.vector.app.core.ui.list.genericItem
|
|
||||||
import im.vector.app.core.ui.list.genericItemHeader
|
|
||||||
import me.gujun.android.span.span
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GossipingEventsEpoxyController @Inject constructor(
|
|
||||||
private val stringProvider: StringProvider,
|
|
||||||
private val vectorDateFormatter: VectorDateFormatter,
|
|
||||||
private val colorProvider: ColorProvider
|
|
||||||
) : TypedEpoxyController<GossipingEventsPaperTrailState>() {
|
|
||||||
|
|
||||||
interface InteractionListener {
|
|
||||||
fun didTap(event: Event)
|
|
||||||
}
|
|
||||||
|
|
||||||
var interactionListener: InteractionListener? = null
|
|
||||||
|
|
||||||
override fun buildModels(data: GossipingEventsPaperTrailState?) {
|
|
||||||
when (val async = data?.events) {
|
|
||||||
is Uninitialized,
|
|
||||||
is Loading -> {
|
|
||||||
loadingItem {
|
|
||||||
id("loadingOutgoing")
|
|
||||||
loadingText(stringProvider.getString(R.string.loading))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Fail -> {
|
|
||||||
genericItem {
|
|
||||||
id("failOutgoing")
|
|
||||||
title(async.error.localizedMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Success -> {
|
|
||||||
val eventList = async.invoke()
|
|
||||||
if (eventList.isEmpty()) {
|
|
||||||
genericFooterItem {
|
|
||||||
id("empty")
|
|
||||||
text(stringProvider.getString(R.string.no_result_placeholder))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
eventList.forEachIndexed { _, event ->
|
|
||||||
genericItem {
|
|
||||||
id(event.hashCode())
|
|
||||||
itemClickAction(GenericItem.Action("view").apply { perform = Runnable { interactionListener?.didTap(event) } })
|
|
||||||
title(
|
|
||||||
if (event.isEncrypted()) {
|
|
||||||
"${event.getClearType()} [encrypted]"
|
|
||||||
} else {
|
|
||||||
event.type
|
|
||||||
}
|
|
||||||
)
|
|
||||||
description(
|
|
||||||
span {
|
|
||||||
+vectorDateFormatter.format(event.ageLocalTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
|
||||||
span("\nfrom: ") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+"${event.senderId}"
|
|
||||||
apply {
|
|
||||||
if (event.getClearType() == EventType.ROOM_KEY_REQUEST) {
|
|
||||||
val content = event.getClearContent().toModel<RoomKeyShareRequest>()
|
|
||||||
span("\nreqId:") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+" ${content?.requestId}"
|
|
||||||
span("\naction:") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+" ${content?.action}"
|
|
||||||
if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) {
|
|
||||||
span("\nsessionId:") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+" ${content.body?.sessionId}"
|
|
||||||
}
|
|
||||||
span("\nrequestedBy: ") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+"${content?.requestingDeviceId}"
|
|
||||||
} else if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
|
|
||||||
val encryptedContent = event.content.toModel<OlmEventContent>()
|
|
||||||
val content = event.getClearContent().toModel<ForwardedRoomKeyContent>()
|
|
||||||
if (event.mxDecryptionResult == null) {
|
|
||||||
span("**Failed to Decrypt** ${event.mCryptoError}") {
|
|
||||||
textColor = colorProvider.getColor(R.color.vector_error_color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
span("\nsessionId:") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+" ${content?.sessionId}"
|
|
||||||
span("\nFrom Device (sender key):") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+" ${encryptedContent?.senderKey}"
|
|
||||||
} else if (event.getClearType() == EventType.SEND_SECRET) {
|
|
||||||
val content = event.getClearContent().toModel<SecretSendEventContent>()
|
|
||||||
|
|
||||||
span("\nrequestId:") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+" ${content?.requestId}"
|
|
||||||
span("\nFrom Device:") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+" ${event.mxDecryptionResult?.payload?.get("sender_device")}"
|
|
||||||
} else if (event.getClearType() == EventType.REQUEST_SECRET) {
|
|
||||||
val content = event.getClearContent().toModel<SecretShareRequest>()
|
|
||||||
span("\nreqId:") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+" ${content?.requestId}"
|
|
||||||
span("\naction:") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+" ${content?.action}"
|
|
||||||
if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) {
|
|
||||||
span("\nsecretName:") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+" ${content.secretName}"
|
|
||||||
}
|
|
||||||
span("\nrequestedBy: ") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+"${content?.requestingDeviceId}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildOutgoing(data: KeyRequestListViewState?) {
|
|
||||||
data?.outgoingRoomKeyRequests?.let { async ->
|
|
||||||
when (async) {
|
|
||||||
is Uninitialized,
|
|
||||||
is Loading -> {
|
|
||||||
loadingItem {
|
|
||||||
id("loadingOutgoing")
|
|
||||||
loadingText(stringProvider.getString(R.string.loading))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Fail -> {
|
|
||||||
genericItem {
|
|
||||||
id("failOutgoing")
|
|
||||||
title(async.error.localizedMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Success -> {
|
|
||||||
if (async.invoke().isEmpty()) {
|
|
||||||
genericFooterItem {
|
|
||||||
id("empty")
|
|
||||||
text(stringProvider.getString(R.string.no_result_placeholder))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val requestList = async.invoke().groupBy { it.roomId }
|
|
||||||
|
|
||||||
requestList.forEach {
|
|
||||||
genericItemHeader {
|
|
||||||
id(it.key)
|
|
||||||
text("roomId: ${it.key}")
|
|
||||||
}
|
|
||||||
it.value.forEach { roomKeyRequest ->
|
|
||||||
genericItem {
|
|
||||||
id(roomKeyRequest.requestId)
|
|
||||||
title(roomKeyRequest.requestId)
|
|
||||||
description(
|
|
||||||
span {
|
|
||||||
span("sessionId:\n") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+"${roomKeyRequest.sessionId}"
|
|
||||||
span("\nstate:") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+"\n${roomKeyRequest.state.name}"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.exhaustive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -33,16 +33,19 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class GossipingEventsPaperTrailFragment @Inject constructor(
|
class GossipingEventsPaperTrailFragment @Inject constructor(
|
||||||
val viewModelFactory: GossipingEventsPaperTrailViewModel.Factory,
|
val viewModelFactory: GossipingEventsPaperTrailViewModel.Factory,
|
||||||
private val epoxyController: GossipingEventsEpoxyController,
|
private val epoxyController: GossipingTrailPagedEpoxyController,
|
||||||
private val colorProvider: ColorProvider
|
private val colorProvider: ColorProvider
|
||||||
) : VectorBaseFragment(), GossipingEventsEpoxyController.InteractionListener {
|
) : VectorBaseFragment(), GossipingTrailPagedEpoxyController.InteractionListener {
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
||||||
|
|
||||||
private val viewModel: GossipingEventsPaperTrailViewModel by fragmentViewModel(GossipingEventsPaperTrailViewModel::class)
|
private val viewModel: GossipingEventsPaperTrailViewModel by fragmentViewModel(GossipingEventsPaperTrailViewModel::class)
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
epoxyController.setData(state)
|
state.events.invoke()?.let {
|
||||||
|
epoxyController.submitList(it)
|
||||||
|
}
|
||||||
|
Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -16,13 +16,12 @@
|
|||||||
|
|
||||||
package im.vector.app.features.settings.devtools
|
package im.vector.app.features.settings.devtools
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.paging.PagedList
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.MvRxState
|
import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
@ -30,12 +29,12 @@ import com.squareup.inject.assisted.AssistedInject
|
|||||||
import im.vector.app.core.platform.EmptyAction
|
import im.vector.app.core.platform.EmptyAction
|
||||||
import im.vector.app.core.platform.EmptyViewEvents
|
import im.vector.app.core.platform.EmptyViewEvents
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.matrix.android.sdk.api.session.Session
|
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.Event
|
||||||
|
import org.matrix.android.sdk.rx.asObservable
|
||||||
|
|
||||||
data class GossipingEventsPaperTrailState(
|
data class GossipingEventsPaperTrailState(
|
||||||
val events: Async<List<Event>> = Uninitialized
|
val events: Async<PagedList<Event>> = Uninitialized
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
||||||
class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted initialState: GossipingEventsPaperTrailState,
|
class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted initialState: GossipingEventsPaperTrailState,
|
||||||
@ -50,14 +49,10 @@ class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted i
|
|||||||
setState {
|
setState {
|
||||||
copy(events = Loading())
|
copy(events = Loading())
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
session.cryptoService().getGossipingEventsTrail().asObservable()
|
||||||
session.cryptoService().getGossipingEventsTrail().let {
|
.execute {
|
||||||
val sorted = it.sortedByDescending { it.ageLocalTs }
|
copy(events = it)
|
||||||
setState {
|
|
||||||
copy(events = Success(sorted))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: EmptyAction) {}
|
override fun handle(action: EmptyAction) {}
|
||||||
|
@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.features.settings.devtools
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
|
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.core.ui.list.GenericItem
|
||||||
|
import im.vector.app.core.ui.list.GenericItem_
|
||||||
|
import im.vector.app.core.utils.createUIHandler
|
||||||
|
import me.gujun.android.span.span
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GossipingTrailPagedEpoxyController @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val vectorDateFormatter: VectorDateFormatter,
|
||||||
|
private val colorProvider: ColorProvider
|
||||||
|
) : PagedListEpoxyController<Event>(
|
||||||
|
// Important it must match the PageList builder notify Looper
|
||||||
|
modelBuildingHandler = createUIHandler()
|
||||||
|
) {
|
||||||
|
|
||||||
|
interface InteractionListener {
|
||||||
|
fun didTap(event: Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
var interactionListener: InteractionListener? = null
|
||||||
|
|
||||||
|
override fun buildItemModel(currentPosition: Int, item: Event?): EpoxyModel<*> {
|
||||||
|
val event = item ?: return GenericItem_().apply { id(currentPosition) }
|
||||||
|
return GenericItem_().apply {
|
||||||
|
id(event.hashCode())
|
||||||
|
itemClickAction(GenericItem.Action("view").apply { perform = Runnable { interactionListener?.didTap(event) } })
|
||||||
|
title(
|
||||||
|
if (event.isEncrypted()) {
|
||||||
|
"${event.getClearType()} [encrypted]"
|
||||||
|
} else {
|
||||||
|
event.type
|
||||||
|
}
|
||||||
|
)
|
||||||
|
description(
|
||||||
|
span {
|
||||||
|
+vectorDateFormatter.format(event.ageLocalTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
||||||
|
span("\nfrom: ") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+"${event.senderId}"
|
||||||
|
apply {
|
||||||
|
if (event.getClearType() == EventType.ROOM_KEY_REQUEST) {
|
||||||
|
val content = event.getClearContent().toModel<RoomKeyShareRequest>()
|
||||||
|
span("\nreqId:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+" ${content?.requestId}"
|
||||||
|
span("\naction:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+" ${content?.action}"
|
||||||
|
if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) {
|
||||||
|
span("\nsessionId:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+" ${content.body?.sessionId}"
|
||||||
|
}
|
||||||
|
span("\nrequestedBy: ") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+"${content?.requestingDeviceId}"
|
||||||
|
} else if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
|
||||||
|
val encryptedContent = event.content.toModel<OlmEventContent>()
|
||||||
|
val content = event.getClearContent().toModel<ForwardedRoomKeyContent>()
|
||||||
|
if (event.mxDecryptionResult == null) {
|
||||||
|
span("**Failed to Decrypt** ${event.mCryptoError}") {
|
||||||
|
textColor = colorProvider.getColor(R.color.vector_error_color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span("\nsessionId:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+" ${content?.sessionId}"
|
||||||
|
span("\nFrom Device (sender key):") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+" ${encryptedContent?.senderKey}"
|
||||||
|
} else if (event.getClearType() == EventType.ROOM_KEY) {
|
||||||
|
// it's a bit of a fake event for trail reasons
|
||||||
|
val content = event.getClearContent()
|
||||||
|
span("\nsessionId:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+" ${content?.get("session_id")}"
|
||||||
|
span("\nroomId:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+" ${content?.get("room_id")}"
|
||||||
|
span("\nTo :") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+" ${content?.get("_dest") ?: "me"}"
|
||||||
|
} else if (event.getClearType() == EventType.SEND_SECRET) {
|
||||||
|
val content = event.getClearContent().toModel<SecretSendEventContent>()
|
||||||
|
|
||||||
|
span("\nrequestId:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+" ${content?.requestId}"
|
||||||
|
span("\nFrom Device:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+" ${event.mxDecryptionResult?.payload?.get("sender_device")}"
|
||||||
|
} else if (event.getClearType() == EventType.REQUEST_SECRET) {
|
||||||
|
val content = event.getClearContent().toModel<SecretShareRequest>()
|
||||||
|
span("\nreqId:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+" ${content?.requestId}"
|
||||||
|
span("\naction:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+" ${content?.action}"
|
||||||
|
if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) {
|
||||||
|
span("\nsecretName:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+" ${content.secretName}"
|
||||||
|
}
|
||||||
|
span("\nrequestedBy: ") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+"${content?.requestingDeviceId}"
|
||||||
|
} else if (event.getClearType() == EventType.ENCRYPTED) {
|
||||||
|
span("**Failed to Decrypt** ${event.mCryptoError}") {
|
||||||
|
textColor = colorProvider.getColor(R.color.vector_error_color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,14 +24,12 @@ import im.vector.app.R
|
|||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.ColorProvider
|
|
||||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class IncomingKeyRequestListFragment @Inject constructor(
|
class IncomingKeyRequestListFragment @Inject constructor(
|
||||||
val viewModelFactory: KeyRequestListViewModel.Factory,
|
val viewModelFactory: KeyRequestListViewModel.Factory,
|
||||||
private val epoxyController: KeyRequestEpoxyController,
|
private val epoxyController: IncomingKeyRequestPagedController
|
||||||
private val colorProvider: ColorProvider
|
|
||||||
) : VectorBaseFragment() {
|
) : VectorBaseFragment() {
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
||||||
@ -39,8 +37,10 @@ class IncomingKeyRequestListFragment @Inject constructor(
|
|||||||
private val viewModel: KeyRequestListViewModel by fragmentViewModel(KeyRequestListViewModel::class)
|
private val viewModel: KeyRequestListViewModel by fragmentViewModel(KeyRequestListViewModel::class)
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
epoxyController.outgoing = false
|
state.incomingRequests.invoke()?.let {
|
||||||
epoxyController.setData(state)
|
epoxyController.submitList(it)
|
||||||
|
}
|
||||||
|
Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.features.settings.devtools
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
|
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
|
import im.vector.app.core.ui.list.GenericItem_
|
||||||
|
import im.vector.app.core.utils.createUIHandler
|
||||||
|
import me.gujun.android.span.span
|
||||||
|
import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class IncomingKeyRequestPagedController @Inject constructor(
|
||||||
|
private val vectorDateFormatter: VectorDateFormatter
|
||||||
|
) : PagedListEpoxyController<IncomingRoomKeyRequest>(
|
||||||
|
// Important it must match the PageList builder notify Looper
|
||||||
|
modelBuildingHandler = createUIHandler()
|
||||||
|
) {
|
||||||
|
|
||||||
|
interface InteractionListener {
|
||||||
|
// fun didTap(data: UserAccountData)
|
||||||
|
}
|
||||||
|
|
||||||
|
var interactionListener: InteractionListener? = null
|
||||||
|
|
||||||
|
override fun buildItemModel(currentPosition: Int, item: IncomingRoomKeyRequest?): EpoxyModel<*> {
|
||||||
|
val roomKeyRequest = item ?: return GenericItem_().apply { id(currentPosition) }
|
||||||
|
|
||||||
|
return GenericItem_().apply {
|
||||||
|
id(roomKeyRequest.requestId)
|
||||||
|
title(roomKeyRequest.requestId)
|
||||||
|
description(
|
||||||
|
span {
|
||||||
|
span("From user: ${roomKeyRequest.userId}")
|
||||||
|
+vectorDateFormatter.format(roomKeyRequest.localCreationTimestamp, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
||||||
|
span("sessionId:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
span("\nFrom device:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+"${roomKeyRequest.deviceId}"
|
||||||
|
+"\n${roomKeyRequest.state.name}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,164 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2020 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.features.settings.devtools
|
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
|
||||||
import com.airbnb.mvrx.Fail
|
|
||||||
import com.airbnb.mvrx.Loading
|
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import com.airbnb.mvrx.Uninitialized
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.core.epoxy.loadingItem
|
|
||||||
import im.vector.app.core.extensions.exhaustive
|
|
||||||
import im.vector.app.core.resources.StringProvider
|
|
||||||
import im.vector.app.core.ui.list.genericFooterItem
|
|
||||||
import im.vector.app.core.ui.list.genericItem
|
|
||||||
import im.vector.app.core.ui.list.genericItemHeader
|
|
||||||
import me.gujun.android.span.span
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class KeyRequestEpoxyController @Inject constructor(
|
|
||||||
private val stringProvider: StringProvider
|
|
||||||
) : TypedEpoxyController<KeyRequestListViewState>() {
|
|
||||||
|
|
||||||
interface InteractionListener {
|
|
||||||
// fun didTap(data: UserAccountData)
|
|
||||||
}
|
|
||||||
|
|
||||||
var outgoing = true
|
|
||||||
|
|
||||||
var interactionListener: InteractionListener? = null
|
|
||||||
|
|
||||||
override fun buildModels(data: KeyRequestListViewState?) {
|
|
||||||
if (outgoing) {
|
|
||||||
buildOutgoing(data)
|
|
||||||
} else {
|
|
||||||
buildIncoming(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildIncoming(data: KeyRequestListViewState?) {
|
|
||||||
data?.incomingRequests?.let { async ->
|
|
||||||
when (async) {
|
|
||||||
is Uninitialized,
|
|
||||||
is Loading -> {
|
|
||||||
loadingItem {
|
|
||||||
id("loadingOutgoing")
|
|
||||||
loadingText(stringProvider.getString(R.string.loading))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Fail -> {
|
|
||||||
genericItem {
|
|
||||||
id("failOutgoing")
|
|
||||||
title(async.error.localizedMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Success -> {
|
|
||||||
if (async.invoke().isEmpty()) {
|
|
||||||
genericFooterItem {
|
|
||||||
id("empty")
|
|
||||||
text(stringProvider.getString(R.string.no_result_placeholder))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val requestList = async.invoke().groupBy { it.userId }
|
|
||||||
|
|
||||||
requestList.forEach {
|
|
||||||
genericItemHeader {
|
|
||||||
id(it.key)
|
|
||||||
text("From user: ${it.key}")
|
|
||||||
}
|
|
||||||
it.value.forEach { roomKeyRequest ->
|
|
||||||
genericItem {
|
|
||||||
id(roomKeyRequest.requestId)
|
|
||||||
title(roomKeyRequest.requestId)
|
|
||||||
description(
|
|
||||||
span {
|
|
||||||
span("sessionId:") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
span("\nFrom device:") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+"${roomKeyRequest.deviceId}"
|
|
||||||
+"\n${roomKeyRequest.state.name}"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.exhaustive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildOutgoing(data: KeyRequestListViewState?) {
|
|
||||||
data?.outgoingRoomKeyRequests?.let { async ->
|
|
||||||
when (async) {
|
|
||||||
is Uninitialized,
|
|
||||||
is Loading -> {
|
|
||||||
loadingItem {
|
|
||||||
id("loadingOutgoing")
|
|
||||||
loadingText(stringProvider.getString(R.string.loading))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Fail -> {
|
|
||||||
genericItem {
|
|
||||||
id("failOutgoing")
|
|
||||||
title(async.error.localizedMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Success -> {
|
|
||||||
if (async.invoke().isEmpty()) {
|
|
||||||
genericFooterItem {
|
|
||||||
id("empty")
|
|
||||||
text(stringProvider.getString(R.string.no_result_placeholder))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val requestList = async.invoke().groupBy { it.roomId }
|
|
||||||
|
|
||||||
requestList.forEach {
|
|
||||||
genericItemHeader {
|
|
||||||
id(it.key)
|
|
||||||
text("roomId: ${it.key}")
|
|
||||||
}
|
|
||||||
it.value.forEach { roomKeyRequest ->
|
|
||||||
genericItem {
|
|
||||||
id(roomKeyRequest.requestId)
|
|
||||||
title(roomKeyRequest.requestId)
|
|
||||||
description(
|
|
||||||
span {
|
|
||||||
span("sessionId:\n") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+"${roomKeyRequest.sessionId}"
|
|
||||||
span("\nstate:") {
|
|
||||||
textStyle = "bold"
|
|
||||||
}
|
|
||||||
+"\n${roomKeyRequest.state.name}"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.exhaustive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,11 +17,11 @@
|
|||||||
package im.vector.app.features.settings.devtools
|
package im.vector.app.features.settings.devtools
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.paging.PagedList
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
import com.airbnb.mvrx.MvRxState
|
import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
@ -33,10 +33,11 @@ import kotlinx.coroutines.launch
|
|||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
|
import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
|
||||||
import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest
|
import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest
|
||||||
|
import org.matrix.android.sdk.rx.asObservable
|
||||||
|
|
||||||
data class KeyRequestListViewState(
|
data class KeyRequestListViewState(
|
||||||
val incomingRequests: Async<List<IncomingRoomKeyRequest>> = Uninitialized,
|
val incomingRequests: Async<PagedList<IncomingRoomKeyRequest>> = Uninitialized,
|
||||||
val outgoingRoomKeyRequests: Async<List<OutgoingRoomKeyRequest>> = Uninitialized
|
val outgoingRoomKeyRequests: Async<PagedList<OutgoingRoomKeyRequest>> = Uninitialized
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
||||||
class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState: KeyRequestListViewState,
|
class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState: KeyRequestListViewState,
|
||||||
@ -49,20 +50,16 @@ class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState
|
|||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
session.cryptoService().getOutgoingRoomKeyRequests().let {
|
session.cryptoService().getOutgoingRoomKeyRequestsPaged().asObservable()
|
||||||
setState {
|
.execute {
|
||||||
copy(
|
copy(outgoingRoomKeyRequests = it)
|
||||||
outgoingRoomKeyRequests = Success(it)
|
}
|
||||||
)
|
|
||||||
}
|
session.cryptoService().getIncomingRoomKeyRequestsPaged()
|
||||||
}
|
.asObservable()
|
||||||
session.cryptoService().getIncomingRoomKeyRequests().let {
|
.execute {
|
||||||
setState {
|
copy(incomingRequests = it)
|
||||||
copy(
|
}
|
||||||
incomingRequests = Success(it)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.features.settings.devtools
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import me.gujun.android.span.span
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
|
||||||
|
|
||||||
|
sealed class KeyRequestAction : VectorViewModelAction {
|
||||||
|
data class ExportAudit(val uri: Uri) : KeyRequestAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class KeyRequestEvents : VectorViewEvents {
|
||||||
|
data class SaveAudit(val uri: Uri, val raw: String) : KeyRequestEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class KeyRequestViewState(
|
||||||
|
val exporting: Async<String> = Uninitialized
|
||||||
|
) : MvRxState
|
||||||
|
|
||||||
|
class KeyRequestViewModel @AssistedInject constructor(
|
||||||
|
@Assisted initialState: KeyRequestViewState,
|
||||||
|
private val session: Session)
|
||||||
|
: VectorViewModel<KeyRequestViewState, KeyRequestAction, KeyRequestEvents>(initialState) {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: KeyRequestViewState): KeyRequestViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<KeyRequestViewModel, KeyRequestViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: KeyRequestViewState): KeyRequestViewModel? {
|
||||||
|
val fragment: KeyRequestsFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
|
return fragment.viewModelFactory.create(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: KeyRequestAction) {
|
||||||
|
when (action) {
|
||||||
|
is KeyRequestAction.ExportAudit -> {
|
||||||
|
setState {
|
||||||
|
copy(exporting = Loading())
|
||||||
|
}
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
// this can take long
|
||||||
|
val eventList = session.cryptoService().getGossipingEvents()
|
||||||
|
// clean it a bit to
|
||||||
|
val stringBuilder = StringBuilder()
|
||||||
|
eventList.forEach {
|
||||||
|
val clearType = it.getClearType()
|
||||||
|
stringBuilder.append("[${it.ageLocalTs}] : $clearType from:${it.senderId} - ")
|
||||||
|
when (clearType) {
|
||||||
|
EventType.ROOM_KEY_REQUEST -> {
|
||||||
|
val content = it.getClearContent().toModel<RoomKeyShareRequest>()
|
||||||
|
stringBuilder.append("reqId:${content?.requestId} action:${content?.action} ")
|
||||||
|
if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) {
|
||||||
|
stringBuilder.append("sessionId: ${content.body?.sessionId} ")
|
||||||
|
}
|
||||||
|
stringBuilder.append("requestedBy: ${content?.requestingDeviceId} ")
|
||||||
|
stringBuilder.append("\n")
|
||||||
|
}
|
||||||
|
EventType.FORWARDED_ROOM_KEY -> {
|
||||||
|
val encryptedContent = it.content.toModel<OlmEventContent>()
|
||||||
|
val content = it.getClearContent().toModel<ForwardedRoomKeyContent>()
|
||||||
|
|
||||||
|
stringBuilder.append("sessionId:${content?.sessionId} From Device (sender key):${encryptedContent?.senderKey} ")
|
||||||
|
span("\nFrom Device (sender key):") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
stringBuilder.append("\n")
|
||||||
|
}
|
||||||
|
EventType.ROOM_KEY -> {
|
||||||
|
val content = it.getClearContent()
|
||||||
|
stringBuilder.append("sessionId:${content?.get("session_id")} roomId:${content?.get("room_id")} dest:${content?.get("_dest") ?: "me"}")
|
||||||
|
stringBuilder.append("\n")
|
||||||
|
}
|
||||||
|
EventType.SEND_SECRET -> {
|
||||||
|
val content = it.getClearContent().toModel<SecretSendEventContent>()
|
||||||
|
stringBuilder.append("requestId:${content?.requestId} From Device:${it.mxDecryptionResult?.payload?.get("sender_device")}")
|
||||||
|
}
|
||||||
|
EventType.REQUEST_SECRET -> {
|
||||||
|
val content = it.getClearContent().toModel<SecretShareRequest>()
|
||||||
|
stringBuilder.append("reqId:${content?.requestId} action:${content?.action} ")
|
||||||
|
if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) {
|
||||||
|
stringBuilder.append("secretName:${content.secretName} ")
|
||||||
|
}
|
||||||
|
stringBuilder.append("requestedBy:${content?.requestingDeviceId}")
|
||||||
|
stringBuilder.append("\n")
|
||||||
|
}
|
||||||
|
EventType.ENCRYPTED -> {
|
||||||
|
stringBuilder.append("Failed to Derypt \n")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
stringBuilder.append("?? \n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val raw = stringBuilder.toString()
|
||||||
|
setState {
|
||||||
|
copy(exporting = Success(""))
|
||||||
|
}
|
||||||
|
_viewEvents.post(KeyRequestEvents.SaveAudit(action.uri, raw))
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(exporting = Fail(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,20 +16,30 @@
|
|||||||
|
|
||||||
package im.vector.app.features.settings.devtools
|
package im.vector.app.features.settings.devtools
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE
|
import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.utils.selectTxtFileToWrite
|
||||||
import kotlinx.android.synthetic.main.fragment_devtool_keyrequests.*
|
import kotlinx.android.synthetic.main.fragment_devtool_keyrequests.*
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() {
|
class KeyRequestsFragment @Inject constructor(
|
||||||
|
val viewModelFactory: KeyRequestViewModel.Factory) : VectorBaseFragment() {
|
||||||
|
|
||||||
override fun getLayoutResId(): Int = R.layout.fragment_devtool_keyrequests
|
override fun getLayoutResId(): Int = R.layout.fragment_devtool_keyrequests
|
||||||
|
|
||||||
@ -40,6 +50,10 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() {
|
|||||||
|
|
||||||
private var mPagerAdapter: KeyReqPagerAdapter? = null
|
private var mPagerAdapter: KeyReqPagerAdapter? = null
|
||||||
|
|
||||||
|
private val viewModel: KeyRequestViewModel by fragmentViewModel()
|
||||||
|
|
||||||
|
override fun getMenuRes(): Int = R.menu.menu_audit
|
||||||
|
|
||||||
private val pageAdapterListener = object : ViewPager2.OnPageChangeCallback() {
|
private val pageAdapterListener = object : ViewPager2.OnPageChangeCallback() {
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
@ -53,6 +67,13 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) {
|
||||||
|
when (it.exporting) {
|
||||||
|
is Loading -> exportWaitingView.isVisible = true
|
||||||
|
else -> exportWaitingView.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
@ -77,6 +98,23 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.attach()
|
}.attach()
|
||||||
|
|
||||||
|
viewModel.observeViewEvents {
|
||||||
|
when (it) {
|
||||||
|
is KeyRequestEvents.SaveAudit -> {
|
||||||
|
tryOrNull {
|
||||||
|
val os = requireContext().contentResolver?.openOutputStream(it.uri)
|
||||||
|
if (os == null) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
os.write(it.raw.toByteArray())
|
||||||
|
os.flush()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
@ -85,6 +123,28 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() {
|
|||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == R.id.audit_export) {
|
||||||
|
selectTxtFileToWrite(
|
||||||
|
activity = requireActivity(),
|
||||||
|
activityResultLauncher = epxortAuditForActivityResult,
|
||||||
|
defaultFileName = "audit-export-json_${System.currentTimeMillis()}.txt",
|
||||||
|
chooserHint = "Export Audit"
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val epxortAuditForActivityResult = registerStartForActivityResult { activityResult ->
|
||||||
|
if (activityResult.resultCode == Activity.RESULT_OK) {
|
||||||
|
val uri = activityResult.data?.data
|
||||||
|
if (uri != null) {
|
||||||
|
viewModel.handle(KeyRequestAction.ExportAudit(uri))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private inner class KeyReqPagerAdapter(fa: Fragment) : FragmentStateAdapter(fa) {
|
private inner class KeyReqPagerAdapter(fa: Fragment) : FragmentStateAdapter(fa) {
|
||||||
override fun getItemCount(): Int = 3
|
override fun getItemCount(): Int = 3
|
||||||
|
|
||||||
|
@ -24,21 +24,19 @@ import im.vector.app.R
|
|||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.ColorProvider
|
|
||||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class OutgoingKeyRequestListFragment @Inject constructor(
|
class OutgoingKeyRequestListFragment @Inject constructor(
|
||||||
val viewModelFactory: KeyRequestListViewModel.Factory,
|
val viewModelFactory: KeyRequestListViewModel.Factory,
|
||||||
private val epoxyController: KeyRequestEpoxyController,
|
private val epoxyController: OutgoingKeyRequestPagedController
|
||||||
private val colorProvider: ColorProvider
|
|
||||||
) : VectorBaseFragment() {
|
) : VectorBaseFragment() {
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
||||||
private val viewModel: KeyRequestListViewModel by fragmentViewModel(KeyRequestListViewModel::class)
|
private val viewModel: KeyRequestListViewModel by fragmentViewModel(KeyRequestListViewModel::class)
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
epoxyController.setData(state)
|
epoxyController.submitList(state.outgoingRoomKeyRequests.invoke())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.features.settings.devtools
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
|
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||||
|
import im.vector.app.core.ui.list.GenericItem_
|
||||||
|
import im.vector.app.core.utils.createUIHandler
|
||||||
|
import me.gujun.android.span.span
|
||||||
|
import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class OutgoingKeyRequestPagedController @Inject constructor() : PagedListEpoxyController<OutgoingRoomKeyRequest>(
|
||||||
|
// Important it must match the PageList builder notify Looper
|
||||||
|
modelBuildingHandler = createUIHandler()
|
||||||
|
) {
|
||||||
|
|
||||||
|
interface InteractionListener {
|
||||||
|
// fun didTap(data: UserAccountData)
|
||||||
|
}
|
||||||
|
|
||||||
|
var interactionListener: InteractionListener? = null
|
||||||
|
|
||||||
|
override fun buildItemModel(currentPosition: Int, item: OutgoingRoomKeyRequest?): EpoxyModel<*> {
|
||||||
|
val roomKeyRequest = item ?: return GenericItem_().apply { id(currentPosition) }
|
||||||
|
|
||||||
|
return GenericItem_().apply {
|
||||||
|
id(roomKeyRequest.requestId)
|
||||||
|
title(roomKeyRequest.requestId)
|
||||||
|
description(
|
||||||
|
span {
|
||||||
|
span("roomId:\n") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+"${roomKeyRequest.roomId}"
|
||||||
|
|
||||||
|
span("sessionId:\n") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+"${roomKeyRequest.sessionId}"
|
||||||
|
span("\nstate:") {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+"\n${roomKeyRequest.state.name}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -11,12 +11,24 @@
|
|||||||
android:id="@+id/devToolKeyRequestTabs"
|
android:id="@+id/devToolKeyRequestTabs"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:tabMode="scrollable" />
|
app:tabMode="scrollable" />
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/devToolKeyRequestTabs"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:id="@+id/devToolKeyRequestPager"
|
android:id="@+id/devToolKeyRequestPager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp" />
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
<ProgressBar
|
||||||
|
android:id="@+id/exportWaitingView"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
10
vector/src/main/res/menu/menu_audit.xml
Normal file
10
vector/src/main/res/menu/menu_audit.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/audit_export"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_material_save"
|
||||||
|
android:title="@string/settings_export_trail" />
|
||||||
|
|
||||||
|
</menu>
|
@ -2304,6 +2304,7 @@
|
|||||||
<string name="login_default_session_public_name">Element Android</string>
|
<string name="login_default_session_public_name">Element Android</string>
|
||||||
|
|
||||||
<string name="settings_key_requests">Key Requests</string>
|
<string name="settings_key_requests">Key Requests</string>
|
||||||
|
<string name="settings_export_trail">Export Audit</string>
|
||||||
|
|
||||||
<string name="e2e_use_keybackup">Unlock encrypted messages history</string>
|
<string name="e2e_use_keybackup">Unlock encrypted messages history</string>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user