Merge remote-tracking branch 'upstream/develop' into direct_share

This commit is contained in:
Constantin Wartenburger 2020-10-13 20:26:07 +02:00
commit afe55ae57e
No known key found for this signature in database
GPG Key ID: 7439D96D8E1DB894
376 changed files with 2926 additions and 1433 deletions

View File

@ -4,16 +4,7 @@
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS"> <option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value> <value>
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" /> <package name="kotlinx.android.synthetic" withSubpackages="true" static="false" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value> </value>
</option> </option>
<option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" /> <option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" />

View File

@ -18,8 +18,10 @@ Improvements 🙌:
- Filter room member (and banned users) by name (#2184) - Filter room member (and banned users) by name (#2184)
- Implement "Jump to read receipt" and "Mention" actions on the room member profile screen - Implement "Jump to read receipt" and "Mention" actions on the room member profile screen
- Direct share (#2029) - Direct share (#2029)
- Add FAB to room members list (#2226)
- Add Sygnal API implementation to test is Push are correctly received - Add Sygnal API implementation to test is Push are correctly received
- Add PushGateway API implementation to test if Push are correctly received - Add PushGateway API implementation to test if Push are correctly received
- Cross signing: shouldn't offer to verify with other session when there is not. (#2227)
Bugfix 🐛: Bugfix 🐛:
- Improve support for image/audio/video/file selection with intent changes (#1376) - Improve support for image/audio/video/file selection with intent changes (#1376)
@ -27,9 +29,10 @@ Bugfix 🐛:
- Invalid popup when pressing back (#1635) - Invalid popup when pressing back (#1635)
- Simplifies draft management and should fix bunch of draft issues (#952, #683) - Simplifies draft management and should fix bunch of draft issues (#952, #683)
- Very long topic cannot be fully visible (#1957) - Very long topic cannot be fully visible (#1957)
- Properly detect cross signing keys reset
Translations 🗣: Translations 🗣:
- - Move store data to `/fastlane/metadata/android` (#812)
SDK API changes ⚠️: SDK API changes ⚠️:
- Search messages in a room by using Session.searchService() or Room.search() - Search messages in a room by using Session.searchService() or Room.search()

View File

@ -0,0 +1 @@
Element (o novo Riot.im)

View File

@ -13,7 +13,7 @@ Element здатен забезпечити усе це завдяки тому,
Element надає вам повний контроль, дозволяючи обирати з-поміж надавачів послуг, що обслуговують сервери з вашими бесідами. Ви вільні обрати будь-який спосіб розміщення прямо з застосунку Element: Element надає вам повний контроль, дозволяючи обирати з-поміж надавачів послуг, що обслуговують сервери з вашими бесідами. Ви вільні обрати будь-який спосіб розміщення прямо з застосунку Element:
1. Отримати безкоштовний обліковий запис на загальнодоступному сервері matrix.org 1. Отримати безкоштовний обліковий запис на загальнодоступному сервері matrix.org, який обслуговують розробники Matrix, чи на одному з тисяч публічних серверів, які обслуговують волонтери
2. Розмістити свій обліковий запис на власному сервері 2. Розмістити свій обліковий запис на власному сервері
3. Зареєструватись на індивідуальному сервері, просто підписавшись на послуги платформи Element Matrix Services 3. Зареєструватись на індивідуальному сервері, просто підписавшись на послуги платформи Element Matrix Services

View File

@ -17,6 +17,8 @@
package org.matrix.android.sdk.api package org.matrix.android.sdk.api
import android.content.Context import android.content.Context
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.Configuration import androidx.work.Configuration
import androidx.work.WorkManager import androidx.work.WorkManager
@ -48,13 +50,17 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
@Inject internal lateinit var olmManager: OlmManager @Inject internal lateinit var olmManager: OlmManager
@Inject internal lateinit var sessionManager: SessionManager @Inject internal lateinit var sessionManager: SessionManager
private val uiHandler = Handler(Looper.getMainLooper())
init { init {
Monarchy.init(context) Monarchy.init(context)
DaggerTestMatrixComponent.factory().create(context, matrixConfiguration).inject(this) DaggerTestMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
if (context.applicationContext !is Configuration.Provider) { if (context.applicationContext !is Configuration.Provider) {
WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build()) WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build())
} }
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver) uiHandler.post {
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
}
} }
fun getUserAgent() = userAgentHolder.userAgent fun getUserAgent() = userAgentHolder.userAgent

View File

@ -16,7 +16,6 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
@ -34,14 +33,8 @@ internal class CryptoStoreHelper {
.modules(RealmCryptoStoreModule()) .modules(RealmCryptoStoreModule())
.build(), .build(),
crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi()), crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi()),
credentials = createCredential()) userId = "userId_" + Random.nextInt(),
deviceId = "deviceId_sample"
)
} }
fun createCredential() = Credentials(
userId = "userId_" + Random.nextInt(),
homeServer = "http://matrix.org",
accessToken = "access_token",
refreshToken = null,
deviceId = "deviceId_sample"
)
} }

View File

@ -0,0 +1,111 @@
/*
* Copyright 2020 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.crypto.encryption
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.amshove.kluent.shouldBe
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class EncryptionTest : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
@Test
fun test_EncryptionEvent() {
performTest(roomShouldBeEncrypted = false) { room ->
// Send an encryption Event as an Event (and not as a state event)
room.sendEvent(
eventType = EventType.STATE_ROOM_ENCRYPTION,
content = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
)
}
}
@Test
fun test_EncryptionStateEvent() {
performTest(roomShouldBeEncrypted = true) { room ->
// Send an encryption Event as a State Event
room.sendStateEvent(
eventType = EventType.STATE_ROOM_ENCRYPTION,
stateKey = null,
body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent(),
callback = NoOpMatrixCallback()
)
}
}
private fun performTest(roomShouldBeEncrypted: Boolean, action: (Room) -> Unit) {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false)
val aliceSession = cryptoTestData.firstSession
val room = aliceSession.getRoom(cryptoTestData.roomId)!!
room.isEncrypted() shouldBe false
val timeline = room.createTimeline(null, TimelineSettings(10))
val latch = CountDownLatch(1)
val timelineListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) {
}
override fun onNewTimelineEvents(eventIds: List<String>) {
// noop
}
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
val newMessages = snapshot
.filter { it.root.sendState == SendState.SYNCED }
.filter { it.root.getClearType() == EventType.STATE_ROOM_ENCRYPTION }
if (newMessages.isNotEmpty()) {
timeline.removeListener(this)
latch.countDown()
}
}
}
timeline.start()
timeline.addListener(timelineListener)
action.invoke(room)
mTestHelper.await(latch)
timeline.dispose()
room.isEncrypted() shouldBe roomShouldBeEncrypted
cryptoTestData.cleanUp(mTestHelper)
}
}

View File

@ -17,15 +17,14 @@
package org.matrix.android.sdk.internal.crypto.verification.qrcode package org.matrix.android.sdk.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import org.matrix.android.sdk.InstrumentedTest import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeNull import org.amshove.kluent.shouldBeNull
import org.amshove.kluent.shouldEqual
import org.amshove.kluent.shouldEqualTo
import org.amshove.kluent.shouldNotBeNull import org.amshove.kluent.shouldNotBeNull
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
@ -66,32 +65,32 @@ class QrCodeTest : InstrumentedTest {
@Test @Test
fun testEncoding1() { fun testEncoding1() {
qrCode1.toEncodedString() shouldEqual value1 qrCode1.toEncodedString() shouldBeEqualTo value1
} }
@Test @Test
fun testEncoding2() { fun testEncoding2() {
qrCode2.toEncodedString() shouldEqual value2 qrCode2.toEncodedString() shouldBeEqualTo value2
} }
@Test @Test
fun testEncoding3() { fun testEncoding3() {
qrCode3.toEncodedString() shouldEqual value3 qrCode3.toEncodedString() shouldBeEqualTo value3
} }
@Test @Test
fun testSymmetry1() { fun testSymmetry1() {
qrCode1.toEncodedString().toQrCodeData() shouldEqual qrCode1 qrCode1.toEncodedString().toQrCodeData() shouldBeEqualTo qrCode1
} }
@Test @Test
fun testSymmetry2() { fun testSymmetry2() {
qrCode2.toEncodedString().toQrCodeData() shouldEqual qrCode2 qrCode2.toEncodedString().toQrCodeData() shouldBeEqualTo qrCode2
} }
@Test @Test
fun testSymmetry3() { fun testSymmetry3() {
qrCode3.toEncodedString().toQrCodeData() shouldEqual qrCode3 qrCode3.toEncodedString().toQrCodeData() shouldBeEqualTo qrCode3
} }
@Test @Test
@ -102,7 +101,7 @@ class QrCodeTest : InstrumentedTest {
checkHeader(byteArray) checkHeader(byteArray)
// Mode // Mode
byteArray[7] shouldEqualTo 0 byteArray[7] shouldBeEqualTo 0
checkSizeAndTransaction(byteArray) checkSizeAndTransaction(byteArray)
@ -120,7 +119,7 @@ class QrCodeTest : InstrumentedTest {
checkHeader(byteArray) checkHeader(byteArray)
// Mode // Mode
byteArray[7] shouldEqualTo 1 byteArray[7] shouldBeEqualTo 1
checkSizeAndTransaction(byteArray) checkSizeAndTransaction(byteArray)
compareArray(byteArray.copyOfRange(23, 23 + 32), kte_byteArray) compareArray(byteArray.copyOfRange(23, 23 + 32), kte_byteArray)
@ -137,7 +136,7 @@ class QrCodeTest : InstrumentedTest {
checkHeader(byteArray) checkHeader(byteArray)
// Mode // Mode
byteArray[7] shouldEqualTo 2 byteArray[7] shouldBeEqualTo 2
checkSizeAndTransaction(byteArray) checkSizeAndTransaction(byteArray)
compareArray(byteArray.copyOfRange(23, 23 + 32), tlx_byteArray) compareArray(byteArray.copyOfRange(23, 23 + 32), tlx_byteArray)
@ -156,10 +155,10 @@ class QrCodeTest : InstrumentedTest {
val result = qrCode.toEncodedString() val result = qrCode.toEncodedString()
val expected = value1.replace("\u0000\u000DMaTransaction", "\u0007\u00D0$longTransactionId") val expected = value1.replace("\u0000\u000DMaTransaction", "\u0007\u00D0$longTransactionId")
result shouldEqual expected result shouldBeEqualTo expected
// Reverse operation // Reverse operation
expected.toQrCodeData() shouldEqual qrCode expected.toQrCodeData() shouldBeEqualTo qrCode
} }
@Test @Test
@ -170,7 +169,7 @@ class QrCodeTest : InstrumentedTest {
val qrCode = qrCode1.copy(transactionId = longTransactionId) val qrCode = qrCode1.copy(transactionId = longTransactionId)
// Symmetric operation // Symmetric operation
qrCode.toEncodedString().toQrCodeData() shouldEqual qrCode qrCode.toEncodedString().toQrCodeData() shouldBeEqualTo qrCode
} }
} }
@ -218,32 +217,32 @@ class QrCodeTest : InstrumentedTest {
} }
private fun compareArray(actual: ByteArray, expected: ByteArray) { private fun compareArray(actual: ByteArray, expected: ByteArray) {
actual.size shouldEqual expected.size actual.size shouldBeEqualTo expected.size
for (i in actual.indices) { for (i in actual.indices) {
actual[i] shouldEqualTo expected[i] actual[i] shouldBeEqualTo expected[i]
} }
} }
private fun checkHeader(byteArray: ByteArray) { private fun checkHeader(byteArray: ByteArray) {
// MATRIX // MATRIX
byteArray[0] shouldEqualTo 'M'.toByte() byteArray[0] shouldBeEqualTo 'M'.toByte()
byteArray[1] shouldEqualTo 'A'.toByte() byteArray[1] shouldBeEqualTo 'A'.toByte()
byteArray[2] shouldEqualTo 'T'.toByte() byteArray[2] shouldBeEqualTo 'T'.toByte()
byteArray[3] shouldEqualTo 'R'.toByte() byteArray[3] shouldBeEqualTo 'R'.toByte()
byteArray[4] shouldEqualTo 'I'.toByte() byteArray[4] shouldBeEqualTo 'I'.toByte()
byteArray[5] shouldEqualTo 'X'.toByte() byteArray[5] shouldBeEqualTo 'X'.toByte()
// Version // Version
byteArray[6] shouldEqualTo 2 byteArray[6] shouldBeEqualTo 2
} }
private fun checkSizeAndTransaction(byteArray: ByteArray) { private fun checkSizeAndTransaction(byteArray: ByteArray) {
// Size // Size
byteArray[8] shouldEqualTo 0 byteArray[8] shouldBeEqualTo 0
byteArray[9] shouldEqualTo 13 byteArray[9] shouldBeEqualTo 13
// Transaction // Transaction
byteArray.copyOfRange(10, 10 + "MaTransaction".length).toString(Charsets.ISO_8859_1) shouldEqual "MaTransaction" byteArray.copyOfRange(10, 10 + "MaTransaction".length).toString(Charsets.ISO_8859_1) shouldBeEqualTo "MaTransaction"
} }
} }

View File

@ -18,6 +18,14 @@ package org.matrix.android.sdk.session.room.timeline
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.kotlin.createObject
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
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.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
@ -29,14 +37,6 @@ import org.matrix.android.sdk.internal.database.model.SessionRealmModule
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeListOfEvents import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeListOfEvents
import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeMessageEvent import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeMessageEvent
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.kotlin.createObject
import org.amshove.kluent.shouldBeTrue
import org.amshove.kluent.shouldEqual
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
internal class ChunkEntityTest : InstrumentedTest { internal class ChunkEntityTest : InstrumentedTest {
@ -60,10 +60,10 @@ internal class ChunkEntityTest : InstrumentedTest {
val chunk: ChunkEntity = realm.createObject() val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let { val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
realm.copyToRealmOrUpdate(it) realm.copyToRealm(it)
} }
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap()) chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.timelineEvents.size shouldEqual 1 chunk.timelineEvents.size shouldBeEqualTo 1
} }
} }
@ -72,11 +72,11 @@ internal class ChunkEntityTest : InstrumentedTest {
monarchy.runTransactionSync { realm -> monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject() val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let { val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
realm.copyToRealmOrUpdate(it) realm.copyToRealm(it)
} }
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap()) chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap()) chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.timelineEvents.size shouldEqual 1 chunk.timelineEvents.size shouldBeEqualTo 1
} }
} }
@ -88,7 +88,7 @@ internal class ChunkEntityTest : InstrumentedTest {
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS) chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS) chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS) chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
chunk1.timelineEvents.size shouldEqual 60 chunk1.timelineEvents.size shouldBeEqualTo 60
} }
} }
@ -104,7 +104,7 @@ internal class ChunkEntityTest : InstrumentedTest {
chunk1.addAll(ROOM_ID, eventsForChunk1, PaginationDirection.FORWARDS) chunk1.addAll(ROOM_ID, eventsForChunk1, PaginationDirection.FORWARDS)
chunk2.addAll(ROOM_ID, eventsForChunk2, PaginationDirection.BACKWARDS) chunk2.addAll(ROOM_ID, eventsForChunk2, PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS) chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
chunk1.timelineEvents.size shouldEqual 40 chunk1.timelineEvents.size shouldBeEqualTo 40
chunk1.isLastForward.shouldBeTrue() chunk1.isLastForward.shouldBeTrue()
} }
} }
@ -119,7 +119,7 @@ internal class ChunkEntityTest : InstrumentedTest {
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS) chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS) chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.FORWARDS) chunk1.merge(ROOM_ID, chunk2, PaginationDirection.FORWARDS)
chunk1.prevToken shouldEqual prevToken chunk1.prevToken shouldBeEqualTo prevToken
} }
} }
@ -133,7 +133,7 @@ internal class ChunkEntityTest : InstrumentedTest {
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS) chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS) chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS) chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
chunk1.nextToken shouldEqual nextToken chunk1.nextToken shouldBeEqualTo nextToken
} }
} }
@ -142,7 +142,7 @@ internal class ChunkEntityTest : InstrumentedTest {
direction: PaginationDirection) { direction: PaginationDirection) {
events.forEach { event -> events.forEach { event ->
val fakeEvent = event.toEntity(roomId, SendState.SYNCED, System.currentTimeMillis()).let { val fakeEvent = event.toEntity(roomId, SendState.SYNCED, System.currentTimeMillis()).let {
realm.copyToRealmOrUpdate(it) realm.copyToRealm(it)
} }
addTimelineEvent(roomId, fakeEvent, direction, emptyMap()) addTimelineEvent(roomId, fakeEvent, direction, emptyMap())
} }

View File

@ -21,7 +21,7 @@ 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.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.MessageType
import kotlin.random.Random import kotlin.random.Random
@ -41,11 +41,11 @@ object RoomDataHelper {
} }
} }
fun createFakeEvent(type: String, private fun createFakeEvent(type: String,
content: Content? = null, content: Content? = null,
prevContent: Content? = null, prevContent: Content? = null,
sender: String = FAKE_TEST_SENDER, sender: String = FAKE_TEST_SENDER,
stateKey: String = FAKE_TEST_SENDER stateKey: String = FAKE_TEST_SENDER
): Event { ): Event {
return Event( return Event(
type = type, type = type,
@ -62,8 +62,8 @@ object RoomDataHelper {
return createFakeEvent(EventType.MESSAGE, message) return createFakeEvent(EventType.MESSAGE, message)
} }
fun createFakeRoomMemberEvent(): Event { private fun createFakeRoomMemberEvent(): Event {
val roomMember = RoomMemberSummary(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent() val roomMember = RoomMemberContent(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember) return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
} }
} }

View File

@ -78,7 +78,7 @@ internal class TimelineTest : InstrumentedTest {
// } // }
// } // }
// latch.await() // latch.await()
// timelineEvents.size shouldEqual initialLoad + paginationCount // timelineEvents.size shouldBeEqualTo initialLoad + paginationCount
// timeline.dispose() // timeline.dispose()
// } // }
} }

View File

@ -2,8 +2,10 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="org.matrix.android.sdk"> package="org.matrix.android.sdk">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<!-- TODO Is WRITE_EXTERNAL_STORAGE necessary? -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:networkSecurityConfig="@xml/network_security_config"> <application android:networkSecurityConfig="@xml/network_security_config">

View File

@ -42,4 +42,4 @@ interface MatrixCallback<in T> {
/** /**
* Basic no op implementation * Basic no op implementation
*/ */
class NoOpMatrixCallback<T>: MatrixCallback<T> class NoOpMatrixCallback<T> : MatrixCallback<T>

View File

@ -48,18 +48,25 @@ data class MatrixError(
companion object { companion object {
/** Forbidden access, e.g. joining a room without permission, failed login. */ /** Forbidden access, e.g. joining a room without permission, failed login. */
const val M_FORBIDDEN = "M_FORBIDDEN" const val M_FORBIDDEN = "M_FORBIDDEN"
/** An unknown error has occurred. */ /** An unknown error has occurred. */
const val M_UNKNOWN = "M_UNKNOWN" const val M_UNKNOWN = "M_UNKNOWN"
/** The access token specified was not recognised. */ /** The access token specified was not recognised. */
const val M_UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN" const val M_UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
/** No access token was specified for the request. */ /** No access token was specified for the request. */
const val M_MISSING_TOKEN = "M_MISSING_TOKEN" const val M_MISSING_TOKEN = "M_MISSING_TOKEN"
/** Request contained valid JSON, but it was malformed in some way, e.g. missing required keys, invalid values for keys. */ /** Request contained valid JSON, but it was malformed in some way, e.g. missing required keys, invalid values for keys. */
const val M_BAD_JSON = "M_BAD_JSON" const val M_BAD_JSON = "M_BAD_JSON"
/** Request did not contain valid JSON. */ /** Request did not contain valid JSON. */
const val M_NOT_JSON = "M_NOT_JSON" const val M_NOT_JSON = "M_NOT_JSON"
/** No resource was found for this request. */ /** No resource was found for this request. */
const val M_NOT_FOUND = "M_NOT_FOUND" const val M_NOT_FOUND = "M_NOT_FOUND"
/** Too many requests have been sent in a short period of time. Wait a while then try again. */ /** Too many requests have been sent in a short period of time. Wait a while then try again. */
const val M_LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED" const val M_LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
@ -69,68 +76,97 @@ data class MatrixError(
/** Encountered when trying to register a user ID which has been taken. */ /** Encountered when trying to register a user ID which has been taken. */
const val M_USER_IN_USE = "M_USER_IN_USE" const val M_USER_IN_USE = "M_USER_IN_USE"
/** Sent when the room alias given to the createRoom API is already in use. */ /** Sent when the room alias given to the createRoom API is already in use. */
const val M_ROOM_IN_USE = "M_ROOM_IN_USE" const val M_ROOM_IN_USE = "M_ROOM_IN_USE"
/** (Not documented yet) */ /** (Not documented yet) */
const val M_BAD_PAGINATION = "M_BAD_PAGINATION" const val M_BAD_PAGINATION = "M_BAD_PAGINATION"
/** The request was not correctly authorized. Usually due to login failures. */ /** The request was not correctly authorized. Usually due to login failures. */
const val M_UNAUTHORIZED = "M_UNAUTHORIZED" const val M_UNAUTHORIZED = "M_UNAUTHORIZED"
/** (Not documented yet) */ /** (Not documented yet) */
const val M_OLD_VERSION = "M_OLD_VERSION" const val M_OLD_VERSION = "M_OLD_VERSION"
/** The server did not understand the request. */ /** The server did not understand the request. */
const val M_UNRECOGNIZED = "M_UNRECOGNIZED" const val M_UNRECOGNIZED = "M_UNRECOGNIZED"
/** (Not documented yet) */ /** (Not documented yet) */
const val M_LOGIN_EMAIL_URL_NOT_YET = "M_LOGIN_EMAIL_URL_NOT_YET" const val M_LOGIN_EMAIL_URL_NOT_YET = "M_LOGIN_EMAIL_URL_NOT_YET"
/** Authentication could not be performed on the third party identifier. */ /** Authentication could not be performed on the third party identifier. */
const val M_THREEPID_AUTH_FAILED = "M_THREEPID_AUTH_FAILED" const val M_THREEPID_AUTH_FAILED = "M_THREEPID_AUTH_FAILED"
/** Sent when a threepid given to an API cannot be used because no record matching the threepid was found. */ /** Sent when a threepid given to an API cannot be used because no record matching the threepid was found. */
const val M_THREEPID_NOT_FOUND = "M_THREEPID_NOT_FOUND" const val M_THREEPID_NOT_FOUND = "M_THREEPID_NOT_FOUND"
/** Sent when a threepid given to an API cannot be used because the same threepid is already in use. */ /** Sent when a threepid given to an API cannot be used because the same threepid is already in use. */
const val M_THREEPID_IN_USE = "M_THREEPID_IN_USE" const val M_THREEPID_IN_USE = "M_THREEPID_IN_USE"
/** The client's request used a third party server, eg. identity server, that this server does not trust. */ /** The client's request used a third party server, eg. identity server, that this server does not trust. */
const val M_SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED" const val M_SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
/** The request or entity was too large. */ /** The request or entity was too large. */
const val M_TOO_LARGE = "M_TOO_LARGE" const val M_TOO_LARGE = "M_TOO_LARGE"
/** (Not documented yet) */ /** (Not documented yet) */
const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN" const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
/** The request cannot be completed because the homeserver has reached a resource limit imposed on it. For example, /** The request cannot be completed because the homeserver has reached a resource limit imposed on it. For example,
* a homeserver held in a shared hosting environment may reach a resource limit if it starts using too much memory * a homeserver held in a shared hosting environment may reach a resource limit if it starts using too much memory
* or disk space. The error MUST have an admin_contact field to provide the user receiving the error a place to reach * or disk space. The error MUST have an admin_contact field to provide the user receiving the error a place to reach
* out to. Typically, this error will appear on routes which attempt to modify state (eg: sending messages, account * out to. Typically, this error will appear on routes which attempt to modify state (eg: sending messages, account
* data, etc) and not routes which only read state (eg: /sync, get account data, etc). */ * data, etc) and not routes which only read state (eg: /sync, get account data, etc). */
const val M_RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED" const val M_RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
/** The user ID associated with the request has been deactivated. Typically for endpoints that prove authentication, such as /login. */ /** The user ID associated with the request has been deactivated. Typically for endpoints that prove authentication, such as /login. */
const val M_USER_DEACTIVATED = "M_USER_DEACTIVATED" const val M_USER_DEACTIVATED = "M_USER_DEACTIVATED"
/** Encountered when trying to register a user ID which is not valid. */ /** Encountered when trying to register a user ID which is not valid. */
const val M_INVALID_USERNAME = "M_INVALID_USERNAME" const val M_INVALID_USERNAME = "M_INVALID_USERNAME"
/** Sent when the initial state given to the createRoom API is invalid. */ /** Sent when the initial state given to the createRoom API is invalid. */
const val M_INVALID_ROOM_STATE = "M_INVALID_ROOM_STATE" const val M_INVALID_ROOM_STATE = "M_INVALID_ROOM_STATE"
/** The server does not permit this third party identifier. This may happen if the server only permits, /** The server does not permit this third party identifier. This may happen if the server only permits,
* for example, email addresses from a particular domain. */ * for example, email addresses from a particular domain. */
const val M_THREEPID_DENIED = "M_THREEPID_DENIED" const val M_THREEPID_DENIED = "M_THREEPID_DENIED"
/** The client's request to create a room used a room version that the server does not support. */ /** The client's request to create a room used a room version that the server does not support. */
const val M_UNSUPPORTED_ROOM_VERSION = "M_UNSUPPORTED_ROOM_VERSION" const val M_UNSUPPORTED_ROOM_VERSION = "M_UNSUPPORTED_ROOM_VERSION"
/** The client attempted to join a room that has a version the server does not support. /** The client attempted to join a room that has a version the server does not support.
* Inspect the room_version property of the error response for the room's version. */ * Inspect the room_version property of the error response for the room's version. */
const val M_INCOMPATIBLE_ROOM_VERSION = "M_INCOMPATIBLE_ROOM_VERSION" const val M_INCOMPATIBLE_ROOM_VERSION = "M_INCOMPATIBLE_ROOM_VERSION"
/** The state change requested cannot be performed, such as attempting to unban a user who is not banned. */ /** The state change requested cannot be performed, such as attempting to unban a user who is not banned. */
const val M_BAD_STATE = "M_BAD_STATE" const val M_BAD_STATE = "M_BAD_STATE"
/** The room or resource does not permit guests to access it. */ /** The room or resource does not permit guests to access it. */
const val M_GUEST_ACCESS_FORBIDDEN = "M_GUEST_ACCESS_FORBIDDEN" const val M_GUEST_ACCESS_FORBIDDEN = "M_GUEST_ACCESS_FORBIDDEN"
/** A Captcha is required to complete the request. */ /** A Captcha is required to complete the request. */
const val M_CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED" const val M_CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
/** The Captcha provided did not match what was expected. */ /** The Captcha provided did not match what was expected. */
const val M_CAPTCHA_INVALID = "M_CAPTCHA_INVALID" const val M_CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
/** A required parameter was missing from the request. */ /** A required parameter was missing from the request. */
const val M_MISSING_PARAM = "M_MISSING_PARAM" const val M_MISSING_PARAM = "M_MISSING_PARAM"
/** A parameter that was specified has the wrong value. For example, the server expected an integer and instead received a string. */ /** A parameter that was specified has the wrong value. For example, the server expected an integer and instead received a string. */
const val M_INVALID_PARAM = "M_INVALID_PARAM" const val M_INVALID_PARAM = "M_INVALID_PARAM"
/** The resource being requested is reserved by an application service, or the application service making the request has not created the resource. */ /** The resource being requested is reserved by an application service, or the application service making the request has not created the resource. */
const val M_EXCLUSIVE = "M_EXCLUSIVE" const val M_EXCLUSIVE = "M_EXCLUSIVE"
/** The user is unable to reject an invite to join the server notices room. See the Server Notices module for more information. */ /** The user is unable to reject an invite to join the server notices room. See the Server Notices module for more information. */
const val M_CANNOT_LEAVE_SERVER_NOTICE_ROOM = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM" const val M_CANNOT_LEAVE_SERVER_NOTICE_ROOM = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM"
/** (Not documented yet) */ /** (Not documented yet) */
const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION" const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
/** (Not documented yet) */ /** (Not documented yet) */
const val M_WEAK_PASSWORD = "M_WEAK_PASSWORD" const val M_WEAK_PASSWORD = "M_WEAK_PASSWORD"

View File

@ -71,15 +71,15 @@ sealed class Action {
fun List<Action>.toJson(): List<Any> { fun List<Action>.toJson(): List<Any> {
return map { action -> return map { action ->
when (action) { when (action) {
is Action.Notify -> Action.ACTION_NOTIFY is Action.Notify -> Action.ACTION_NOTIFY
is Action.DoNotNotify -> Action.ACTION_DONT_NOTIFY is Action.DoNotNotify -> Action.ACTION_DONT_NOTIFY
is Action.Sound -> { is Action.Sound -> {
mapOf( mapOf(
Action.ACTION_OBJECT_SET_TWEAK_KEY to Action.ACTION_OBJECT_SET_TWEAK_VALUE_SOUND, Action.ACTION_OBJECT_SET_TWEAK_KEY to Action.ACTION_OBJECT_SET_TWEAK_VALUE_SOUND,
Action.ACTION_OBJECT_VALUE_KEY to action.sound Action.ACTION_OBJECT_VALUE_KEY to action.sound
) )
} }
is Action.Highlight -> { is Action.Highlight -> {
mapOf( mapOf(
Action.ACTION_OBJECT_SET_TWEAK_KEY to Action.ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT, Action.ACTION_OBJECT_SET_TWEAK_KEY to Action.ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT,
Action.ACTION_OBJECT_VALUE_KEY to action.highlight Action.ACTION_OBJECT_VALUE_KEY to action.highlight
@ -94,9 +94,9 @@ fun PushRule.getActions(): List<Action> {
actions.forEach { actionStrOrObj -> actions.forEach { actionStrOrObj ->
when (actionStrOrObj) { when (actionStrOrObj) {
Action.ACTION_NOTIFY -> Action.Notify Action.ACTION_NOTIFY -> Action.Notify
Action.ACTION_DONT_NOTIFY -> Action.DoNotNotify Action.ACTION_DONT_NOTIFY -> Action.DoNotNotify
is Map<*, *> -> { is Map<*, *> -> {
when (actionStrOrObj[Action.ACTION_OBJECT_SET_TWEAK_KEY]) { when (actionStrOrObj[Action.ACTION_OBJECT_SET_TWEAK_KEY]) {
Action.ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> { Action.ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> {
(actionStrOrObj[Action.ACTION_OBJECT_VALUE_KEY] as? String)?.let { stringValue -> (actionStrOrObj[Action.ACTION_OBJECT_VALUE_KEY] as? String)?.let { stringValue ->
@ -112,13 +112,13 @@ fun PushRule.getActions(): List<Action> {
// When the value is not there, default is true, says the spec // When the value is not there, default is true, says the spec
?: Action.Highlight(true) ?: Action.Highlight(true)
} }
else -> { else -> {
Timber.w("Unsupported set_tweak value ${actionStrOrObj[Action.ACTION_OBJECT_SET_TWEAK_KEY]}") Timber.w("Unsupported set_tweak value ${actionStrOrObj[Action.ACTION_OBJECT_SET_TWEAK_KEY]}")
null null
} }
} }
} }
else -> { else -> {
Timber.w("Unsupported action type $actionStrOrObj") Timber.w("Unsupported action type $actionStrOrObj")
null null
} }

View File

@ -18,12 +18,12 @@ package org.matrix.android.sdk.api.raw
sealed class RawCacheStrategy { sealed class RawCacheStrategy {
// Data is always fetched from the server // Data is always fetched from the server
object NoCache: RawCacheStrategy() object NoCache : RawCacheStrategy()
// Once data is retrieved, it is stored for the provided amount of time. // Once data is retrieved, it is stored for the provided amount of time.
// In case of error, and if strict is set to false, the cache can be returned if available // In case of error, and if strict is set to false, the cache can be returned if available
data class TtlCache(val validityDurationInMillis: Long, val strict: Boolean): RawCacheStrategy() data class TtlCache(val validityDurationInMillis: Long, val strict: Boolean) : RawCacheStrategy()
// Once retrieved, the data is stored in cache and will be always get from the cache // Once retrieved, the data is stored in cache and will be always get from the cache
object InfiniteCache: RawCacheStrategy() object InfiniteCache : RawCacheStrategy()
} }

View File

@ -32,7 +32,7 @@ interface CallSignalingService {
fun removeCallListener(listener: CallsListener) fun removeCallListener(listener: CallsListener)
fun getCallWithId(callId: String) : MxCall? fun getCallWithId(callId: String): MxCall?
fun isThereAnyActiveCall(): Boolean fun isThereAnyActiveCall(): Boolean
} }

View File

@ -33,6 +33,7 @@ interface MxCallDetail {
interface MxCall : MxCallDetail { interface MxCall : MxCallDetail {
var state: CallState var state: CallState
/** /**
* Pick Up the incoming call * Pick Up the incoming call
* It has no effect on outgoing call * It has no effect on outgoing call

View File

@ -101,9 +101,9 @@ interface CryptoService {
fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>)
fun getMyDevicesInfo() : List<DeviceInfo> fun getMyDevicesInfo(): List<DeviceInfo>
fun getLiveMyDevicesInfo() : LiveData<List<DeviceInfo>> fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
@ -148,6 +148,6 @@ interface CryptoService {
fun getGossipingEventsTrail(): List<Event> fun getGossipingEventsTrail(): 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>
fun getWithHeldMegolmSession(roomId: String, sessionId: String) : RoomKeyWithHeldContent? fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent?
} }

View File

@ -216,7 +216,7 @@ interface KeysBackupService {
// For gossiping // For gossiping
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
fun getKeyBackupRecoveryKeyInfo() : SavedKeyBackupKeyInfo? fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>) fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>)
} }

View File

@ -53,23 +53,31 @@ package org.matrix.android.sdk.api.session.crypto.keysbackup
enum class KeysBackupState { enum class KeysBackupState {
// Need to check the current backup version on the homeserver // Need to check the current backup version on the homeserver
Unknown, Unknown,
// Checking if backup is enabled on home server // Checking if backup is enabled on home server
CheckingBackUpOnHomeserver, CheckingBackUpOnHomeserver,
// Backup has been stopped because a new backup version has been detected on the homeserver // Backup has been stopped because a new backup version has been detected on the homeserver
WrongBackUpVersion, WrongBackUpVersion,
// Backup from this device is not enabled // Backup from this device is not enabled
Disabled, Disabled,
// There is a backup available on the homeserver but it is not trusted. // There is a backup available on the homeserver but it is not trusted.
// It is not trusted because the signature is invalid or the device that created it is not verified // It is not trusted because the signature is invalid or the device that created it is not verified
// Use [KeysBackup.getKeysBackupTrust()] to get trust details. // Use [KeysBackup.getKeysBackupTrust()] to get trust details.
// Consequently, the backup from this device is not enabled. // Consequently, the backup from this device is not enabled.
NotTrusted, NotTrusted,
// Backup is being enabled: the backup version is being created on the homeserver // Backup is being enabled: the backup version is being created on the homeserver
Enabling, Enabling,
// Backup is enabled and ready to send backup to the homeserver // Backup is enabled and ready to send backup to the homeserver
ReadyToBackUp, ReadyToBackUp,
// e2e keys are going to be sent to the homeserver // e2e keys are going to be sent to the homeserver
WillBackUp, WillBackUp,
// e2e keys are being sent to the homeserver // e2e keys are being sent to the homeserver
BackingUp BackingUp
} }

View File

@ -35,7 +35,7 @@ interface GossipingRequestListener {
* Returns the secret value to be shared * Returns the secret value to be shared
* @return true if is handled * @return true if is handled
*/ */
fun onSecretShareRequest(request: IncomingSecretShareRequest) : Boolean fun onSecretShareRequest(request: IncomingSecretShareRequest): Boolean
/** /**
* A room key request cancellation has been received. * A room key request cancellation has been received.

View File

@ -22,8 +22,10 @@ package org.matrix.android.sdk.api.session.crypto.verification
enum class VerificationMethod { enum class VerificationMethod {
// Use it when your application supports the SAS verification method // Use it when your application supports the SAS verification method
SAS, SAS,
// Use it if your application is able to display QR codes // Use it if your application is able to display QR codes
QR_CODE_SHOW, QR_CODE_SHOW,
// Use it if your application is able to scan QR codes // Use it if your application is able to scan QR codes
QR_CODE_SCAN QR_CODE_SCAN
} }

View File

@ -253,6 +253,7 @@ fun Event.isFileMessage(): Boolean {
else -> false else -> false
} }
} }
fun Event.isAttachmentMessage(): Boolean { fun Event.isAttachmentMessage(): Boolean {
return getClearType() == EventType.MESSAGE return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) { && when (getClearContent()?.toModel<MessageContent>()?.msgType) {

View File

@ -21,10 +21,13 @@ package org.matrix.android.sdk.api.session.events.model
object RelationType { object RelationType {
/** Lets you define an event which annotates an existing event.*/ /** Lets you define an event which annotates an existing event.*/
const val ANNOTATION = "m.annotation" const val ANNOTATION = "m.annotation"
/** Lets you define an event which replaces an existing event.*/ /** Lets you define an event which replaces an existing event.*/
const val REPLACE = "m.replace" const val REPLACE = "m.replace"
/** Lets you define an event which references an existing event.*/ /** Lets you define an event which references an existing event.*/
const val REFERENCE = "m.reference" const val REFERENCE = "m.reference"
/** Lets you define an event which adds a response to an existing event.*/ /** Lets you define an event which adds a response to an existing event.*/
const val RESPONSE = "org.matrix.response" const val RESPONSE = "org.matrix.response"
} }

View File

@ -16,6 +16,6 @@
package org.matrix.android.sdk.api.session.events.model package org.matrix.android.sdk.api.session.events.model
interface UnsignedRelationInfo { interface UnsignedRelationInfo {
val limited : Boolean? val limited: Boolean?
val count: Int? val count: Int?
} }

View File

@ -33,10 +33,12 @@ data class IntegrationManagerConfig(
* Defined in UserAccountData * Defined in UserAccountData
*/ */
ACCOUNT, ACCOUNT,
/** /**
* Defined in Wellknown * Defined in Wellknown
*/ */
HOMESERVER, HOMESERVER,
/** /**
* Fallback value, hardcoded by the SDK * Fallback value, hardcoded by the SDK
*/ */

View File

@ -16,7 +16,7 @@
package org.matrix.android.sdk.api.session.room.members package org.matrix.android.sdk.api.session.room.members
sealed class ChangeMembershipState() { sealed class ChangeMembershipState {
object Unknown : ChangeMembershipState() object Unknown : ChangeMembershipState()
object Joining : ChangeMembershipState() object Joining : ChangeMembershipState()
data class FailedJoining(val throwable: Throwable) : ChangeMembershipState() data class FailedJoining(val throwable: Throwable) : ChangeMembershipState()

View File

@ -35,7 +35,7 @@ data class PollSummaryContent(
return votes?.size ?: 0 return votes?.size ?: 0
} }
fun voteCountForOption(optionIndex: Int) : Int { fun voteCountForOption(optionIndex: Int): Int {
return votes?.filter { it.optionIndex == optionIndex }?.count() ?: 0 return votes?.filter { it.optionIndex == optionIndex }?.count() ?: 0
} }
} }

View File

@ -33,6 +33,7 @@ data class RoomGuestAccessContent(
enum class GuestAccess(val value: String) { enum class GuestAccess(val value: String) {
@Json(name = "can_join") @Json(name = "can_join")
CanJoin("can_join"), CanJoin("can_join"),
@Json(name = "forbidden") @Json(name = "forbidden")
Forbidden("forbidden") Forbidden("forbidden")
} }

View File

@ -29,16 +29,19 @@ enum class RoomHistoryVisibility {
* participating homeserver with anyone, regardless of whether they have ever joined the room. * participating homeserver with anyone, regardless of whether they have ever joined the room.
*/ */
@Json(name = "world_readable") WORLD_READABLE, @Json(name = "world_readable") WORLD_READABLE,
/** /**
* Previous events are always accessible to newly joined members. All events in the * Previous events are always accessible to newly joined members. All events in the
* room are accessible, even those sent when the member was not a part of the room. * room are accessible, even those sent when the member was not a part of the room.
*/ */
@Json(name = "shared") SHARED, @Json(name = "shared") SHARED,
/** /**
* Events are accessible to newly joined members from the point they were invited onwards. * Events are accessible to newly joined members from the point they were invited onwards.
* Events stop being accessible when the member's state changes to something other than invite or join. * Events stop being accessible when the member's state changes to something other than invite or join.
*/ */
@Json(name = "invited") INVITED, @Json(name = "invited") INVITED,
/** /**
* Events are accessible to newly joined members from the point they joined the room onwards. * Events are accessible to newly joined members from the point they joined the room onwards.
* Events stop being accessible when the member's state changes to something other than join. * Events stop being accessible when the member's state changes to something other than join.

View File

@ -53,6 +53,6 @@ data class MessageAudioContent(
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageWithAttachmentContent { ) : MessageWithAttachmentContent {
override val mimeType: String? override val mimeType: String?
get() = encryptedFileInfo?.mimetype ?: audioInfo?.mimeType get() = encryptedFileInfo?.mimetype ?: audioInfo?.mimeType
} }

View File

@ -53,6 +53,6 @@ data class MessageImageContent(
*/ */
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageImageInfoContent { ) : MessageImageInfoContent {
override val mimeType: String? override val mimeType: String?
get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: "image/*" get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: "image/*"
} }

View File

@ -53,6 +53,6 @@ data class MessageStickerContent(
*/ */
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageImageInfoContent { ) : MessageImageInfoContent {
override val mimeType: String? override val mimeType: String?
get() = encryptedFileInfo?.mimetype ?: info?.mimeType get() = encryptedFileInfo?.mimetype ?: info?.mimeType
} }

View File

@ -29,6 +29,7 @@ object MessageType {
const val MSGTYPE_RESPONSE = "org.matrix.response" const val MSGTYPE_RESPONSE = "org.matrix.response"
const val MSGTYPE_POLL_CLOSED = "org.matrix.poll_closed" const val MSGTYPE_POLL_CLOSED = "org.matrix.poll_closed"
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request" const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
// Add, in local, a fake message type in order to StickerMessage can inherit Message class // Add, in local, a fake message type in order to StickerMessage can inherit Message class
// Because sticker isn't a message type but a event type without msgtype field // Because sticker isn't a message type but a event type without msgtype field
const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker" const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"

View File

@ -52,6 +52,6 @@ data class MessageVideoContent(
*/ */
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageWithAttachmentContent { ) : MessageWithAttachmentContent {
override val mimeType: String? override val mimeType: String?
get() = encryptedFileInfo?.mimetype ?: videoInfo?.mimeType get() = encryptedFileInfo?.mimetype ?: videoInfo?.mimeType
} }

View File

@ -18,18 +18,25 @@ package org.matrix.android.sdk.api.session.room.send
enum class SendState { enum class SendState {
UNKNOWN, UNKNOWN,
// the event has not been sent // the event has not been sent
UNSENT, UNSENT,
// the event is encrypting // the event is encrypting
ENCRYPTING, ENCRYPTING,
// the event is currently sending // the event is currently sending
SENDING, SENDING,
// the event has been sent // the event has been sent
SENT, SENT,
// the event has been received from server // the event has been received from server
SYNCED, SYNCED,
// The event failed to be sent // The event failed to be sent
UNDELIVERED, UNDELIVERED,
// the event failed to be sent because some unknown devices have been found while encrypting it // the event failed to be sent because some unknown devices have been found while encrypting it
FAILED_UNKNOWN_DEVICES; FAILED_UNKNOWN_DEVICES;

View File

@ -128,6 +128,7 @@ interface Timeline {
* It represents future events. * It represents future events.
*/ */
FORWARDS, FORWARDS,
/** /**
* It represents past events. * It represents past events.
*/ */

View File

@ -40,5 +40,5 @@ interface TimelineService {
fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>>
fun getAttachmentMessages() : List<TimelineEvent> fun getAttachmentMessages(): List<TimelineEvent>
} }

View File

@ -20,6 +20,7 @@ interface FilterService {
enum class FilterPreset { enum class FilterPreset {
NoFilter, NoFilter,
/** /**
* Filter for Riot, will include only known event type * Filter for Riot, will include only known event type
*/ */

View File

@ -46,7 +46,7 @@ data class Optional<T : Any> constructor(private val value: T?) {
return Optional(value) return Optional(value)
} }
fun <T: Any> empty(): Optional<T> { fun <T : Any> empty(): Optional<T> {
return Optional(null) return Optional(null)
} }
} }

View File

@ -35,6 +35,7 @@ const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-
* Secured Shared Storage algorithm constant * Secured Shared Storage algorithm constant
*/ */
const val SSSS_ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2" const val SSSS_ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2"
/* Secrets are encrypted using AES-CTR-256 and MACed using HMAC-SHA-256. **/ /* Secrets are encrypted using AES-CTR-256 and MACed using HMAC-SHA-256. **/
const val SSSS_ALGORITHM_AES_HMAC_SHA2 = "m.secret_storage.v1.aes-hmac-sha2" const val SSSS_ALGORITHM_AES_HMAC_SHA2 = "m.secret_storage.v1.aes-hmac-sha2"

View File

@ -194,18 +194,18 @@ internal class DefaultCryptoService @Inject constructor(
private val lastNewSessionForcedDates = MXUsersDevicesMap<Long>() private val lastNewSessionForcedDates = MXUsersDevicesMap<Long>()
fun onStateEvent(roomId: String, event: Event) { fun onStateEvent(roomId: String, event: Event) {
when { when (event.getClearType()) {
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
} }
} }
fun onLiveEvent(roomId: String, event: Event) { fun onLiveEvent(roomId: String, event: Event) {
when { when (event.getClearType()) {
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
} }
} }
@ -615,6 +615,7 @@ internal class DefaultCryptoService @Inject constructor(
val encryptionEvent = monarchy.fetchCopied { realm -> val encryptionEvent = monarchy.fetchCopied { realm ->
EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
.contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"") .contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
.isNotNull(EventEntityFields.STATE_KEY)
.findFirst() .findFirst()
} }
return encryptionEvent != null return encryptionEvent != null
@ -915,6 +916,11 @@ internal class DefaultCryptoService @Inject constructor(
* @param event the encryption event. * @param event the encryption event.
*/ */
private fun onRoomEncryptionEvent(roomId: String, event: Event) { private fun onRoomEncryptionEvent(roomId: String, event: Event) {
if (!event.isStateEvent()) {
// Ignore
Timber.w("Invalid encryption event")
return
}
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
val params = LoadRoomMembersTask.Params(roomId) val params = LoadRoomMembersTask.Params(roomId)
try { try {

View File

@ -359,7 +359,6 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
cryptoStore.storeUserDevices(userId, workingCopy) cryptoStore.storeUserDevices(userId, workingCopy)
} }
// Handle cross signing keys update
val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also { val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also {
Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}") Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}")
} }

View File

@ -28,7 +28,8 @@ enum class GossipingRequestState {
ACCEPTING, ACCEPTING,
ACCEPTED, ACCEPTED,
FAILED_TO_ACCEPTED, FAILED_TO_ACCEPTED,
// USER_REJECTED,
// USER_REJECTED,
UNABLE_TO_PROCESS, UNABLE_TO_PROCESS,
CANCELLED_BY_REQUESTER, CANCELLED_BY_REQUESTER,
RE_REQUESTED RE_REQUESTED

View File

@ -36,6 +36,7 @@ import kotlin.math.min
object MXMegolmExportEncryption { object MXMegolmExportEncryption {
private const val HEADER_LINE = "-----BEGIN MEGOLM SESSION DATA-----" private const val HEADER_LINE = "-----BEGIN MEGOLM SESSION DATA-----"
private const val TRAILER_LINE = "-----END MEGOLM SESSION DATA-----" private const val TRAILER_LINE = "-----END MEGOLM SESSION DATA-----"
// we split into lines before base64ing, because encodeBase64 doesn't deal // we split into lines before base64ing, because encodeBase64 doesn't deal
// terribly well with large arrays. // terribly well with large arrays.
private const val LINE_LENGTH = 72 * 4 / 3 private const val LINE_LENGTH = 72 * 4 / 3

View File

@ -70,7 +70,9 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
delay(1500) delay(1500)
cryptoStore.getOrAddOutgoingSecretShareRequest(secretName, recipients)?.let { cryptoStore.getOrAddOutgoingSecretShareRequest(secretName, recipients)?.let {
// TODO check if there is already one that is being sent? // TODO check if there is already one that is being sent?
if (it.state == OutgoingGossipingRequestState.SENDING /**|| it.state == OutgoingGossipingRequestState.SENT*/) { if (it.state == OutgoingGossipingRequestState.SENDING
/**|| it.state == OutgoingGossipingRequestState.SENT*/
) {
Timber.v("## CRYPTO - GOSSIP sendSecretShareRequest() : we are already sending for that session: $it") Timber.v("## CRYPTO - GOSSIP sendSecretShareRequest() : we are already sending for that session: $it")
return@launch return@launch
} }

View File

@ -68,7 +68,7 @@ internal interface IMXDecrypting {
*/ */
fun shareKeysWithDevice(request: IncomingRoomKeyRequest) {} fun shareKeysWithDevice(request: IncomingRoomKeyRequest) {}
fun shareSecretWithDevice(request: IncomingSecretShareRequest, secretValue : String) {} fun shareSecretWithDevice(request: IncomingSecretShareRequest, secretValue: String) {}
fun requestKeysForEvent(event: Event, withHeld: Boolean) fun requestKeysForEvent(event: Event, withHeld: Boolean)
} }

View File

@ -104,7 +104,7 @@ internal class MXMegolmDecryption(private val userId: String,
senderCurve25519Key = olmDecryptionResult.senderKey, senderCurve25519Key = olmDecryptionResult.senderKey,
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"), claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
.orEmpty() .orEmpty()
) )
} else { } else {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON) throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)

View File

@ -43,7 +43,7 @@ internal class ShieldTrustUpdater @Inject constructor(
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
@SessionDatabase private val sessionRealmConfiguration: RealmConfiguration, @SessionDatabase private val sessionRealmConfiguration: RealmConfiguration,
private val roomSummaryUpdater: RoomSummaryUpdater private val roomSummaryUpdater: RoomSummaryUpdater
): SessionLifecycleObserver { ) : SessionLifecycleObserver {
companion object { companion object {
private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD") private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD")

View File

@ -80,6 +80,7 @@ class OlmInboundGroupSessionWrapper2 : Serializable {
constructor() { constructor() {
// empty // empty
} }
/** /**
* Create a new instance from the provided keys map. * Create a new instance from the provided keys map.
* *

View File

@ -66,19 +66,23 @@ enum class WithHeldCode(val value: String) {
* the user/device was blacklisted * the user/device was blacklisted
*/ */
BLACKLISTED("m.blacklisted"), BLACKLISTED("m.blacklisted"),
/** /**
* the user/devices is unverified * the user/devices is unverified
*/ */
UNVERIFIED("m.unverified"), UNVERIFIED("m.unverified"),
/** /**
* the user/device is not allowed have the key. For example, this would usually be sent in response * the user/device is not allowed have the key. For example, this would usually be sent in response
* to a key request if the user was not in the room when the message was sent * to a key request if the user was not in the room when the message was sent
*/ */
UNAUTHORISED("m.unauthorised"), UNAUTHORISED("m.unauthorised"),
/** /**
* Sent in reply to a key request if the device that the key is requested from does not have the requested key * Sent in reply to a key request if the device that the key is requested from does not have the requested key
*/ */
UNAVAILABLE("m.unavailable"), UNAVAILABLE("m.unavailable"),
/** /**
* An olm session could not be established. * An olm session could not be established.
* This may happen, for example, if the sender was unable to obtain a one-time key from the recipient. * This may happen, for example, if the sender was unable to obtain a one-time key from the recipient.

View File

@ -17,6 +17,6 @@
package org.matrix.android.sdk.internal.crypto.store package org.matrix.android.sdk.internal.crypto.store
data class SavedKeyBackupKeyInfo( data class SavedKeyBackupKeyInfo(
val recoveryKey : String, val recoveryKey: String,
val version: String val version: String
) )

View File

@ -23,7 +23,6 @@ import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import io.realm.Sort import io.realm.Sort
import io.realm.kotlin.where import io.realm.kotlin.where
import org.matrix.android.sdk.api.auth.data.Credentials
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.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.LocalEcho
@ -86,7 +85,9 @@ import org.matrix.android.sdk.internal.crypto.store.db.query.getById
import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate
import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.ContentMapper
import org.matrix.android.sdk.internal.di.CryptoDatabase import org.matrix.android.sdk.internal.di.CryptoDatabase
import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.olm.OlmAccount import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmException import org.matrix.olm.OlmException
@ -98,7 +99,9 @@ import kotlin.collections.set
internal class RealmCryptoStore @Inject constructor( internal class RealmCryptoStore @Inject constructor(
@CryptoDatabase private val realmConfiguration: RealmConfiguration, @CryptoDatabase private val realmConfiguration: RealmConfiguration,
private val crossSigningKeysMapper: CrossSigningKeysMapper, private val crossSigningKeysMapper: CrossSigningKeysMapper,
private val credentials: Credentials) : IMXCryptoStore { @UserId private val userId: String,
@DeviceId private val deviceId: String?
) : IMXCryptoStore {
/* ========================================================================================== /* ==========================================================================================
* Memory cache, to correctly release JNI objects * Memory cache, to correctly release JNI objects
@ -141,8 +144,8 @@ internal class RealmCryptoStore @Inject constructor(
// Check credentials // Check credentials
// The device id may not have been provided in credentials. // The device id may not have been provided in credentials.
// Check it only if provided, else trust the stored one. // Check it only if provided, else trust the stored one.
if (currentMetadata.userId != credentials.userId if (currentMetadata.userId != userId
|| (credentials.deviceId != null && credentials.deviceId != currentMetadata.deviceId)) { || (deviceId != null && deviceId != currentMetadata.deviceId)) {
Timber.w("## open() : Credentials do not match, close this store and delete data") Timber.w("## open() : Credentials do not match, close this store and delete data")
deleteAll = true deleteAll = true
currentMetadata = null currentMetadata = null
@ -155,8 +158,8 @@ internal class RealmCryptoStore @Inject constructor(
} }
// Metadata not found, or database cleaned, create it // Metadata not found, or database cleaned, create it
realm.createObject(CryptoMetadataEntity::class.java, credentials.userId).apply { realm.createObject(CryptoMetadataEntity::class.java, userId).apply {
deviceId = credentials.deviceId deviceId = this@RealmCryptoStore.deviceId
} }
} }
} }
@ -302,22 +305,42 @@ internal class RealmCryptoStore @Inject constructor(
userEntity.crossSigningInfoEntity?.deleteFromRealm() userEntity.crossSigningInfoEntity?.deleteFromRealm()
userEntity.crossSigningInfoEntity = null userEntity.crossSigningInfoEntity = null
} else { } else {
var shouldResetMyDevicesLocalTrust = false
CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo ->
// What should we do if we detect a change of the keys? // What should we do if we detect a change of the keys?
val existingMaster = signingInfo.getMasterKey() val existingMaster = signingInfo.getMasterKey()
if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) { if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) {
crossSigningKeysMapper.update(existingMaster, masterKey) crossSigningKeysMapper.update(existingMaster, masterKey)
} else { } else {
Timber.d("## CrossSigning MSK change for $userId")
val keyEntity = crossSigningKeysMapper.map(masterKey) val keyEntity = crossSigningKeysMapper.map(masterKey)
signingInfo.setMasterKey(keyEntity) signingInfo.setMasterKey(keyEntity)
if (userId == this.userId) {
shouldResetMyDevicesLocalTrust = true
// my msk has changed! clear my private key
// Could we have some race here? e.g I am the one that did change the keys
// could i get this update to early and clear the private keys?
// -> initializeCrossSigning is guarding for that by storing all at once
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignMasterPrivateKey = null
}
}
} }
val existingSelfSigned = signingInfo.getSelfSignedKey() val existingSelfSigned = signingInfo.getSelfSignedKey()
if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) { if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) {
crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey) crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey)
} else { } else {
Timber.d("## CrossSigning SSK change for $userId")
val keyEntity = crossSigningKeysMapper.map(selfSigningKey) val keyEntity = crossSigningKeysMapper.map(selfSigningKey)
signingInfo.setSelfSignedKey(keyEntity) signingInfo.setSelfSignedKey(keyEntity)
if (userId == this.userId) {
shouldResetMyDevicesLocalTrust = true
// my ssk has changed! clear my private key
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignSelfSignedPrivateKey = null
}
}
} }
// Only for me // Only for me
@ -326,10 +349,29 @@ internal class RealmCryptoStore @Inject constructor(
if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) { if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) {
crossSigningKeysMapper.update(existingUSK, userSigningKey) crossSigningKeysMapper.update(existingUSK, userSigningKey)
} else { } else {
Timber.d("## CrossSigning USK change for $userId")
val keyEntity = crossSigningKeysMapper.map(userSigningKey) val keyEntity = crossSigningKeysMapper.map(userSigningKey)
signingInfo.setUserSignedKey(keyEntity) signingInfo.setUserSignedKey(keyEntity)
if (userId == this.userId) {
shouldResetMyDevicesLocalTrust = true
// my usk has changed! clear my private key
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignUserPrivateKey = null
}
}
} }
} }
// When my cross signing keys are reset, we consider clearing all existing device trust
if (shouldResetMyDevicesLocalTrust) {
realm.where<UserEntity>()
.equalTo(UserEntityFields.USER_ID, this.userId)
.findFirst()
?.devices?.forEach {
it?.trustLevelEntity?.crossSignedVerified = false
it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId
}
}
userEntity.crossSigningInfoEntity = signingInfo userEntity.crossSigningInfoEntity = signingInfo
} }
} }
@ -1197,7 +1239,7 @@ internal class RealmCryptoStore @Inject constructor(
.findAll() .findAll()
.mapNotNull { entity -> .mapNotNull { entity ->
when (entity.type) { when (entity.type) {
GossipRequestType.KEY -> { GossipRequestType.KEY -> {
IncomingRoomKeyRequest( IncomingRoomKeyRequest(
userId = entity.otherUserId, userId = entity.otherUserId,
deviceId = entity.otherDeviceId, deviceId = entity.otherDeviceId,
@ -1316,7 +1358,7 @@ internal class RealmCryptoStore @Inject constructor(
.findAll() .findAll()
xInfoEntities?.forEach { info -> xInfoEntities?.forEach { info ->
// Need to ignore mine // Need to ignore mine
if (info.userId != credentials.userId) { if (info.userId != userId) {
info.crossSigningKeys.forEach { info.crossSigningKeys.forEach {
it.trustLevelEntity = null it.trustLevelEntity = null
} }
@ -1331,7 +1373,7 @@ internal class RealmCryptoStore @Inject constructor(
.findAll() .findAll()
xInfoEntities?.forEach { xInfoEntity -> xInfoEntities?.forEach { xInfoEntity ->
// Need to ignore mine // Need to ignore mine
if (xInfoEntity.userId == credentials.userId) return@forEach if (xInfoEntity.userId == userId) return@forEach
val mapped = mapCrossSigningInfoEntity(xInfoEntity) val mapped = mapCrossSigningInfoEntity(xInfoEntity)
val currentTrust = mapped.isTrusted() val currentTrust = mapped.isTrusted()
val newTrust = check(mapped.userId) val newTrust = check(mapped.userId)

View File

@ -26,10 +26,10 @@ internal fun OlmSessionEntity.Companion.createPrimaryKey(sessionId: String, devi
// olmSessionData is a serialized OlmSession // olmSessionData is a serialized OlmSession
internal open class OlmSessionEntity(@PrimaryKey var primaryKey: String = "", internal open class OlmSessionEntity(@PrimaryKey var primaryKey: String = "",
var sessionId: String? = null, var sessionId: String? = null,
var deviceKey: String? = null, var deviceKey: String? = null,
var olmSessionData: String? = null, var olmSessionData: String? = null,
var lastReceivedMessageTs: Long = 0) var lastReceivedMessageTs: Long = 0)
: RealmObject() { : RealmObject() {
fun getOlmSession(): OlmSession? { fun getOlmSession(): OlmSession? {

View File

@ -138,7 +138,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
override fun onVerificationAccept(accept: ValidVerificationInfoAccept) { override fun onVerificationAccept(accept: ValidVerificationInfoAccept) {
Timber.v("## SAS O: onVerificationAccept id:$transactionId") Timber.v("## SAS O: onVerificationAccept id:$transactionId")
if (state != VerificationTxState.Started && state != VerificationTxState.SendingStart) { if (state != VerificationTxState.Started && state != VerificationTxState.SendingStart) {
Timber.e("## SAS O: received accept request from invalid state $state") Timber.e("## SAS O: received accept request from invalid state $state")
cancel(CancelCode.UnexpectedMessage) cancel(CancelCode.UnexpectedMessage)
return return
@ -212,7 +212,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
// - the Matrix ID of the user who sent the m.key.verification.accept message, // - the Matrix ID of the user who sent the m.key.verification.accept message,
// - he device ID of the device that sent the m.key.verification.accept message // - he device ID of the device that sent the m.key.verification.accept message
// - the transaction ID. // - the transaction ID.
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$userId$deviceId$otherUserId$otherDeviceId$transactionId" val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$userId$deviceId$otherUserId$otherDeviceId$transactionId"
// decimal: generate five bytes by using HKDF. // decimal: generate five bytes by using HKDF.
// emoji: generate six bytes by using HKDF. // emoji: generate six bytes by using HKDF.
@ -220,7 +220,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
} }
KEY_AGREEMENT_V2 -> { KEY_AGREEMENT_V2 -> {
// Adds the SAS public key, and separate by | // Adds the SAS public key, and separate by |
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS|$userId|$deviceId|${getSAS().publicKey}|$otherUserId|$otherDeviceId|$otherKey|$transactionId" val sasInfo = "MATRIX_KEY_VERIFICATION_SAS|$userId|$deviceId|${getSAS().publicKey}|$otherUserId|$otherDeviceId|$otherKey|$transactionId"
return getSAS().generateShortCode(sasInfo, 6) return getSAS().generateShortCode(sasInfo, 6)
} }
else -> { else -> {

View File

@ -57,7 +57,7 @@ internal abstract class DefaultVerificationTransaction(
protected fun trust(canTrustOtherUserMasterKey: Boolean, protected fun trust(canTrustOtherUserMasterKey: Boolean,
toVerifyDeviceIds: List<String>, toVerifyDeviceIds: List<String>,
eventuallyMarkMyMasterKeyAsTrusted: Boolean, autoDone : Boolean = true) { eventuallyMarkMyMasterKeyAsTrusted: Boolean, autoDone: Boolean = true) {
Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds") Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds")
Timber.d("## Verification: trust Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted") Timber.d("## Verification: trust Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted")

View File

@ -68,10 +68,13 @@ internal abstract class SASDefaultVerificationTransaction(
// Deprecated maybe removed later, use V2 // Deprecated maybe removed later, use V2
const val KEY_AGREEMENT_V1 = "curve25519" const val KEY_AGREEMENT_V1 = "curve25519"
const val KEY_AGREEMENT_V2 = "curve25519-hkdf-sha256" const val KEY_AGREEMENT_V2 = "curve25519-hkdf-sha256"
// ordered by preferred order // ordered by preferred order
val KNOWN_AGREEMENT_PROTOCOLS = listOf(KEY_AGREEMENT_V2, KEY_AGREEMENT_V1) val KNOWN_AGREEMENT_PROTOCOLS = listOf(KEY_AGREEMENT_V2, KEY_AGREEMENT_V1)
// ordered by preferred order // ordered by preferred order
val KNOWN_HASHES = listOf("sha256") val KNOWN_HASHES = listOf("sha256")
// ordered by preferred order // ordered by preferred order
val KNOWN_MACS = listOf(SAS_MAC_SHA256, SAS_MAC_SHA256_LONGKDF) val KNOWN_MACS = listOf(SAS_MAC_SHA256, SAS_MAC_SHA256_LONGKDF)
@ -101,6 +104,7 @@ internal abstract class SASDefaultVerificationTransaction(
// Visible for test // Visible for test
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
// Visible for test // Visible for test
var accepted: ValidVerificationInfoAccept? = null var accepted: ValidVerificationInfoAccept? = null
protected var otherKey: String? = null protected var otherKey: String? = null

View File

@ -118,7 +118,7 @@ internal class VerificationTransportRoomMessage(
?.firstOrNull { it.id == enqueueInfo.second } ?.firstOrNull { it.id == enqueueInfo.second }
?.let { wInfo -> ?.let { wInfo ->
when (wInfo.state) { when (wInfo.state) {
WorkInfo.State.FAILED -> { WorkInfo.State.FAILED -> {
tx?.cancel(onErrorReason) tx?.cancel(onErrorReason)
workLiveData.removeObserver(this) workLiveData.removeObserver(this)
} }
@ -135,7 +135,7 @@ internal class VerificationTransportRoomMessage(
} }
workLiveData.removeObserver(this) workLiveData.removeObserver(this)
} }
else -> { else -> {
// nop // nop
} }
} }

View File

@ -93,7 +93,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
} }
// Expose to handle Realm migration to riotX // Expose to handle Realm migration to riotX
fun getRealmEncryptionKey(alias: String) : ByteArray { fun getRealmEncryptionKey(alias: String): ByteArray {
val key = if (hasKeyForDatabase(alias)) { val key = if (hasKeyForDatabase(alias)) {
Timber.i("Found key for alias:$alias") Timber.i("Found key for alias:$alias")
extractKeyForDatabase(alias) extractKeyForDatabase(alias)

View File

@ -28,8 +28,8 @@ import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
internal suspend fun <T> awaitNotEmptyResult(realmConfiguration: RealmConfiguration, internal suspend fun <T> awaitNotEmptyResult(realmConfiguration: RealmConfiguration,
timeoutMillis: Long, timeoutMillis: Long,
builder: (Realm) -> RealmQuery<T>) { builder: (Realm) -> RealmQuery<T>) {
withTimeout(timeoutMillis) { withTimeout(timeoutMillis) {
// Confine Realm interaction to a single thread with Looper. // Confine Realm interaction to a single thread with Looper.
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {

View File

@ -23,12 +23,12 @@ import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects import io.realm.annotations.LinkingObjects
internal open class ChunkEntity(@Index var prevToken: String? = null, internal open class ChunkEntity(@Index var prevToken: String? = null,
// Because of gaps we can have several chunks with nextToken == null // Because of gaps we can have several chunks with nextToken == null
@Index var nextToken: String? = null, @Index var nextToken: String? = null,
var stateEvents: RealmList<EventEntity> = RealmList(), var stateEvents: RealmList<EventEntity> = RealmList(),
var timelineEvents: RealmList<TimelineEventEntity> = RealmList(), var timelineEvents: RealmList<TimelineEventEntity> = RealmList(),
var numberOfTimelineEvents: Long = 0, var numberOfTimelineEvents: Long = 0,
// Only one chunk will have isLastForward == true // Only one chunk will have isLastForward == true
@Index var isLastForward: Boolean = false, @Index var isLastForward: Boolean = false,
@Index var isLastBackward: Boolean = false @Index var isLastBackward: Boolean = false
) : RealmObject() { ) : RealmObject() {

Some files were not shown because too many files have changed in this diff Show More