Merge branch 'release/0.21.0'

This commit is contained in:
Benoit Marty 2020-05-28 10:43:55 +02:00
commit d1c4d4b099
411 changed files with 13503 additions and 1602 deletions

View file

@ -12,12 +12,15 @@
<w>fdroid</w> <w>fdroid</w>
<w>gplay</w> <w>gplay</w>
<w>hmac</w> <w>hmac</w>
<w>homeserver</w>
<w>ktlint</w> <w>ktlint</w>
<w>linkified</w> <w>linkified</w>
<w>linkify</w> <w>linkify</w>
<w>megolm</w> <w>megolm</w>
<w>msisdn</w> <w>msisdn</w>
<w>msisdns</w>
<w>pbkdf</w> <w>pbkdf</w>
<w>pids</w>
<w>pkcs</w> <w>pkcs</w>
<w>riotx</w> <w>riotx</w>
<w>signin</w> <w>signin</w>

View file

@ -1,3 +1,30 @@
Changes in RiotX 0.21.0 (2020-05-28)
===================================================
Features ✨:
- Identity server support (#607)
- Switch language support (#41)
- Display list of attachments of a room (#860)
Improvements 🙌:
- Better connectivity lost indicator when airplane mode is on
- Add a setting to hide redacted events (#951)
- Render formatted_body for m.notice and m.emote (#1196)
- Change icon to magnifying-glass to filter room (#1384)
Bugfix 🐛:
- After jump to unread, newer messages are never loaded (#1008)
- Fix issues with FontScale switch (#69, #645)
- "Seen by" uses 12h time (#1378)
- Enable markdown (if active) when sending emote (#734)
- Screenshots for Rageshake now includes Dialogs such as BottomSheet (#1349)
SDK API changes ⚠️:
- initialize with proxy configuration
Other changes:
- support new key agreement method for SAS (#1374)
Changes in RiotX 0.20.0 (2020-05-15) Changes in RiotX 0.20.0 (2020-05-15)
=================================================== ===================================================

92
docs/identity_server.md Normal file
View file

@ -0,0 +1,92 @@
# Identity server
Issue: #607
PR: #1354
## Introduction
Identity Servers support contact discovery on Matrix by letting people look up Third Party Identifiers to see if the owner has publicly linked them with their Matrix ID.
## Implementation
The current implementation was Inspired by the code from Riot-Android.
Difference though (list not exhaustive):
- Only API v2 is supported (see https://matrix.org/docs/spec/identity_service/latest)
- Homeserver has to be up to date to support binding (Versions.isLoginAndRegistrationSupportedBySdk() has to return true)
- The SDK managed the session and client secret when binding ThreePid. Those data are not exposed to the client.
- The SDK supports incremental sendAttempt (this is not used by RiotX)
- The "Continue" button is now under the information, and not as the same place that the checkbox
- The app can cancel a binding. Current data are erased from DB.
- The API (IdentityService) is improved.
- A new DB to store data related to the identity server management.
Missing features (list not exhaustive):
- Invite by 3Pid (will be in a dedicated PR)
- Add email or phone to account (not P1, can be done on Riot-Web)
- List email and phone of the account (could be done in a dedicated PR)
- Search contact (not P1)
- Logout from identity server when user sign out or deactivate his account.
## Related MSCs
The list can be found here: https://matrix.org/blog/2019/09/27/privacy-improvements-in-synapse-1-4-and-riot-1-4
## Steps and requirements
- Only one identity server by account can be set. The user's choice is stored in account data with key `m.identity_server`. But every clients will managed its own token to log in to the identity server
```json
{
"type": "m.identity_server",
"content": {
"base_url": "https://matrix.org"
}
}
```
- The accepted terms are stored in the account data:
```json
{
"type": "m.accepted_terms",
"content": {
"accepted": [
"https://vector.im/identity-server-privacy-notice-1"
]
}
}
```
- Default identity server URL, from Wellknown data is proposed to the user.
- Identity server can be set
- Identity server can be changed on another user's device, so when the change is detected (thanks to account data sync) RiotX should properly disconnect from a previous identity server (I think it was not the case in Riot-Android, where we keep the token forever)
- Registration to the identity server is managed with an openId token
- Terms of service can be accepted when configuring the identity server.
- Terms of service can be accepted after, if they change.
- Identity server can be modified
- Identity server can be disconnected with a warning dialog, with special content if there are current bound 3pid on this identity server.
- Email can be bound
- Email can be unbound
- Phone can be bound
- Phone can be unbound
- Look up can be performed, to get matrixIds from local contact book (phone and email): Android permission correctly handled (not done yet)
- Look up pepper can be updated if it is rotated on the identity server
- Invitation using 3PID can be done (See #548) (not done yet)
- Homeserver access-token will never be sent to an identity server
- When user sign-out: logout from the identity server if any.
- When user deactivate account: logout from the identity server if any.
## Screens
### Settings
Identity server settings can be accessed from the internal setting of the application, both from "Discovery" section and from identity detail section.
### Discovery screen
This screen displays the identity server configuration and the binding of the user's ThreePid (email and msisdn). This is the main screen of the feature.
### Set identity server screen
This screen is a form to set a new identity server URL
## Ref:
- https://matrix.org/blog/2019/09/27/privacy-improvements-in-synapse-1-4-and-riot-1-4 is a good summary of the role of an Identity server and the proper way to configure and use it in respect to the privacy and the consent of the user.
- API documentation: https://matrix.org/docs/spec/identity_service/latest
- vector.im TOS: https://vector.im/identity-server-privacy-notice

View file

@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.matrix.android.api.session.pushers.Pusher import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
@ -94,6 +95,11 @@ class RxSession(private val session: Session) {
return session.getPagedUsersLive(filter, excludedUserIds).asObservable() return session.getPagedUsersLive(filter, excludedUserIds).asObservable()
} }
fun liveThreePIds(refreshData: Boolean): Observable<List<ThreePid>> {
return session.getThreePidsLive(refreshData).asObservable()
.startWithCallable { session.getThreePids() }
}
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder { fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
session.createRoom(roomParams, it) session.createRoom(roomParams, it)
} }

View file

@ -158,6 +158,9 @@ dependencies {
// Bus // Bus
implementation 'org.greenrobot:eventbus:3.1.1' implementation 'org.greenrobot:eventbus:3.1.1'
// Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0' debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0' releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0' androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'

View file

@ -28,10 +28,10 @@ import im.vector.matrix.android.api.auth.data.LoginFlowResult
import im.vector.matrix.android.api.auth.registration.RegistrationResult import im.vector.matrix.android.api.auth.registration.RegistrationResult
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.LocalEcho
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
@ -88,7 +88,8 @@ class CommonTestHelper(context: Context) {
fun syncSession(session: Session) { fun syncSession(session: Session) {
val lock = CountDownLatch(1) val lock = CountDownLatch(1)
session.open() GlobalScope.launch(Dispatchers.Main) { session.open() }
session.startSync(true) session.startSync(true)
val syncLiveData = runBlocking(Dispatchers.Main) { val syncLiveData = runBlocking(Dispatchers.Main) {
@ -116,7 +117,7 @@ class CommonTestHelper(context: Context) {
*/ */
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> { fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> {
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages) val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
val latch = CountDownLatch(nbOfMessages) val latch = CountDownLatch(1)
val timelineListener = object : Timeline.Listener { val timelineListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) { override fun onTimelineFailure(throwable: Throwable) {
} }
@ -127,7 +128,7 @@ class CommonTestHelper(context: Context) {
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) { override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
val newMessages = snapshot val newMessages = snapshot
.filter { LocalEcho.isLocalEchoId(it.eventId).not() } .filter { it.root.sendState == SendState.SYNCED }
.filter { it.root.getClearType() == EventType.MESSAGE } .filter { it.root.getClearType() == EventType.MESSAGE }
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true } .filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
@ -143,7 +144,8 @@ class CommonTestHelper(context: Context) {
for (i in 0 until nbOfMessages) { for (i in 0 until nbOfMessages) {
room.sendTextMessage(message + " #" + (i + 1)) room.sendTextMessage(message + " #" + (i + 1))
} }
await(latch) // Wait 3 second more per message
await(latch, timeout = TestConstants.timeOutMillis + 3_000L * nbOfMessages)
timeline.removeListener(timelineListener) timeline.removeListener(timelineListener)
timeline.dispose() timeline.dispose()
@ -291,6 +293,24 @@ class CommonTestHelper(context: Context) {
return requestFailure!! return requestFailure!!
} }
fun createEventListener(latch: CountDownLatch, predicate: (List<TimelineEvent>) -> Boolean): Timeline.Listener {
return object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) {
// noop
}
override fun onNewTimelineEvents(eventIds: List<String>) {
// noop
}
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
if (predicate(snapshot)) {
latch.countDown()
}
}
}
}
/** /**
* Await for a latch and ensure the result is true * Await for a latch and ensure the result is true
* *
@ -349,3 +369,13 @@ class CommonTestHelper(context: Context) {
session.close() session.close()
} }
} }
fun List<TimelineEvent>.checkSendOrder(baseTextMessage: String, numberOfMessages: Int, startIndex: Int): Boolean {
return drop(startIndex)
.take(numberOfMessages)
.foldRightIndexed(true) { index, timelineEvent, acc ->
val body = timelineEvent.root.content.toModel<MessageContent>()?.body
val currentMessageSuffix = numberOfMessages - index
acc && (body == null || body.startsWith(baseTextMessage) && body.endsWith("#$currentMessageSuffix"))
}
}

View file

@ -53,17 +53,19 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
/** /**
* @return alice session * @return alice session
*/ */
fun doE2ETestWithAliceInARoom(): CryptoTestData { fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
val roomId = mTestHelper.doSync<String> { val roomId = mTestHelper.doSync<String> {
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it) aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it)
} }
val room = aliceSession.getRoom(roomId)!! if (encryptedRoom) {
val room = aliceSession.getRoom(roomId)!!
mTestHelper.doSync<Unit> { mTestHelper.doSync<Unit> {
room.enableEncryption(callback = it) room.enableEncryption(callback = it)
}
} }
return CryptoTestData(aliceSession, roomId) return CryptoTestData(aliceSession, roomId)
@ -72,8 +74,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
/** /**
* @return alice and bob sessions * @return alice and bob sessions
*/ */
fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData { fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData {
val cryptoTestData = doE2ETestWithAliceInARoom() val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceRoomId = cryptoTestData.roomId val aliceRoomId = cryptoTestData.roomId
@ -246,7 +248,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
assertNotNull(eventWireContent.get("session_id")) assertNotNull(eventWireContent.get("session_id"))
assertNotNull(eventWireContent.get("sender_key")) assertNotNull(eventWireContent.get("sender_key"))
assertEquals(senderSession.sessionParams.credentials.deviceId, eventWireContent.get("device_id")) assertEquals(senderSession.sessionParams.deviceId, eventWireContent.get("device_id"))
assertNotNull(event.eventId) assertNotNull(event.eventId)
assertEquals(roomId, event.roomId) assertEquals(roomId, event.roomId)

View file

@ -122,7 +122,7 @@ class XSigningTest : InstrumentedTest {
// We will want to test that in alice POV, this new device would be trusted by cross signing // We will want to test that in alice POV, this new device would be trusted by cross signing
val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true)) val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true))
val bobSecondDeviceId = bobSession2.sessionParams.credentials.deviceId!! val bobSecondDeviceId = bobSession2.sessionParams.deviceId!!
// Check that bob first session sees the new login // Check that bob first session sees the new login
val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {

View file

@ -148,7 +148,7 @@ class KeyShareTests : InstrumentedTest {
// Mark the device as trusted // Mark the device as trusted
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId, aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
aliceSession2.sessionParams.credentials.deviceId ?: "") aliceSession2.sessionParams.deviceId ?: "")
// Re request // Re request
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root) aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
@ -253,12 +253,12 @@ class KeyShareTests : InstrumentedTest {
}) })
val txId: String = "m.testVerif12" val txId: String = "m.testVerif12"
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.credentials.deviceId aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
?: "", txId) ?: "", txId)
mTestHelper.waitWithLatch { latch -> mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) { mTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.credentials.deviceId ?: "")?.isVerified == true aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
} }
} }

View file

@ -835,7 +835,7 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(signature.valid) assertTrue(signature.valid)
assertNotNull(signature.device) assertNotNull(signature.device)
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId) assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.credentials.deviceId) assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(mTestHelper) cryptoTestData.cleanUp(mTestHelper)
@ -997,7 +997,7 @@ class KeysBackupTest : InstrumentedTest {
keysBackup.backupAllGroupSessions(null, it) keysBackup.backupAllGroupSessions(null, it)
} }
val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!! val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId!!
val oldKeyBackupVersion = keysBackup.currentBackupVersion val oldKeyBackupVersion = keysBackup.currentBackupVersion
val aliceUserId = cryptoTestData.firstSession.myUserId val aliceUserId = cryptoTestData.firstSession.myUserId

View file

@ -468,14 +468,19 @@ class SASTest : InstrumentedTest {
val aliceSASLatch = CountDownLatch(1) val aliceSASLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
var matchOnce = true
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as OutgoingSasVerificationTransaction).uxState val uxState = (tx as OutgoingSasVerificationTransaction).uxState
Log.v("TEST", "== aliceState ${uxState.name}")
when (uxState) { when (uxState) {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> { OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
tx.userHasVerifiedShortCode() tx.userHasVerifiedShortCode()
} }
OutgoingSasVerificationTransaction.UxState.VERIFIED -> { OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
aliceSASLatch.countDown() if (matchOnce) {
matchOnce = false
aliceSASLatch.countDown()
}
} }
else -> Unit else -> Unit
} }
@ -485,14 +490,23 @@ class SASTest : InstrumentedTest {
val bobSASLatch = CountDownLatch(1) val bobSASLatch = CountDownLatch(1)
val bobListener = object : VerificationService.Listener { val bobListener = object : VerificationService.Listener {
var acceptOnce = true
var matchOnce = true
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as IncomingSasVerificationTransaction).uxState val uxState = (tx as IncomingSasVerificationTransaction).uxState
Log.v("TEST", "== bobState ${uxState.name}")
when (uxState) { when (uxState) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
tx.performAccept() if (acceptOnce) {
acceptOnce = false
tx.performAccept()
}
} }
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
tx.userHasVerifiedShortCode() if (matchOnce) {
matchOnce = false
tx.userHasVerifiedShortCode()
}
} }
IncomingSasVerificationTransaction.UxState.VERIFIED -> { IncomingSasVerificationTransaction.UxState.VERIFIED -> {
bobSASLatch.countDown() bobSASLatch.countDown()
@ -579,7 +593,7 @@ class SASTest : InstrumentedTest {
requestID!!, requestID!!,
cryptoTestData.roomId, cryptoTestData.roomId,
bobSession.myUserId, bobSession.myUserId,
bobSession.sessionParams.credentials.deviceId!!, bobSession.sessionParams.deviceId!!,
null) null)
bobVerificationService.beginKeyVerificationInDMs( bobVerificationService.beginKeyVerificationInDMs(
@ -587,7 +601,7 @@ class SASTest : InstrumentedTest {
requestID!!, requestID!!,
cryptoTestData.roomId, cryptoTestData.roomId,
aliceSession.myUserId, aliceSession.myUserId,
aliceSession.sessionParams.credentials.deviceId!!, aliceSession.sessionParams.deviceId!!,
null) null)
// we should reach SHOW SAS on both // we should reach SHOW SAS on both

View file

@ -0,0 +1,183 @@
/*
* 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.matrix.android.session.room.timeline
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.extensions.orFalse
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.common.checkSendOrder
import org.amshove.kluent.shouldBeFalse
import org.amshove.kluent.shouldBeTrue
import org.junit.Assert.assertTrue
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters
import timber.log.Timber
import java.util.concurrent.CountDownLatch
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class TimelineBackToPreviousLastForwardTest : InstrumentedTest {
private val commonTestHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
/**
* This test ensure that if we have a chunk in the timeline which is due to a sync, and we click to permalink of an
* even contained in a previous lastForward chunk, we will be able to go back to the live
*/
@Test
fun backToPreviousLastForwardTest() {
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
val aliceRoomId = cryptoTestData.roomId
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
bobSession.cryptoService().setWarnOnUnknownDevices(false)
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30))
bobTimeline.start()
var roomCreationEventId: String? = null
run {
val lock = CountDownLatch(1)
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root}")
}
roomCreationEventId = snapshot.lastOrNull()?.root?.eventId
// Ok, we have the 8 first messages of the initial sync (room creation and bob join event)
snapshot.size == 8
}
bobTimeline.addListener(eventsListener)
commonTestHelper.await(lock)
bobTimeline.removeAllListeners()
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
}
// Bob stop to sync
bobSession.stopSync()
val messageRoot = "First messages from Alice"
// Alice sends 30 messages
commonTestHelper.sendTextMessage(
roomFromAlicePOV,
messageRoot,
30)
// Bob start to sync
bobSession.startSync(true)
run {
val lock = CountDownLatch(1)
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root}")
}
// Ok, we have the 10 last messages from Alice.
snapshot.size == 10
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith(messageRoot).orFalse() }
}
bobTimeline.addListener(eventsListener)
commonTestHelper.await(lock)
bobTimeline.removeAllListeners()
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
}
// Bob navigate to the first event (room creation event), so inside the previous last forward chunk
run {
val lock = CountDownLatch(1)
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root}")
}
// The event is in db, so it is fetch and auto pagination occurs, half of the number of events we have for this chunk (?)
snapshot.size == 4
}
bobTimeline.addListener(eventsListener)
// Restart the timeline to the first sent event, which is already in the database, so pagination should start automatically
assertTrue(roomFromBobPOV.getTimeLineEvent(roomCreationEventId!!) != null)
bobTimeline.restartWithEventId(roomCreationEventId)
commonTestHelper.await(lock)
bobTimeline.removeAllListeners()
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
}
// Bob scroll to the future
run {
val lock = CountDownLatch(1)
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root}")
}
// Bob can see the first event of the room (so Back pagination has worked)
snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE
// 8 for room creation item, and 30 for the forward pagination
&& snapshot.size == 38
&& snapshot.checkSendOrder(messageRoot, 30, 0)
}
bobTimeline.addListener(eventsListener)
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
commonTestHelper.await(lock)
bobTimeline.removeAllListeners()
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
}
bobTimeline.dispose()
cryptoTestData.cleanUp(commonTestHelper)
}
}

View file

@ -0,0 +1,190 @@
/*
* 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.matrix.android.session.room.timeline
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.extensions.orFalse
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.common.checkSendOrder
import org.amshove.kluent.shouldBeFalse
import org.amshove.kluent.shouldBeTrue
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters
import timber.log.Timber
import java.util.concurrent.CountDownLatch
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class TimelineForwardPaginationTest : InstrumentedTest {
private val commonTestHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
/**
* This test ensure that if we click to permalink, we will be able to go back to the live
*/
@Test
fun forwardPaginationTest() {
val numberOfMessagesToSend = 90
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
val aliceSession = cryptoTestData.firstSession
val aliceRoomId = cryptoTestData.roomId
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
// Alice sends X messages
val message = "Message from Alice"
val sentMessages = commonTestHelper.sendTextMessage(
roomFromAlicePOV,
message,
numberOfMessagesToSend)
// Alice clear the cache
commonTestHelper.doSync<Unit> {
aliceSession.clearCache(it)
}
// And restarts the sync
aliceSession.startSync(true)
val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30))
aliceTimeline.start()
// Alice sees the 10 last message of the room, and can only navigate BACKWARD
run {
val lock = CountDownLatch(1)
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root.content}")
}
// Ok, we have the 10 last messages of the initial sync
snapshot.size == 10
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith(message).orFalse() }
}
// Open the timeline at last sent message
aliceTimeline.addListener(eventsListener)
commonTestHelper.await(lock)
aliceTimeline.removeAllListeners()
aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
}
// Alice navigates to the first message of the room, which is not in its database. A GET /context is performed
// Then she can paginate BACKWARD and FORWARD
run {
val lock = CountDownLatch(1)
val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root.content}")
}
// The event is not in db, so it is fetch alone
snapshot.size == 1
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith("Message from Alice").orFalse() }
}
aliceTimeline.addListener(aliceEventsListener)
// Restart the timeline to the first sent event
aliceTimeline.restartWithEventId(sentMessages.last().eventId)
commonTestHelper.await(lock)
aliceTimeline.removeAllListeners()
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
}
// Alice paginates BACKWARD and FORWARD of 50 events each
// Then she can only navigate FORWARD
run {
val lock = CountDownLatch(1)
val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root.content}")
}
// Alice can see the first event of the room (so Back pagination has worked)
snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE
// 6 for room creation item (backward pagination), 1 for the context, and 50 for the forward pagination
&& snapshot.size == 6 + 1 + 50
}
aliceTimeline.addListener(aliceEventsListener)
// Restart the timeline to the first sent event
// We ask to load event backward and forward
aliceTimeline.paginate(Timeline.Direction.BACKWARDS, 50)
aliceTimeline.paginate(Timeline.Direction.FORWARDS, 50)
commonTestHelper.await(lock)
aliceTimeline.removeAllListeners()
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
}
// Alice paginates once again FORWARD for 50 events
// All the timeline is retrieved, she cannot paginate anymore in both direction
run {
val lock = CountDownLatch(1)
val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root.content}")
}
// 6 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room)
snapshot.size == 6 + numberOfMessagesToSend
&& snapshot.checkSendOrder(message, numberOfMessagesToSend, 0)
}
aliceTimeline.addListener(aliceEventsListener)
// Ask for a forward pagination
aliceTimeline.paginate(Timeline.Direction.FORWARDS, 50)
commonTestHelper.await(lock)
aliceTimeline.removeAllListeners()
// The timeline is fully loaded
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
}
aliceTimeline.dispose()
cryptoTestData.cleanUp(commonTestHelper)
}
}

View file

@ -0,0 +1,241 @@
/*
* 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.matrix.android.session.room.timeline
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.extensions.orFalse
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.common.checkSendOrder
import org.amshove.kluent.shouldBeFalse
import org.amshove.kluent.shouldBeTrue
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters
import timber.log.Timber
import java.util.concurrent.CountDownLatch
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class TimelinePreviousLastForwardTest : InstrumentedTest {
private val commonTestHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
/**
* This test ensure that if we have a chunk in the timeline which is due to a sync, and we click to permalink, we will be able to go back to the live
*/
@Test
fun previousLastForwardTest() {
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
val aliceRoomId = cryptoTestData.roomId
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
bobSession.cryptoService().setWarnOnUnknownDevices(false)
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30))
bobTimeline.start()
run {
val lock = CountDownLatch(1)
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root}")
}
// Ok, we have the 8 first messages of the initial sync (room creation and bob invite and join events)
snapshot.size == 8
}
bobTimeline.addListener(eventsListener)
commonTestHelper.await(lock)
bobTimeline.removeAllListeners()
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
}
// Bob stop to sync
bobSession.stopSync()
val firstMessage = "First messages from Alice"
// Alice sends 30 messages
val firstMessageFromAliceId = commonTestHelper.sendTextMessage(
roomFromAlicePOV,
firstMessage,
30)
.last()
.eventId
// Bob start to sync
bobSession.startSync(true)
run {
val lock = CountDownLatch(1)
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root}")
}
// Ok, we have the 10 last messages from Alice. This will be our future previous lastForward chunk
snapshot.size == 10
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith(firstMessage).orFalse() }
}
bobTimeline.addListener(eventsListener)
commonTestHelper.await(lock)
bobTimeline.removeAllListeners()
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
}
// Bob stop to sync
bobSession.stopSync()
val secondMessage = "Second messages from Alice"
// Alice sends again 30 messages
commonTestHelper.sendTextMessage(
roomFromAlicePOV,
secondMessage,
30)
// Bob start to sync
bobSession.startSync(true)
run {
val lock = CountDownLatch(1)
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root}")
}
// Ok, we have the 10 last messages from Alice. This will be our future previous lastForward chunk
snapshot.size == 10
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith(secondMessage).orFalse() }
}
bobTimeline.addListener(eventsListener)
commonTestHelper.await(lock)
bobTimeline.removeAllListeners()
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
}
// Bob navigate to the first message sent from Alice
run {
val lock = CountDownLatch(1)
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root}")
}
// The event is not in db, so it is fetch
snapshot.size == 1
}
bobTimeline.addListener(eventsListener)
// Restart the timeline to the first sent event, and paginate in both direction
bobTimeline.restartWithEventId(firstMessageFromAliceId)
bobTimeline.paginate(Timeline.Direction.BACKWARDS, 50)
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
commonTestHelper.await(lock)
bobTimeline.removeAllListeners()
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
}
// Paginate in both direction
run {
val lock = CountDownLatch(1)
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root}")
}
snapshot.size == 8 + 1 + 35
}
bobTimeline.addListener(eventsListener)
// Paginate in both direction
bobTimeline.paginate(Timeline.Direction.BACKWARDS, 50)
// Ensure the chunk in the middle is included in the next pagination
bobTimeline.paginate(Timeline.Direction.FORWARDS, 35)
commonTestHelper.await(lock)
bobTimeline.removeAllListeners()
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
}
// Bob scroll to the future, till the live
run {
val lock = CountDownLatch(1)
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
snapshot.forEach {
Timber.w(" event ${it.root}")
}
// Bob can see the first event of the room (so Back pagination has worked)
snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE
// 8 for room creation item 60 message from Alice
&& snapshot.size == 8 + 60
&& snapshot.checkSendOrder(secondMessage, 30, 0)
&& snapshot.checkSendOrder(firstMessage, 30, 30)
}
bobTimeline.addListener(eventsListener)
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
commonTestHelper.await(lock)
bobTimeline.removeAllListeners()
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
}
bobTimeline.dispose()
cryptoTestData.cleanUp(commonTestHelper)
}
}

View file

@ -23,7 +23,6 @@ import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.auth.AuthenticationService import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.api.crypto.MXCryptoConfig
import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
@ -32,20 +31,10 @@ import im.vector.matrix.android.internal.network.UserAgentHolder
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import org.matrix.olm.OlmManager import org.matrix.olm.OlmManager
import java.io.InputStream import java.io.InputStream
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject import javax.inject.Inject
data class MatrixConfiguration(
val applicationFlavor: String = "Default-application-flavor",
val cryptoConfig: MXCryptoConfig = MXCryptoConfig()
) {
interface Provider {
fun providesMatrixConfiguration(): MatrixConfiguration
}
}
/** /**
* This is the main entry point to the matrix sdk. * This is the main entry point to the matrix sdk.
* To get the singleton instance, use getInstance static method. * To get the singleton instance, use getInstance static method.

View file

@ -0,0 +1,38 @@
/*
* 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.matrix.android.api
import im.vector.matrix.android.api.crypto.MXCryptoConfig
import java.net.Proxy
data class MatrixConfiguration(
val applicationFlavor: String = "Default-application-flavor",
val cryptoConfig: MXCryptoConfig = MXCryptoConfig(),
/**
* Optional proxy to connect to the matrix servers
* You can create one using for instance Proxy(proxyType, InetSocketAddress(hostname, port)
*/
val proxy: Proxy? = null
) {
/**
* Can be implemented by your Application class
*/
interface Provider {
fun providesMatrixConfiguration(): MatrixConfiguration
}
}

View file

@ -20,7 +20,6 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.LoginFlowResult import im.vector.matrix.android.api.auth.data.LoginFlowResult
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.login.LoginWizard import im.vector.matrix.android.api.auth.login.LoginWizard
import im.vector.matrix.android.api.auth.registration.RegistrationWizard import im.vector.matrix.android.api.auth.registration.RegistrationWizard
import im.vector.matrix.android.api.auth.wellknown.WellknownResult import im.vector.matrix.android.api.auth.wellknown.WellknownResult
@ -37,6 +36,11 @@ interface AuthenticationService {
*/ */
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable
/**
* Request the supported login flows for the corresponding sessionId.
*/
fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable
/** /**
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first. * Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
*/ */
@ -74,15 +78,6 @@ interface AuthenticationService {
*/ */
fun getLastAuthenticatedSession(): Session? fun getLastAuthenticatedSession(): Session?
/**
* Get an authenticated session. You should at least call authenticate one time before.
* If you logout, this session will no longer be valid.
*
* @param sessionParams the sessionParams to open with.
* @return the associated session if any, or null
*/
fun getSession(sessionParams: SessionParams): Session?
/** /**
* Create a session after a SSO successful login * Create a session after a SSO successful login
*/ */

View file

@ -21,7 +21,48 @@ package im.vector.matrix.android.api.auth.data
* You don't have to manually instantiate it. * You don't have to manually instantiate it.
*/ */
data class SessionParams( data class SessionParams(
/**
* Please consider using shortcuts instead
*/
val credentials: Credentials, val credentials: Credentials,
/**
* Please consider using shortcuts instead
*/
val homeServerConnectionConfig: HomeServerConnectionConfig, val homeServerConnectionConfig: HomeServerConnectionConfig,
/**
* Set to false if the current token is not valid anymore. Application should not have to use this info.
*/
val isTokenValid: Boolean val isTokenValid: Boolean
) ) {
/*
* Shortcuts. Usually the application should only need to use these shortcuts
*/
/**
* The userId of the session (Ex: "@user:domain.org")
*/
val userId = credentials.userId
/**
* The deviceId of the session (Ex: "ABCDEFGH")
*/
val deviceId = credentials.deviceId
/**
* The current homeserver Url. It can be different that the homeserver url entered
* during login phase, because a redirection may have occurred
*/
val homeServerUrl = homeServerConnectionConfig.homeServerUri.toString()
/**
* The current homeserver host
*/
val homeServerHost = homeServerConnectionConfig.homeServerUri.host
/**
* The default identity server url if any, returned by the homeserver during login phase
*/
val defaultIdentityServerUrl = homeServerConnectionConfig.identityServerUri?.toString()
}

View file

@ -19,11 +19,11 @@ package im.vector.matrix.android.api.crypto
/** /**
* Class to define the parameters used to customize or configure the end-to-end crypto. * Class to define the parameters used to customize or configure the end-to-end crypto.
*/ */
data class MXCryptoConfig( data class MXCryptoConfig constructor(
// Tell whether the encryption of the event content is enabled for the invited members. // Tell whether the encryption of the event content is enabled for the invited members.
// SDK clients can disable this by settings it to false. // SDK clients can disable this by settings it to false.
// Note that the encryption for the invited members will be blocked if the history visibility is "joined". // Note that the encryption for the invited members will be blocked if the history visibility is "joined".
var enableEncryptionForInvitedMembers: Boolean = true, val enableEncryptionForInvitedMembers: Boolean = true,
/** /**
* If set to true, the SDK will automatically ignore room key request (gossiping) * If set to true, the SDK will automatically ignore room key request (gossiping)
@ -31,6 +31,5 @@ data class MXCryptoConfig(
* If set to false, the request will be forwarded to the application layer; in this * If set to false, the request will be forwarded to the application layer; in this
* case the application can decide to prompt the user. * case the application can decide to prompt the user.
*/ */
var discardRoomKeyRequestsFromUntrustedDevices : Boolean = true val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true
) )

View file

@ -39,7 +39,10 @@ data class MatrixError(
// For M_LIMIT_EXCEEDED // For M_LIMIT_EXCEEDED
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null, @Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
// For M_UNKNOWN_TOKEN // For M_UNKNOWN_TOKEN
@Json(name = "soft_logout") val isSoftLogout: Boolean = false @Json(name = "soft_logout") val isSoftLogout: Boolean = false,
// For M_INVALID_PEPPER
// {"error": "pepper does not match 'erZvr'", "lookup_pepper": "pQgMS", "algorithm": "sha256", "errcode": "M_INVALID_PEPPER"}
@Json(name = "lookup_pepper") val newLookupPepper: String? = null
) { ) {
companion object { companion object {
@ -129,6 +132,11 @@ data class MatrixError(
/** (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"
const val M_TERMS_NOT_SIGNED = "M_TERMS_NOT_SIGNED"
// For identity service
const val M_INVALID_PEPPER = "M_INVALID_PEPPER"
// Possible value for "limit_type" // Possible value for "limit_type"
const val LIMIT_TYPE_MAU = "monthly_active_user" const val LIMIT_TYPE_MAU = "monthly_active_user"
} }

View file

@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.file.FileService import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
import im.vector.matrix.android.api.session.identity.IdentityService
import im.vector.matrix.android.api.session.profile.ProfileService import im.vector.matrix.android.api.session.profile.ProfileService
import im.vector.matrix.android.api.session.pushers.PushersService import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.api.session.room.RoomDirectoryService import im.vector.matrix.android.api.session.room.RoomDirectoryService
@ -39,6 +40,7 @@ import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageSer
import im.vector.matrix.android.api.session.signout.SignOutService import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.terms.TermsService
import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.UserService
/** /**
@ -54,6 +56,7 @@ interface Session :
SignOutService, SignOutService,
FilterService, FilterService,
FileService, FileService,
TermsService,
ProfileService, ProfileService,
PushRuleService, PushRuleService,
PushersService, PushersService,
@ -77,7 +80,7 @@ interface Session :
* Useful shortcut to get access to the userId * Useful shortcut to get access to the userId
*/ */
val myUserId: String val myUserId: String
get() = sessionParams.credentials.userId get() = sessionParams.userId
/** /**
* The sessionId * The sessionId
@ -145,6 +148,11 @@ interface Session :
*/ */
fun cryptoService(): CryptoService fun cryptoService(): CryptoService
/**
* Returns the identity service associated with the session
*/
fun identityService(): IdentityService
/** /**
* Add a listener to the session. * Add a listener to the session.
* @param listener the listener to add. * @param listener the listener to add.

View file

@ -220,3 +220,11 @@ fun Event.isImageMessage(): Boolean {
else -> false else -> false
} }
} }
fun Event.isVideoMessage(): Boolean {
return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
MessageType.MSGTYPE_VIDEO -> true
else -> false
}
}

View file

@ -24,7 +24,15 @@ data class HomeServerCapabilities(
/** /**
* Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet * Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet
*/ */
val maxUploadFileSize: Long = MAX_UPLOAD_FILE_SIZE_UNKNOWN val maxUploadFileSize: Long = MAX_UPLOAD_FILE_SIZE_UNKNOWN,
/**
* Last version identity server and binding supported
*/
val lastVersionIdentityServerSupported: Boolean = false,
/**
* Default identity server url, provided in Wellknown
*/
val defaultIdentityServerUrl: String? = null
) { ) {
companion object { companion object {
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L

View file

@ -0,0 +1,22 @@
/*
* 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.matrix.android.api.session.identity
data class FoundThreePid(
val threePid: ThreePid,
val matrixId: String
)

View file

@ -0,0 +1,109 @@
/*
* 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.matrix.android.api.session.identity
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
/**
* Provides access to the identity server configuration and services identity server can provide
*/
interface IdentityService {
/**
* Return the default identity server of the user, which may have been provided at login time by the homeserver,
* or by the Well-known setup of the homeserver
* It may be different from the current configured identity server
*/
fun getDefaultIdentityServer(): String?
/**
* Return the current identity server URL used by this account. Returns null if no identity server is configured.
*/
fun getCurrentIdentityServerUrl(): String?
/**
* Check if the identity server is valid
* See https://matrix.org/docs/spec/identity_service/latest#status-check
* RiotX SDK only supports identity server API v2
*/
fun isValidIdentityServer(url: String, callback: MatrixCallback<Unit>): Cancelable
/**
* Update the identity server url.
* If successful, any previous identity server will be disconnected.
* In case of error, any previous identity server will remain configured.
* @param url the new url.
* @param callback will notify the user if change is successful. The String will be the final url of the identity server.
* The SDK can prepend "https://" for instance.
*/
fun setNewIdentityServer(url: String, callback: MatrixCallback<String>): Cancelable
/**
* Disconnect (logout) from the current identity server
*/
fun disconnect(callback: MatrixCallback<Unit>): Cancelable
/**
* This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid
*/
fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
/**
* This will cancel a pending binding of threePid.
*/
fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
/**
* This will ask the identity server to send an new email or a new SMS to let the user confirm he owns the ThreePid
*/
fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
/**
* Submit the code that the identity server has sent to the user (in email or SMS)
* Once successful, you will have to call [finalizeBindThreePid]
* @param code the code sent to the user
*/
fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable
/**
* This will perform the actual association of ThreePid and Matrix account
*/
fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
/**
* Unbind a threePid
* The request will actually be done on the homeserver
*/
fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
/**
* Search MatrixId of users providing email and phone numbers
*/
fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable
/**
* Get the status of the current user's threePid
* A lookup will be performed, but also pending binding state will be restored
*
* @param threePids the list of threePid the user owns (retrieved form the homeserver)
* @param callback onSuccess will be called with a map of ThreePid -> SharedState
*/
fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable
fun addListener(listener: IdentityServiceListener)
fun removeListener(listener: IdentityServiceListener)
}

View file

@ -0,0 +1,27 @@
/*
* 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.matrix.android.api.session.identity
sealed class IdentityServiceError : Throwable() {
object OutdatedIdentityServer : IdentityServiceError()
object OutdatedHomeServer : IdentityServiceError()
object NoIdentityServerConfigured : IdentityServiceError()
object TermsNotSignedException : IdentityServiceError()
object BulkLookupSha256NotSupported : IdentityServiceError()
object BindingError : IdentityServiceError()
object NoCurrentBindingError : IdentityServiceError()
}

View file

@ -0,0 +1,21 @@
/*
* 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.matrix.android.api.session.identity
interface IdentityServiceListener {
fun onIdentityServerChange()
}

View file

@ -0,0 +1,23 @@
/*
* 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.matrix.android.api.session.identity
enum class SharedState {
SHARED,
NOT_SHARED,
BINDING_IN_PROGRESS
}

View file

@ -0,0 +1,40 @@
/*
* 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.matrix.android.api.session.identity
import com.google.i18n.phonenumbers.NumberParseException
import com.google.i18n.phonenumbers.PhoneNumberUtil
import im.vector.matrix.android.internal.session.profile.ThirdPartyIdentifier
sealed class ThreePid(open val value: String) {
data class Email(val email: String) : ThreePid(email)
data class Msisdn(val msisdn: String) : ThreePid(msisdn)
}
internal fun ThreePid.toMedium(): String {
return when (this) {
is ThreePid.Email -> ThirdPartyIdentifier.MEDIUM_EMAIL
is ThreePid.Msisdn -> ThirdPartyIdentifier.MEDIUM_MSISDN
}
}
@Throws(NumberParseException::class)
internal fun ThreePid.Msisdn.getCountryCode(): String {
return with(PhoneNumberUtil.getInstance()) {
getRegionCodeForCountryCode(parse("+$msisdn", null).countryCode)
}
}

View file

@ -17,7 +17,9 @@
package im.vector.matrix.android.api.session.profile package im.vector.matrix.android.api.session.profile
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
@ -53,4 +55,15 @@ interface ProfileService {
* *
*/ */
fun getProfile(userId: String, matrixCallback: MatrixCallback<JsonDict>): Cancelable fun getProfile(userId: String, matrixCallback: MatrixCallback<JsonDict>): Cancelable
/**
* Get the current user 3Pids
*/
fun getThreePids(): List<ThreePid>
/**
* Get the current user 3Pids Live
* @param refreshData set to true to fetch data from the homeserver
*/
fun getThreePidsLive(refreshData: Boolean): LiveData<List<ThreePid>>
} }

View file

@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.room.send.SendService
import im.vector.matrix.android.api.session.room.state.StateService import im.vector.matrix.android.api.session.room.state.StateService
import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.api.session.room.typing.TypingService import im.vector.matrix.android.api.session.room.typing.TypingService
import im.vector.matrix.android.api.session.room.uploads.UploadsService
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
/** /**
@ -42,6 +43,7 @@ interface Room :
TypingService, TypingService,
MembershipService, MembershipService,
StateService, StateService,
UploadsService,
ReportingService, ReportingService,
RelationService, RelationService,
RoomCryptoService, RoomCryptoService,

View file

@ -0,0 +1,35 @@
/*
* 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.matrix.android.api.session.room.model.message
interface MessageContentWithFormattedBody : MessageContent {
/**
* The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported.
*/
val format: String?
/**
* The formatted version of the body. This is required if format is specified.
*/
val formattedBody: String?
/**
* Get the formattedBody, only if not blank and if the format is equal to "org.matrix.custom.html"
*/
val matrixFormattedBody: String?
get() = formattedBody?.takeIf { it.isNotBlank() && format == MessageFormat.FORMAT_MATRIX_HTML }
}

View file

@ -34,15 +34,15 @@ data class MessageEmoteContent(
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
/** /**
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported. * The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported.
*/ */
@Json(name = "format") val format: String? = null, @Json(name = "format") override val format: String? = null,
/** /**
* The formatted version of the body. This is required if format is specified. * The formatted version of the body. This is required if format is specified.
*/ */
@Json(name = "formatted_body") val formattedBody: String? = null, @Json(name = "formatted_body") override val formattedBody: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null @Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent ) : MessageContentWithFormattedBody

View file

@ -34,15 +34,15 @@ data class MessageNoticeContent(
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
/** /**
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported. * The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported.
*/ */
@Json(name = "format") val format: String? = null, @Json(name = "format") override val format: String? = null,
/** /**
* The formatted version of the body. This is required if format is specified. * The formatted version of the body. This is required if format is specified.
*/ */
@Json(name = "formatted_body") val formattedBody: String? = null, @Json(name = "formatted_body") override val formattedBody: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null @Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent ) : MessageContentWithFormattedBody

View file

@ -34,15 +34,15 @@ data class MessageTextContent(
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
/** /**
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported. * The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported.
*/ */
@Json(name = "format") val format: String? = null, @Json(name = "format") override val format: String? = null,
/** /**
* The formatted version of the body. This is required if format is specified. * The formatted version of the body. This is required if format is specified.
*/ */
@Json(name = "formatted_body") val formattedBody: String? = null, @Json(name = "formatted_body") override val formattedBody: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null @Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent ) : MessageContentWithFormattedBody

View file

@ -0,0 +1,34 @@
/*
* 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.matrix.android.api.session.room.sender
data class SenderInfo(
val userId: String,
/**
* Consider using [disambiguatedDisplayName]
*/
val displayName: String?,
val isUniqueDisplayName: Boolean,
val avatarUrl: String?
) {
val disambiguatedDisplayName: String
get() = when {
displayName.isNullOrBlank() -> userId
isUniqueDisplayName -> displayName
else -> "$displayName ($userId)"
}
}

View file

@ -58,7 +58,7 @@ interface Timeline {
/** /**
* Check if the timeline can be enriched by paginating. * Check if the timeline can be enriched by paginating.
* @param the direction to check in * @param direction the direction to check in
* @return true if timeline can be enriched * @return true if timeline can be enriched
*/ */
fun hasMoreToLoad(direction: Direction): Boolean fun hasMoreToLoad(direction: Direction): Boolean

View file

@ -16,6 +16,7 @@
package im.vector.matrix.android.api.session.room.timeline package im.vector.matrix.android.api.session.room.timeline
import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.RelationType
@ -25,6 +26,7 @@ import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent
import im.vector.matrix.android.api.session.room.model.message.isReply import im.vector.matrix.android.api.session.room.model.message.isReply
import im.vector.matrix.android.api.session.room.sender.SenderInfo
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
@ -38,13 +40,17 @@ data class TimelineEvent(
val localId: Long, val localId: Long,
val eventId: String, val eventId: String,
val displayIndex: Int, val displayIndex: Int,
val senderName: String?, val senderInfo: SenderInfo,
val isUniqueDisplayName: Boolean,
val senderAvatar: String?,
val annotations: EventAnnotationsSummary? = null, val annotations: EventAnnotationsSummary? = null,
val readReceipts: List<ReadReceipt> = emptyList() val readReceipts: List<ReadReceipt> = emptyList()
) { ) {
init {
if (BuildConfig.DEBUG) {
assert(eventId == root.eventId)
}
}
val metadata = HashMap<String, Any>() val metadata = HashMap<String, Any>()
/** /**
@ -62,14 +68,6 @@ data class TimelineEvent(
} }
} }
fun getDisambiguatedDisplayName(): String {
return when {
senderName.isNullOrBlank() -> root.senderId ?: ""
isUniqueDisplayName -> senderName
else -> "$senderName (${root.senderId})"
}
}
/** /**
* Get the metadata associated with a key. * Get the metadata associated with a key.
* @param key the key to get the metadata * @param key the key to get the metadata

View file

@ -28,6 +28,10 @@ data class TimelineSettings(
* A flag to filter edit events * A flag to filter edit events
*/ */
val filterEdits: Boolean = false, val filterEdits: Boolean = false,
/**
* A flag to filter redacted events
*/
val filterRedacted: Boolean = false,
/** /**
* A flag to filter by types. It should be used with [allowedTypes] field * A flag to filter by types. It should be used with [allowedTypes] field
*/ */

View file

@ -0,0 +1,26 @@
/*
* 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.matrix.android.api.session.room.uploads
data class GetUploadsResult(
// List of fetched Events, most recent first
val uploadEvents: List<UploadEvent>,
// token to get more events
val nextToken: String,
// True if there are more event to load
val hasMore: Boolean
)

View file

@ -0,0 +1,32 @@
/*
* 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.matrix.android.api.session.room.uploads
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent
import im.vector.matrix.android.api.session.room.sender.SenderInfo
/**
* Wrapper around on Event.
* Similar to [im.vector.matrix.android.api.session.room.timeline.TimelineEvent], contains an Event with extra useful data
*/
data class UploadEvent(
val root: Event,
val eventId: String,
val contentWithAttachmentContent: MessageWithAttachmentContent,
val senderInfo: SenderInfo
)

View file

@ -0,0 +1,35 @@
/*
* 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.matrix.android.api.session.room.uploads
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
/**
* This interface defines methods to get event with uploads (= attachments) sent to a room. It's implemented at the room level.
*/
interface UploadsService {
/**
* Get a list of events containing URL sent to a room, from most recent to oldest one
* @param numberOfEvents the expected number of events to retrieve. The result can contain less events.
* @param since token to get next page, or null to get the first page
*/
fun getUploads(numberOfEvents: Int,
since: String?,
callback: MatrixCallback<GetUploadsResult>): Cancelable
}

View file

@ -0,0 +1,24 @@
/*
* 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.matrix.android.api.session.terms
import im.vector.matrix.android.internal.session.terms.TermsResponse
data class GetTermsResponse(
val serverResponse: TermsResponse,
val alreadyAcceptedTermUrls: Set<String>
)

View file

@ -0,0 +1,37 @@
/*
* 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.matrix.android.api.session.terms
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
interface TermsService {
enum class ServiceType {
IntegrationManager,
IdentityService
}
fun getTerms(serviceType: ServiceType,
baseUrl: String,
callback: MatrixCallback<GetTermsResponse>): Cancelable
fun agreeToTerms(serviceType: ServiceType,
baseUrl: String,
agreedUrls: List<String>,
token: String?,
callback: MatrixCallback<Unit>): Cancelable
}

View file

@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.session.room.sender.SenderInfo
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import java.util.Locale import java.util.Locale
@ -154,3 +155,5 @@ fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlia
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl) fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl)
fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)

View file

@ -25,16 +25,15 @@ import im.vector.matrix.android.internal.auth.db.AuthRealmMigration
import im.vector.matrix.android.internal.auth.db.AuthRealmModule import im.vector.matrix.android.internal.auth.db.AuthRealmModule
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
import im.vector.matrix.android.internal.auth.wellknown.DefaultDirectLoginTask import im.vector.matrix.android.internal.auth.login.DefaultDirectLoginTask
import im.vector.matrix.android.internal.auth.wellknown.DefaultGetWellknownTask import im.vector.matrix.android.internal.auth.login.DirectLoginTask
import im.vector.matrix.android.internal.auth.wellknown.DirectLoginTask
import im.vector.matrix.android.internal.auth.wellknown.GetWellknownTask
import im.vector.matrix.android.internal.database.RealmKeysUtils import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.di.AuthDatabase import im.vector.matrix.android.internal.di.AuthDatabase
import im.vector.matrix.android.internal.wellknown.WellknownModule
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import java.io.File import java.io.File
@Module @Module(includes = [WellknownModule::class])
internal abstract class AuthModule { internal abstract class AuthModule {
@Module @Module
@ -74,9 +73,6 @@ internal abstract class AuthModule {
@Binds @Binds
abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator
@Binds
abstract fun bindGetWellknownTask(task: DefaultGetWellknownTask): GetWellknownTask
@Binds @Binds
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
} }

View file

@ -23,7 +23,6 @@ import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.LoginFlowResult import im.vector.matrix.android.api.auth.data.LoginFlowResult
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.data.Versions import im.vector.matrix.android.api.auth.data.Versions
import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
import im.vector.matrix.android.api.auth.data.isSupportedBySdk import im.vector.matrix.android.api.auth.data.isSupportedBySdk
@ -33,14 +32,14 @@ import im.vector.matrix.android.api.auth.wellknown.WellknownResult
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.NoOpCancellable
import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
import im.vector.matrix.android.internal.auth.data.RiotConfig import im.vector.matrix.android.internal.auth.data.RiotConfig
import im.vector.matrix.android.internal.auth.db.PendingSessionData import im.vector.matrix.android.internal.auth.db.PendingSessionData
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
import im.vector.matrix.android.internal.auth.login.DirectLoginTask
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
import im.vector.matrix.android.internal.auth.wellknown.DirectLoginTask
import im.vector.matrix.android.internal.auth.wellknown.GetWellknownTask
import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
@ -50,7 +49,7 @@ import im.vector.matrix.android.internal.task.launchToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.exhaustive import im.vector.matrix.android.internal.util.exhaustive
import im.vector.matrix.android.internal.util.toCancelable import im.vector.matrix.android.internal.util.toCancelable
import kotlinx.coroutines.GlobalScope import im.vector.matrix.android.internal.wellknown.GetWellknownTask
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -87,14 +86,21 @@ internal class DefaultAuthenticationService @Inject constructor(
} }
} }
override fun getSession(sessionParams: SessionParams): Session? { override fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable {
return sessionManager.getOrCreateSession(sessionParams) val homeServerConnectionConfig = sessionParamsStore.get(sessionId)?.homeServerConnectionConfig
return if (homeServerConnectionConfig == null) {
callback.onFailure(IllegalStateException("Session not found"))
NoOpCancellable
} else {
getLoginFlow(homeServerConnectionConfig, callback)
}
} }
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable { override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
pendingSessionData = null pendingSessionData = null
return GlobalScope.launch(coroutineDispatchers.main) { return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
pendingSessionStore.delete() pendingSessionStore.delete()
val result = runCatching { val result = runCatching {
@ -246,7 +252,8 @@ internal class DefaultAuthenticationService @Inject constructor(
retrofitFactory, retrofitFactory,
coroutineDispatchers, coroutineDispatchers,
sessionCreator, sessionCreator,
pendingSessionStore pendingSessionStore,
taskExecutor.executorScope
).also { ).also {
currentRegistrationWizard = it currentRegistrationWizard = it
} }
@ -266,7 +273,8 @@ internal class DefaultAuthenticationService @Inject constructor(
retrofitFactory, retrofitFactory,
coroutineDispatchers, coroutineDispatchers,
sessionCreator, sessionCreator,
pendingSessionStore pendingSessionStore,
taskExecutor.executorScope
).also { ).also {
currentLoginWizard = it currentLoginWizard = it
} }
@ -283,7 +291,7 @@ internal class DefaultAuthenticationService @Inject constructor(
pendingSessionData = pendingSessionData?.homeServerConnectionConfig pendingSessionData = pendingSessionData?.homeServerConnectionConfig
?.let { PendingSessionData(it) } ?.let { PendingSessionData(it) }
.also { .also {
GlobalScope.launch(coroutineDispatchers.main) { taskExecutor.executorScope.launch(coroutineDispatchers.main) {
if (it == null) { if (it == null) {
// Should not happen // Should not happen
pendingSessionStore.delete() pendingSessionStore.delete()
@ -300,7 +308,7 @@ internal class DefaultAuthenticationService @Inject constructor(
pendingSessionData = null pendingSessionData = null
GlobalScope.launch(coroutineDispatchers.main) { taskExecutor.executorScope.launch(coroutineDispatchers.main) {
pendingSessionStore.delete() pendingSessionStore.delete()
} }
} }
@ -308,7 +316,7 @@ internal class DefaultAuthenticationService @Inject constructor(
override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig, override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials, credentials: Credentials,
callback: MatrixCallback<Session>): Cancelable { callback: MatrixCallback<Session>): Cancelable {
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
createSessionFromSso(credentials, homeServerConnectionConfig) createSessionFromSso(credentials, homeServerConnectionConfig)
} }
} }

View file

@ -51,7 +51,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
} }
return SessionParamsEntity( return SessionParamsEntity(
sessionParams.credentials.sessionId(), sessionParams.credentials.sessionId(),
sessionParams.credentials.userId, sessionParams.userId,
credentialsJson, credentialsJson,
homeServerConnectionConfigJson, homeServerConnectionConfigJson,
sessionParams.isTokenValid) sessionParams.isTokenValid)

View file

@ -38,7 +38,7 @@ import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.launchToCallback import im.vector.matrix.android.internal.task.launchToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -47,7 +47,8 @@ internal class DefaultLoginWizard(
retrofitFactory: RetrofitFactory, retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionCreator: SessionCreator, private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore private val pendingSessionStore: PendingSessionStore,
private val coroutineScope: CoroutineScope
) : LoginWizard { ) : LoginWizard {
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here") private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
@ -59,7 +60,7 @@ internal class DefaultLoginWizard(
password: String, password: String,
deviceName: String, deviceName: String,
callback: MatrixCallback<Session>): Cancelable { callback: MatrixCallback<Session>): Cancelable {
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
loginInternal(login, password, deviceName) loginInternal(login, password, deviceName)
} }
} }
@ -80,7 +81,7 @@ internal class DefaultLoginWizard(
} }
override fun resetPassword(email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable { override fun resetPassword(email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
resetPasswordInternal(email, newPassword) resetPasswordInternal(email, newPassword)
} }
} }
@ -108,7 +109,7 @@ internal class DefaultLoginWizard(
callback.onFailure(IllegalStateException("developer error, no reset password in progress")) callback.onFailure(IllegalStateException("developer error, no reset password in progress"))
return NoOpCancellable return NoOpCancellable
} }
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
resetPasswordMailConfirmedInternal(safeResetPasswordData) resetPasswordMailConfirmedInternal(safeResetPasswordData)
} }
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.auth.wellknown package im.vector.matrix.android.internal.auth.login
import dagger.Lazy import dagger.Lazy
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials

View file

@ -33,7 +33,7 @@ import im.vector.matrix.android.internal.auth.db.PendingSessionData
import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.task.launchToCallback import im.vector.matrix.android.internal.task.launchToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -45,7 +45,8 @@ internal class DefaultRegistrationWizard(
private val retrofitFactory: RetrofitFactory, private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionCreator: SessionCreator, private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore private val pendingSessionStore: PendingSessionStore,
private val coroutineScope: CoroutineScope
) : RegistrationWizard { ) : RegistrationWizard {
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here") private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
@ -72,7 +73,7 @@ internal class DefaultRegistrationWizard(
override fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable { override fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable {
val params = RegistrationParams() val params = RegistrationParams()
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params) performRegistrationRequest(params)
} }
} }
@ -86,7 +87,7 @@ internal class DefaultRegistrationWizard(
password = password, password = password,
initialDeviceDisplayName = initialDeviceDisplayName initialDeviceDisplayName = initialDeviceDisplayName
) )
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params) performRegistrationRequest(params)
.also { .also {
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true) pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
@ -101,7 +102,7 @@ internal class DefaultRegistrationWizard(
return NoOpCancellable return NoOpCancellable
} }
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response)) val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params) performRegistrationRequest(params)
} }
} }
@ -112,13 +113,13 @@ internal class DefaultRegistrationWizard(
return NoOpCancellable return NoOpCancellable
} }
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession)) val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params) performRegistrationRequest(params)
} }
} }
override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable { override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable {
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
pendingSessionData = pendingSessionData.copy(currentThreePidData = null) pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
.also { pendingSessionStore.savePendingSessionData(it) } .also { pendingSessionStore.savePendingSessionData(it) }
@ -131,7 +132,7 @@ internal class DefaultRegistrationWizard(
callback.onFailure(IllegalStateException("developer error, call createAccount() method first")) callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable return NoOpCancellable
} }
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
sendThreePid(safeCurrentThreePid) sendThreePid(safeCurrentThreePid)
} }
} }
@ -177,13 +178,13 @@ internal class DefaultRegistrationWizard(
callback.onFailure(IllegalStateException("developer error, no pending three pid")) callback.onFailure(IllegalStateException("developer error, no pending three pid"))
return NoOpCancellable return NoOpCancellable
} }
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(safeParam, delayMillis) performRegistrationRequest(safeParam, delayMillis)
} }
} }
override fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable { override fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
validateThreePid(code) validateThreePid(code)
} }
} }
@ -199,7 +200,7 @@ internal class DefaultRegistrationWizard(
code = code code = code
) )
val validationResponse = validateCodeTask.execute(ValidateCodeTask.Params(url, validationBody)) val validationResponse = validateCodeTask.execute(ValidateCodeTask.Params(url, validationBody))
if (validationResponse.success == true) { if (validationResponse.isSuccess()) {
// The entered code is correct // The entered code is correct
// Same than validate email // Same than validate email
return performRegistrationRequest(registrationParams, 3_000) return performRegistrationRequest(registrationParams, 3_000)
@ -214,7 +215,7 @@ internal class DefaultRegistrationWizard(
callback.onFailure(IllegalStateException("developer error, call createAccount() method first")) callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable return NoOpCancellable
} }
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) { return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession)) val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
performRegistrationRequest(params) performRegistrationRequest(params)
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.matrix.androidsdk.rest.model.login package im.vector.matrix.android.internal.auth.registration
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize

View file

@ -76,7 +76,7 @@ fun RegistrationFlowResponse.toFlowResult(): FlowResult {
this.flows?.forEach { it.stages?.mapTo(allFlowTypes) { type -> type } } this.flows?.forEach { it.stages?.mapTo(allFlowTypes) { type -> type } }
allFlowTypes.forEach { type -> allFlowTypes.forEach { type ->
val isMandatory = flows?.all { type in it.stages ?: emptyList() } == true val isMandatory = flows?.all { type in it.stages.orEmpty() } == true
val stage = when (type) { val stage = when (type) {
LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha(isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String) LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha(isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String)
@ -88,7 +88,7 @@ fun RegistrationFlowResponse.toFlowResult(): FlowResult {
else -> Stage.Other(isMandatory, type, (params?.get(type) as? Map<*, *>)) else -> Stage.Other(isMandatory, type, (params?.get(type) as? Map<*, *>))
} }
if (type in completedStages ?: emptyList()) { if (type in completedStages.orEmpty()) {
completedStage.add(stage) completedStage.add(stage)
} else { } else {
missingStage.add(stage) missingStage.add(stage)

View file

@ -18,9 +18,12 @@ package im.vector.matrix.android.internal.auth.registration
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.extensions.orFalse
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class SuccessResult( data class SuccessResult(
@Json(name = "success") @Json(name = "success")
val success: Boolean? val success: Boolean?
) ) {
fun isSuccess() = success.orFalse()
}

View file

@ -262,7 +262,7 @@ internal class DefaultCryptoService @Inject constructor(
override fun onSuccess(data: DevicesListResponse) { override fun onSuccess(data: DevicesListResponse) {
// Save in local DB // Save in local DB
cryptoStore.saveMyDevicesInfo(data.devices ?: emptyList()) cryptoStore.saveMyDevicesInfo(data.devices.orEmpty())
callback.onSuccess(data) callback.onSuccess(data)
} }
} }
@ -446,7 +446,7 @@ internal class DefaultCryptoService @Inject constructor(
} }
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> { override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
return cryptoStore.getUserDeviceList(userId) ?: emptyList() return cryptoStore.getUserDeviceList(userId).orEmpty()
} }
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> { override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {

View file

@ -137,7 +137,7 @@ internal class OneTimeKeysUploader @Inject constructor(
private suspend fun uploadOneTimeKeys(oneTimeKeys: Map<String, Map<String, String>>?): KeysUploadResponse { private suspend fun uploadOneTimeKeys(oneTimeKeys: Map<String, Map<String, String>>?): KeysUploadResponse {
val oneTimeJson = mutableMapOf<String, Any>() val oneTimeJson = mutableMapOf<String, Any>()
val curve25519Map = oneTimeKeys?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY) ?: emptyMap() val curve25519Map = oneTimeKeys?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty()
curve25519Map.forEach { (key_id, value) -> curve25519Map.forEach { (key_id, value) ->
val k = mutableMapOf<String, Any>() val k = mutableMapOf<String, Any>()

View file

@ -34,7 +34,7 @@ internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val o
suspend fun handle(users: List<String>): MXUsersDevicesMap<MXOlmSessionResult> { suspend fun handle(users: List<String>): MXUsersDevicesMap<MXOlmSessionResult> {
Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users") Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users")
val devicesByUser = users.associateWith { userId -> val devicesByUser = users.associateWith { userId ->
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList() val devices = cryptoStore.getUserDevices(userId)?.values.orEmpty()
devices.filter { devices.filter {
// Don't bother setting up session to ourself // Don't bother setting up session to ourself

View file

@ -103,7 +103,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
?: emptyList() .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

@ -44,7 +44,7 @@ internal class MXOlmEncryption(
ensureSession(userIds) ensureSession(userIds)
val deviceInfos = ArrayList<CryptoDeviceInfo>() val deviceInfos = ArrayList<CryptoDeviceInfo>()
for (userId in userIds) { for (userId in userIds) {
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList() val devices = cryptoStore.getUserDevices(userId)?.values.orEmpty()
for (device in devices) { for (device in devices) {
val key = device.identityKey() val key = device.identityKey()
if (key == olmDevice.deviceCurve25519Key) { if (key == olmDevice.deviceCurve25519Key) {

View file

@ -199,7 +199,7 @@ internal object MXEncryptedAttachments {
.replace('_', '/') .replace('_', '/')
} }
private fun base64ToBase64Url(base64: String): String { internal fun base64ToBase64Url(base64: String): String {
return base64.replace("\n".toRegex(), "") return base64.replace("\n".toRegex(), "")
.replace("\\+".toRegex(), "-") .replace("\\+".toRegex(), "-")
.replace('/', '_') .replace('/', '_')

View file

@ -47,7 +47,7 @@ internal object CryptoInfoMapper {
return CryptoCrossSigningKey( return CryptoCrossSigningKey(
userId = keyInfo.userId, userId = keyInfo.userId,
usages = keyInfo.usages, usages = keyInfo.usages,
keys = keyInfo.keys ?: emptyMap(), keys = keyInfo.keys.orEmpty(),
signatures = keyInfo.signatures, signatures = keyInfo.signatures,
trustLevel = null trustLevel = null
) )

View file

@ -450,7 +450,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
) )
return Transformations.map(liveData) { return Transformations.map(liveData) {
it.firstOrNull() ?: emptyList() it.firstOrNull().orEmpty()
} }
} }
@ -480,7 +480,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
) )
return Transformations.map(liveData) { return Transformations.map(liveData) {
it.firstOrNull() ?: emptyList() it.firstOrNull().orEmpty()
} }
} }

View file

@ -200,6 +200,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
} }
private fun migrateTo3(realm: DynamicRealm) { private fun migrateTo3(realm: DynamicRealm) {
Timber.d("Step 2 -> 3")
Timber.d("Updating CryptoMetadataEntity table") Timber.d("Updating CryptoMetadataEntity table")
realm.schema.get("CryptoMetadataEntity") realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY, String::class.java) ?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY, String::class.java)
@ -207,6 +208,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
} }
private fun migrateTo4(realm: DynamicRealm) { private fun migrateTo4(realm: DynamicRealm) {
Timber.d("Step 3 -> 4")
Timber.d("Updating KeyInfoEntity table") Timber.d("Updating KeyInfoEntity table")
val keyInfoEntities = realm.where("KeyInfoEntity").findAll() val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
try { try {
@ -238,6 +240,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
} }
private fun migrateTo5(realm: DynamicRealm) { private fun migrateTo5(realm: DynamicRealm) {
Timber.d("Step 4 -> 5")
realm.schema.create("MyDeviceLastSeenInfoEntity") realm.schema.create("MyDeviceLastSeenInfoEntity")
.addField(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, String::class.java) .addField(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, String::class.java)
.addPrimaryKey(MyDeviceLastSeenInfoEntityFields.DEVICE_ID) .addPrimaryKey(MyDeviceLastSeenInfoEntityFields.DEVICE_ID)

View file

@ -78,7 +78,7 @@ internal open class OutgoingGossipingRequestEntity(
GossipRequestType.KEY -> { GossipRequestType.KEY -> {
OutgoingRoomKeyRequest( OutgoingRoomKeyRequest(
requestBody = getRequestedKeyInfo(), requestBody = getRequestedKeyInfo(),
recipients = getRecipients() ?: emptyMap(), recipients = getRecipients().orEmpty(),
requestId = requestId ?: "", requestId = requestId ?: "",
state = requestState state = requestState
) )
@ -86,7 +86,7 @@ internal open class OutgoingGossipingRequestEntity(
GossipRequestType.SECRET -> { GossipRequestType.SECRET -> {
OutgoingSecretRequest( OutgoingSecretRequest(
secretName = getRequestedSecretName(), secretName = getRequestedSecretName(),
recipients = getRecipients() ?: emptyMap(), recipients = getRecipients().orEmpty(),
requestId = requestId ?: "", requestId = requestId ?: "",
state = requestState state = requestState
) )

View file

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.tools
import org.matrix.olm.OlmPkDecryption import org.matrix.olm.OlmPkDecryption
import org.matrix.olm.OlmPkEncryption import org.matrix.olm.OlmPkEncryption
import org.matrix.olm.OlmPkSigning import org.matrix.olm.OlmPkSigning
import org.matrix.olm.OlmUtility
fun <T> withOlmEncryption(block: (OlmPkEncryption) -> T): T { fun <T> withOlmEncryption(block: (OlmPkEncryption) -> T): T {
val olmPkEncryption = OlmPkEncryption() val olmPkEncryption = OlmPkEncryption()
@ -46,3 +47,12 @@ fun <T> withOlmSigning(block: (OlmPkSigning) -> T): T {
olmPkSigning.releaseSigning() olmPkSigning.releaseSigning()
} }
} }
fun <T> withOlmUtility(block: (OlmUtility) -> T): T {
val olmUtility = OlmUtility()
try {
return block(olmUtility)
} finally {
olmUtility.releaseUtility()
}
}

View file

@ -198,18 +198,8 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
// using the result as the shared secret. // using the result as the shared secret.
getSAS().setTheirPublicKey(otherKey) getSAS().setTheirPublicKey(otherKey)
// (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function,
// the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of: shortCodeBytes = calculateSASBytes()
// - the string “MATRIX_KEY_VERIFICATION_SAS”,
// - the Matrix ID of the user who sent the m.key.verification.start message,
// - the device ID of the device that sent the m.key.verification.start 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
// - the transaction ID.
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$otherUserId$otherDeviceId$userId$deviceId$transactionId"
// decimal: generate five bytes by using HKDF.
// emoji: generate six bytes by using HKDF.
shortCodeBytes = getSAS().generateShortCode(sasInfo, 6)
if (BuildConfig.LOG_PRIVATE_DATA) { if (BuildConfig.LOG_PRIVATE_DATA) {
Timber.v("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}") Timber.v("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}")
@ -219,6 +209,35 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
state = VerificationTxState.ShortCodeReady state = VerificationTxState.ShortCodeReady
} }
private fun calculateSASBytes(): ByteArray {
when (accepted?.keyAgreementProtocol) {
KEY_AGREEMENT_V1 -> {
// (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function,
// the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of:
// - the string “MATRIX_KEY_VERIFICATION_SAS”,
// - the Matrix ID of the user who sent the m.key.verification.start message,
// - the device ID of the device that sent the m.key.verification.start 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
// - the transaction ID.
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$otherUserId$otherDeviceId$userId$deviceId$transactionId"
// decimal: generate five bytes by using HKDF.
// emoji: generate six bytes by using HKDF.
return getSAS().generateShortCode(sasInfo, 6)
}
KEY_AGREEMENT_V2 -> {
// Adds the SAS public key, and separate by |
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS|$otherUserId|$otherDeviceId|$otherKey|$userId|$deviceId|${getSAS().publicKey}|$transactionId"
return getSAS().generateShortCode(sasInfo, 6)
}
else -> {
// Protocol has been checked earlier
throw IllegalArgumentException()
}
}
}
override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) { override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) {
Timber.v("## SAS I: received mac for request id:$transactionId") Timber.v("## SAS I: received mac for request id:$transactionId")
// Check for state? // Check for state?

View file

@ -121,7 +121,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
// } // }
// //
// val requestMessage = KeyVerificationRequest( // val requestMessage = KeyVerificationRequest(
// fromDevice = session.sessionParams.credentials.deviceId ?: "", // fromDevice = session.sessionParams.deviceId ?: "",
// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS), // methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
// timestamp = System.currentTimeMillis().toInt(), // timestamp = System.currentTimeMillis().toInt(),
// transactionId = transactionId // transactionId = transactionId
@ -193,18 +193,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
if (accepted!!.commitment.equals(otherCommitment)) { if (accepted!!.commitment.equals(otherCommitment)) {
getSAS().setTheirPublicKey(otherKey) getSAS().setTheirPublicKey(otherKey)
// (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function, shortCodeBytes = calculateSASBytes()
// the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of:
// - the string “MATRIX_KEY_VERIFICATION_SAS”,
// - the Matrix ID of the user who sent the m.key.verification.start message,
// - the device ID of the device that sent the m.key.verification.start 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
// - the transaction ID.
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$userId$deviceId$otherUserId$otherDeviceId$transactionId"
// decimal: generate five bytes by using HKDF.
// emoji: generate six bytes by using HKDF.
shortCodeBytes = getSAS().generateShortCode(sasInfo, 6)
state = VerificationTxState.ShortCodeReady state = VerificationTxState.ShortCodeReady
} else { } else {
// bad commitment // bad commitment
@ -212,14 +201,45 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
} }
} }
private fun calculateSASBytes(): ByteArray {
when (accepted?.keyAgreementProtocol) {
KEY_AGREEMENT_V1 -> {
// (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function,
// the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of:
// - the string “MATRIX_KEY_VERIFICATION_SAS”,
// - the Matrix ID of the user who sent the m.key.verification.start message,
// - the device ID of the device that sent the m.key.verification.start 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
// - the transaction ID.
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$userId$deviceId$otherUserId$otherDeviceId$transactionId"
// decimal: generate five bytes by using HKDF.
// emoji: generate six bytes by using HKDF.
return getSAS().generateShortCode(sasInfo, 6)
}
KEY_AGREEMENT_V2 -> {
// Adds the SAS public key, and separate by |
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS|$userId|$deviceId|${getSAS().publicKey}|$otherUserId|$otherDeviceId|$otherKey|$transactionId"
return getSAS().generateShortCode(sasInfo, 6)
}
else -> {
// Protocol has been checked earlier
throw IllegalArgumentException()
}
}
}
override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) { override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) {
Timber.v("## SAS O: onKeyVerificationMac id:$transactionId") Timber.v("## SAS O: onKeyVerificationMac id:$transactionId")
// There is starting to be a huge amount of state / race here :/
if (state != VerificationTxState.OnKeyReceived if (state != VerificationTxState.OnKeyReceived
&& state != VerificationTxState.ShortCodeReady && state != VerificationTxState.ShortCodeReady
&& state != VerificationTxState.ShortCodeAccepted && state != VerificationTxState.ShortCodeAccepted
&& state != VerificationTxState.KeySent
&& state != VerificationTxState.SendingMac && state != VerificationTxState.SendingMac
&& state != VerificationTxState.MacSent) { && state != VerificationTxState.MacSent) {
Timber.e("## SAS O: received key from invalid state $state") Timber.e("## SAS O: received mac from invalid state $state")
cancel(CancelCode.UnexpectedMessage) cancel(CancelCode.UnexpectedMessage)
return return
} }

View file

@ -81,9 +81,9 @@ import im.vector.matrix.android.internal.crypto.verification.qrcode.generateShar
import im.vector.matrix.android.internal.di.DeviceId import im.vector.matrix.android.internal.di.DeviceId
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.util.UUID
@ -104,7 +104,8 @@ internal class DefaultVerificationService @Inject constructor(
private val verificationTransportRoomMessageFactory: VerificationTransportRoomMessageFactory, private val verificationTransportRoomMessageFactory: VerificationTransportRoomMessageFactory,
private val verificationTransportToDeviceFactory: VerificationTransportToDeviceFactory, private val verificationTransportToDeviceFactory: VerificationTransportToDeviceFactory,
private val crossSigningService: CrossSigningService, private val crossSigningService: CrossSigningService,
private val cryptoCoroutineScope: CoroutineScope private val cryptoCoroutineScope: CoroutineScope,
private val taskExecutor: TaskExecutor
) : DefaultVerificationTransaction.Listener, VerificationService { ) : DefaultVerificationTransaction.Listener, VerificationService {
private val uiHandler = Handler(Looper.getMainLooper()) private val uiHandler = Handler(Looper.getMainLooper())
@ -161,7 +162,7 @@ internal class DefaultVerificationService @Inject constructor(
} }
fun onRoomEvent(event: Event) { fun onRoomEvent(event: Event) {
GlobalScope.launch(coroutineDispatchers.crypto) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
when (event.getClearType()) { when (event.getClearType()) {
EventType.KEY_VERIFICATION_START -> { EventType.KEY_VERIFICATION_START -> {
onRoomStartRequestReceived(event) onRoomStartRequestReceived(event)
@ -301,7 +302,7 @@ internal class DefaultVerificationService @Inject constructor(
// We don't want to block here // We don't want to block here
val otherDeviceId = validRequestInfo.fromDevice val otherDeviceId = validRequestInfo.fromDevice
GlobalScope.launch { cryptoCoroutineScope.launch {
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) { if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
Timber.e("## Verification device $otherDeviceId is not known") Timber.e("## Verification device $otherDeviceId is not known")
} }
@ -340,7 +341,7 @@ internal class DefaultVerificationService @Inject constructor(
} }
// We don't want to block here // We don't want to block here
GlobalScope.launch { taskExecutor.executorScope.launch {
if (checkKeysAreDownloaded(senderId, validRequestInfo.fromDevice) == null) { if (checkKeysAreDownloaded(senderId, validRequestInfo.fromDevice) == null) {
Timber.e("## SAS Verification device ${validRequestInfo.fromDevice} is not known") Timber.e("## SAS Verification device ${validRequestInfo.fromDevice} is not known")
} }
@ -610,7 +611,7 @@ internal class DefaultVerificationService @Inject constructor(
if (validCancelReq == null) { if (validCancelReq == null) {
// ignore // ignore
Timber.e("## SAS Received invalid key request") Timber.e("## SAS Received invalid cancel request")
// TODO should we cancel? // TODO should we cancel?
return return
} }

View file

@ -26,7 +26,6 @@ import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager
import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
import im.vector.matrix.android.internal.crypto.model.MXKey
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.extensions.toUnsignedInt import im.vector.matrix.android.internal.extensions.toUnsignedInt
import im.vector.matrix.android.internal.util.withoutPrefix import im.vector.matrix.android.internal.util.withoutPrefix
@ -66,8 +65,11 @@ internal abstract class SASDefaultVerificationTransaction(
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256" const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
const val SAS_MAC_SHA256 = "hkdf-hmac-sha256" const val SAS_MAC_SHA256 = "hkdf-hmac-sha256"
// Deprecated maybe removed later, use V2
const val KEY_AGREEMENT_V1 = "curve25519"
const val KEY_AGREEMENT_V2 = "curve25519-hkdf-sha256"
// ordered by preferred order // ordered by preferred order
val KNOWN_AGREEMENT_PROTOCOLS = listOf(MXKey.KEY_CURVE_25519_TYPE) 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

View file

@ -48,10 +48,11 @@ import im.vector.matrix.android.internal.di.SessionId
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.di.WorkManagerProvider
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.StringProvider import im.vector.matrix.android.internal.util.StringProvider
import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.util.UUID
@ -66,7 +67,8 @@ internal class VerificationTransportRoomMessage(
private val userDeviceId: String?, private val userDeviceId: String?,
private val roomId: String, private val roomId: String,
private val localEchoEventFactory: LocalEchoEventFactory, private val localEchoEventFactory: LocalEchoEventFactory,
private val tx: DefaultVerificationTransaction? private val tx: DefaultVerificationTransaction?,
private val coroutineScope: CoroutineScope
) : VerificationTransport { ) : VerificationTransport {
override fun <T> sendToOther(type: String, override fun <T> sendToOther(type: String,
@ -131,7 +133,7 @@ internal class VerificationTransportRoomMessage(
} }
// TODO listen to DB to get synced info // TODO listen to DB to get synced info
GlobalScope.launch(Dispatchers.Main) { coroutineScope.launch(Dispatchers.Main) {
workLiveData.observeForever(observer) workLiveData.observeForever(observer)
} }
} }
@ -212,7 +214,7 @@ internal class VerificationTransportRoomMessage(
} }
// TODO listen to DB to get synced info // TODO listen to DB to get synced info
GlobalScope.launch(Dispatchers.Main) { coroutineScope.launch(Dispatchers.Main) {
workLiveData.observeForever(observer) workLiveData.observeForever(observer)
} }
} }
@ -265,7 +267,7 @@ internal class VerificationTransportRoomMessage(
} }
// TODO listen to DB to get synced info // TODO listen to DB to get synced info
GlobalScope.launch(Dispatchers.Main) { coroutineScope.launch(Dispatchers.Main) {
workLiveData.observeForever(observer) workLiveData.observeForever(observer)
} }
} }
@ -384,9 +386,19 @@ internal class VerificationTransportRoomMessageFactory @Inject constructor(
private val userId: String, private val userId: String,
@DeviceId @DeviceId
private val deviceId: String?, private val deviceId: String?,
private val localEchoEventFactory: LocalEchoEventFactory) { private val localEchoEventFactory: LocalEchoEventFactory,
private val taskExecutor: TaskExecutor
) {
fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage { fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage {
return VerificationTransportRoomMessage(workManagerProvider, stringProvider, sessionId, userId, deviceId, roomId, localEchoEventFactory, tx) return VerificationTransportRoomMessage(workManagerProvider,
stringProvider,
sessionId,
userId,
deviceId,
roomId,
localEchoEventFactory,
tx,
taskExecutor.executorScope)
} }
} }

View file

@ -60,10 +60,9 @@ internal fun ChunkEntity.merge(roomId: String, chunkToMerge: ChunkEntity, direct
chunkToMerge.stateEvents.forEach { stateEvent -> chunkToMerge.stateEvents.forEach { stateEvent ->
addStateEvent(roomId, stateEvent, direction) addStateEvent(roomId, stateEvent, direction)
} }
return eventsToMerge eventsToMerge.forEach {
.forEach { addTimelineEventFromMerge(localRealm, it, direction)
addTimelineEventFromMerge(localRealm, it, direction) }
}
} }
internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity, direction: PaginationDirection) { internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity, direction: PaginationDirection) {

View file

@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.database.helper
import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
import im.vector.matrix.android.internal.extensions.assertIsManaged
import io.realm.Realm import io.realm.Realm
internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long { internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
@ -28,3 +29,11 @@ internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
currentIdNum.toLong() + 1 currentIdNum.toLong() + 1
} }
} }
internal fun TimelineEventEntity.deleteOnCascade() {
assertIsManaged()
root?.deleteFromRealm()
annotations?.deleteFromRealm()
readReceipts?.deleteFromRealm()
deleteFromRealm()
}

View file

@ -20,21 +20,16 @@ import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity
/** /**
* HomeServerCapabilitiesEntity <-> HomeSeverCapabilities * HomeServerCapabilitiesEntity -> HomeSeverCapabilities
*/ */
internal object HomeServerCapabilitiesMapper { internal object HomeServerCapabilitiesMapper {
fun map(entity: HomeServerCapabilitiesEntity): HomeServerCapabilities { fun map(entity: HomeServerCapabilitiesEntity): HomeServerCapabilities {
return HomeServerCapabilities( return HomeServerCapabilities(
canChangePassword = entity.canChangePassword, canChangePassword = entity.canChangePassword,
maxUploadFileSize = entity.maxUploadFileSize maxUploadFileSize = entity.maxUploadFileSize,
) lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
} defaultIdentityServerUrl = entity.defaultIdentityServerUrl
fun map(domain: HomeServerCapabilities): HomeServerCapabilitiesEntity {
return HomeServerCapabilitiesEntity(
canChangePassword = domain.canChangePassword,
maxUploadFileSize = domain.maxUploadFileSize
) )
} }
} }

View file

@ -45,7 +45,7 @@ internal object PushRulesMapper {
private fun fromActionStr(actionsStr: String?): List<Any> { private fun fromActionStr(actionsStr: String?): List<Any> {
try { try {
return actionsStr?.let { moshiActionsAdapter.fromJson(it) } ?: emptyList() return actionsStr?.let { moshiActionsAdapter.fromJson(it) }.orEmpty()
} catch (e: Throwable) { } catch (e: Throwable) {
Timber.e(e, "## failed to map push rule actions <$actionsStr>") Timber.e(e, "## failed to map push rule actions <$actionsStr>")
return emptyList() return emptyList()

View file

@ -49,7 +49,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
membership = roomSummaryEntity.membership, membership = roomSummaryEntity.membership,
versioningState = roomSummaryEntity.versioningState, versioningState = roomSummaryEntity.versioningState,
readMarkerId = roomSummaryEntity.readMarkerId, readMarkerId = roomSummaryEntity.readMarkerId,
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(), userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) }.orEmpty(),
canonicalAlias = roomSummaryEntity.canonicalAlias, canonicalAlias = roomSummaryEntity.canonicalAlias,
aliases = roomSummaryEntity.aliases.toList(), aliases = roomSummaryEntity.aliases.toList(),
isEncrypted = roomSummaryEntity.isEncrypted, isEncrypted = roomSummaryEntity.isEncrypted,

View file

@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.ReadReceipt import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.sender.SenderInfo
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import javax.inject.Inject import javax.inject.Inject
@ -41,15 +41,18 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS
annotations = timelineEventEntity.annotations?.asDomain(), annotations = timelineEventEntity.annotations?.asDomain(),
localId = timelineEventEntity.localId, localId = timelineEventEntity.localId,
displayIndex = timelineEventEntity.displayIndex, displayIndex = timelineEventEntity.displayIndex,
senderName = timelineEventEntity.senderName, senderInfo = SenderInfo(
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, userId = timelineEventEntity.root?.sender ?: "",
senderAvatar = timelineEventEntity.senderAvatar, displayName = timelineEventEntity.senderName,
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
avatarUrl = timelineEventEntity.senderAvatar
),
readReceipts = readReceipts readReceipts = readReceipts
?.distinctBy { ?.distinctBy {
it.user it.user
}?.sortedByDescending { }?.sortedByDescending {
it.originServerTs it.originServerTs
} ?: emptyList() }.orEmpty()
) )
} }
} }

View file

@ -23,15 +23,20 @@ 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
@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(),
// 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() {
fun identifier() = "${prevToken}_$nextToken" fun identifier() = "${prevToken}_$nextToken"
// If true, then this chunk was previously a last forward chunk
fun hasBeenALastForwardChunk() = nextToken == null && !isLastForward
@LinkingObjects("chunks") @LinkingObjects("chunks")
val room: RealmResults<RoomEntity>? = null val room: RealmResults<RoomEntity>? = null

View file

@ -22,6 +22,8 @@ import io.realm.RealmObject
internal open class HomeServerCapabilitiesEntity( internal open class HomeServerCapabilitiesEntity(
var canChangePassword: Boolean = true, var canChangePassword: Boolean = true,
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN, var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
var lastVersionIdentityServerSupported: Boolean = false,
var defaultIdentityServerUrl: String? = null,
var lastUpdatedTimestamp: Long = 0L var lastUpdatedTimestamp: Long = 0L
) : RealmObject() { ) : RealmObject() {

View file

@ -37,6 +37,7 @@ import io.realm.annotations.RealmModule
UserEntity::class, UserEntity::class,
IgnoredUserEntity::class, IgnoredUserEntity::class,
BreadcrumbsEntity::class, BreadcrumbsEntity::class,
UserThreePidEntity::class,
EventAnnotationsSummaryEntity::class, EventAnnotationsSummaryEntity::class,
ReactionAggregatedSummaryEntity::class, ReactionAggregatedSummaryEntity::class,
EditAggregatedSummaryEntity::class, EditAggregatedSummaryEntity::class,

View file

@ -0,0 +1,25 @@
/*
* 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.matrix.android.internal.database.model
import io.realm.RealmObject
internal open class UserThreePidEntity(
var medium: String = "",
var address: String = "",
var validatedAt: Long = 0,
var addedAt: Long = 0
) : RealmObject()

View file

@ -41,7 +41,7 @@ internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken:
return query.findFirst() return query.findFirst()
} }
internal fun ChunkEntity.Companion.findLastLiveChunkFromRoom(realm: Realm, roomId: String): ChunkEntity? { internal fun ChunkEntity.Companion.findLastForwardChunkOfRoom(realm: Realm, roomId: String): ChunkEntity? {
return where(realm, roomId) return where(realm, roomId)
.equalTo(ChunkEntityFields.IS_LAST_FORWARD, true) .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true)
.findFirst() .findFirst()

View file

@ -35,7 +35,7 @@ internal fun FilterEntity.Companion.get(realm: Realm): FilterEntity? {
internal fun FilterEntity.Companion.getOrCreate(realm: Realm): FilterEntity { internal fun FilterEntity.Companion.getOrCreate(realm: Realm): FilterEntity {
return get(realm) ?: realm.createObject<FilterEntity>() return get(realm) ?: realm.createObject<FilterEntity>()
.apply { .apply {
filterBodyJson = FilterFactory.createDefaultFilterBody().toJSONString() filterBodyJson = FilterFactory.createDefaultFilter().toJSONString()
roomEventFilterJson = FilterFactory.createDefaultRoomFilter().toJSONString() roomEventFilterJson = FilterFactory.createDefaultRoomFilter().toJSONString()
filterId = "" filterId = ""
} }

View file

@ -36,7 +36,7 @@ internal fun isEventRead(monarchy: Monarchy,
var isEventRead = false var isEventRead = false
monarchy.doWithRealm { realm -> monarchy.doWithRealm { realm ->
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) ?: return@doWithRealm val liveChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: return@doWithRealm
val eventToCheck = liveChunk.timelineEvents.find(eventId) val eventToCheck = liveChunk.timelineEvents.find(eventId)
isEventRead = if (eventToCheck == null || eventToCheck.root?.sender == userId) { isEventRead = if (eventToCheck == null || eventToCheck.root?.sender == userId) {
true true

View file

@ -59,11 +59,11 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
filterTypes: List<String> = emptyList()): TimelineEventEntity? { filterTypes: List<String> = emptyList()): TimelineEventEntity? {
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null
val sendingTimelineEvents = roomEntity.sendingTimelineEvents.where().filterTypes(filterTypes) val sendingTimelineEvents = roomEntity.sendingTimelineEvents.where().filterTypes(filterTypes)
val liveEvents = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes) val liveEvents = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes)
if (filterContentRelation) { if (filterContentRelation) {
liveEvents liveEvents
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE) ?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.RESPONSE_TYPE) ?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
} }
val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) { val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) {
sendingTimelineEvents sendingTimelineEvents

View file

@ -0,0 +1,44 @@
/*
* Copyright 2019 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.matrix.android.internal.database.query
/**
* Query strings used to filter the timeline events regarding the Json raw string of the Event
*/
internal object TimelineEventFilter {
/**
* To apply to Event.content
*/
internal object Content {
internal const val EDIT = """{*"m.relates_to"*"rel_type":*"m.replace"*}"""
internal const val RESPONSE = """{*"m.relates_to"*"rel_type":*"m.response"*}"""
}
/**
* To apply to Event.decryptionResultJson
*/
internal object DecryptedContent {
internal const val URL = """{*"file":*"url":*}"""
}
/**
* To apply to Event.unsigned
*/
internal object Unsigned {
internal const val REDACTED = """{*"redacted_because":*}"""
}
}

View file

@ -20,8 +20,12 @@ import javax.inject.Qualifier
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class Authenticated internal annotation class Authenticated
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class Unauthenticated internal annotation class AuthenticatedIdentity
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
internal annotation class Unauthenticated

View file

@ -20,12 +20,16 @@ import javax.inject.Qualifier
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class AuthDatabase internal annotation class AuthDatabase
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class SessionDatabase internal annotation class SessionDatabase
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class CryptoDatabase internal annotation class CryptoDatabase
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
internal annotation class IdentityDatabase

View file

@ -20,16 +20,16 @@ import javax.inject.Qualifier
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class SessionFilesDirectory internal annotation class SessionFilesDirectory
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class SessionCacheDirectory internal annotation class SessionCacheDirectory
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class CacheDirectory internal annotation class CacheDirectory
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class ExternalFilesDirectory internal annotation class ExternalFilesDirectory

View file

@ -21,6 +21,7 @@ import com.squareup.moshi.Moshi
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.MatrixConfiguration
import im.vector.matrix.android.internal.network.TimeOutInterceptor import im.vector.matrix.android.internal.network.TimeOutInterceptor
import im.vector.matrix.android.internal.network.UserAgentInterceptor import im.vector.matrix.android.internal.network.UserAgentInterceptor
import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
@ -64,7 +65,8 @@ internal object NetworkModule {
@Provides @Provides
@JvmStatic @JvmStatic
@Unauthenticated @Unauthenticated
fun providesOkHttpClient(stethoInterceptor: StethoInterceptor, fun providesOkHttpClient(matrixConfiguration: MatrixConfiguration,
stethoInterceptor: StethoInterceptor,
timeoutInterceptor: TimeOutInterceptor, timeoutInterceptor: TimeOutInterceptor,
userAgentInterceptor: UserAgentInterceptor, userAgentInterceptor: UserAgentInterceptor,
httpLoggingInterceptor: HttpLoggingInterceptor, httpLoggingInterceptor: HttpLoggingInterceptor,
@ -82,6 +84,9 @@ internal object NetworkModule {
if (BuildConfig.LOG_PRIVATE_DATA) { if (BuildConfig.LOG_PRIVATE_DATA) {
addInterceptor(curlLoggingInterceptor) addInterceptor(curlLoggingInterceptor)
} }
matrixConfiguration.proxy?.let {
proxy(it)
}
} }
.addInterceptor(okReplayInterceptor) .addInterceptor(okReplayInterceptor)
.build() .build()

View file

@ -0,0 +1,24 @@
/*
* 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.matrix.android.internal.extensions
/**
* Ex: "abcdef".subStringBetween("a", "f") -> "bcde"
* Ex: "abcdefff".subStringBetween("a", "f") -> "bcdeff"
* Ex: "aaabcdef".subStringBetween("a", "f") -> "aabcde"
*/
internal fun String.subStringBetween(prefix: String, suffix: String) = substringAfter(prefix).substringBeforeLast(suffix)

View file

@ -16,20 +16,16 @@
package im.vector.matrix.android.internal.network package im.vector.matrix.android.internal.network
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.network.token.AccessTokenProvider
import im.vector.matrix.android.internal.di.SessionId
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
import javax.inject.Inject
internal class AccessTokenInterceptor @Inject constructor( internal class AccessTokenInterceptor(private val accessTokenProvider: AccessTokenProvider) : Interceptor {
@SessionId private val sessionId: String,
private val sessionParamsStore: SessionParamsStore) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request() var request = chain.request()
accessToken?.let { accessTokenProvider.getToken()?.let {
val newRequestBuilder = request.newBuilder() val newRequestBuilder = request.newBuilder()
// Add the access token to all requests if it is set // Add the access token to all requests if it is set
newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer $it") newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer $it")
@ -38,7 +34,4 @@ internal class AccessTokenInterceptor @Inject constructor(
return chain.proceed(request) return chain.proceed(request)
} }
private val accessToken
get() = sessionParamsStore.get(sessionId)?.credentials?.accessToken
} }

View file

@ -28,8 +28,9 @@ internal object NetworkConstants {
const val URI_API_MEDIA_PREFIX_PATH_R0 = "$URI_API_MEDIA_PREFIX_PATH/r0/" const val URI_API_MEDIA_PREFIX_PATH_R0 = "$URI_API_MEDIA_PREFIX_PATH/r0/"
// Identity server // Identity server
const val URI_IDENTITY_PATH = "_matrix/identity/api/v1/" const val URI_IDENTITY_PREFIX_PATH = "_matrix/identity/v2"
const val URI_IDENTITY_PATH_V2 = "_matrix/identity/v2/" const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/"
const val URI_API_PREFIX_IDENTITY = "_matrix/identity/api/v1" // TODO Ganfra, use correct value
const val URI_INTEGRATION_MANAGER_PATH = "TODO/"
} }

View file

@ -0,0 +1,39 @@
/*
* 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.matrix.android.internal.network.httpclient
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
import okhttp3.OkHttpClient
internal fun OkHttpClient.addAccessTokenInterceptor(accessTokenProvider: AccessTokenProvider): OkHttpClient {
return newBuilder()
.apply {
// Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor
val existingCurlInterceptors = interceptors().filterIsInstance<CurlLoggingInterceptor>()
interceptors().removeAll(existingCurlInterceptors)
addInterceptor(AccessTokenInterceptor(accessTokenProvider))
// Re add eventually the curl logging interceptors
existingCurlInterceptors.forEach {
addInterceptor(it)
}
}
.build()
}

View file

@ -0,0 +1,21 @@
/*
* 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.matrix.android.internal.network.token
internal interface AccessTokenProvider {
fun getToken(): String?
}

View file

@ -0,0 +1,28 @@
/*
* 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.matrix.android.internal.network.token
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.di.SessionId
import javax.inject.Inject
internal class HomeserverAccessTokenProvider @Inject constructor(
@SessionId private val sessionId: String,
private val sessionParamsStore: SessionParamsStore
) : AccessTokenProvider {
override fun getToken() = sessionParamsStore.get(sessionId)?.credentials?.accessToken
}

View file

@ -28,10 +28,10 @@ import im.vector.matrix.android.internal.di.ExternalFilesDirectory
import im.vector.matrix.android.internal.di.SessionCacheDirectory import im.vector.matrix.android.internal.di.SessionCacheDirectory
import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.toCancelable import im.vector.matrix.android.internal.util.toCancelable
import im.vector.matrix.android.internal.util.writeToFile import im.vector.matrix.android.internal.util.writeToFile
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -51,7 +51,9 @@ internal class DefaultFileService @Inject constructor(
private val contentUrlResolver: ContentUrlResolver, private val contentUrlResolver: ContentUrlResolver,
@Unauthenticated @Unauthenticated
private val okHttpClient: OkHttpClient, private val okHttpClient: OkHttpClient,
private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService { private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor
) : FileService {
/** /**
* Download file in the cache folder, and eventually decrypt it * Download file in the cache folder, and eventually decrypt it
@ -63,7 +65,7 @@ internal class DefaultFileService @Inject constructor(
url: String?, url: String?,
elementToDecrypt: ElementToDecrypt?, elementToDecrypt: ElementToDecrypt?,
callback: MatrixCallback<File>): Cancelable { callback: MatrixCallback<File>): Cancelable {
return GlobalScope.launch(coroutineDispatchers.main) { return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
Try { Try {
val folder = File(sessionCacheDirectory, "MF") val folder = File(sessionCacheDirectory, "MF")

View file

@ -43,6 +43,7 @@ import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageSer
import im.vector.matrix.android.api.session.signout.SignOutService import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.terms.TermsService
import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.crypto.DefaultCryptoService import im.vector.matrix.android.internal.crypto.DefaultCryptoService
@ -50,12 +51,14 @@ import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater
import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.di.SessionId
import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.di.WorkManagerProvider
import im.vector.matrix.android.internal.session.identity.DefaultIdentityService
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecryptor import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecryptor
import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.job.SyncThread import im.vector.matrix.android.internal.session.sync.job.SyncThread
import im.vector.matrix.android.internal.session.sync.job.SyncWorker import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
@ -82,6 +85,7 @@ internal class DefaultSession @Inject constructor(
private val signOutService: Lazy<SignOutService>, private val signOutService: Lazy<SignOutService>,
private val pushRuleService: Lazy<PushRuleService>, private val pushRuleService: Lazy<PushRuleService>,
private val pushersService: Lazy<PushersService>, private val pushersService: Lazy<PushersService>,
private val termsService: Lazy<TermsService>,
private val cryptoService: Lazy<DefaultCryptoService>, private val cryptoService: Lazy<DefaultCryptoService>,
private val fileService: Lazy<FileService>, private val fileService: Lazy<FileService>,
private val secureStorageService: Lazy<SecureStorageService>, private val secureStorageService: Lazy<SecureStorageService>,
@ -97,8 +101,11 @@ internal class DefaultSession @Inject constructor(
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>, private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
private val accountService: Lazy<AccountService>, private val accountService: Lazy<AccountService>,
private val timelineEventDecryptor: TimelineEventDecryptor, private val timelineEventDecryptor: TimelineEventDecryptor,
private val shieldTrustUpdater: ShieldTrustUpdater) private val shieldTrustUpdater: ShieldTrustUpdater,
: Session, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val defaultIdentityService: DefaultIdentityService,
private val taskExecutor: TaskExecutor
) : Session,
RoomService by roomService.get(), RoomService by roomService.get(),
RoomDirectoryService by roomDirectoryService.get(), RoomDirectoryService by roomDirectoryService.get(),
GroupService by groupService.get(), GroupService by groupService.get(),
@ -108,6 +115,7 @@ internal class DefaultSession @Inject constructor(
PushRuleService by pushRuleService.get(), PushRuleService by pushRuleService.get(),
PushersService by pushersService.get(), PushersService by pushersService.get(),
FileService by fileService.get(), FileService by fileService.get(),
TermsService by termsService.get(),
InitialSyncProgressService by initialSyncProgressService.get(), InitialSyncProgressService by initialSyncProgressService.get(),
SecureStorageService by secureStorageService.get(), SecureStorageService by secureStorageService.get(),
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(), HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
@ -133,6 +141,7 @@ internal class DefaultSession @Inject constructor(
eventBus.register(this) eventBus.register(this)
timelineEventDecryptor.start() timelineEventDecryptor.start()
shieldTrustUpdater.start() shieldTrustUpdater.start()
defaultIdentityService.start()
} }
override fun requireBackgroundSync() { override fun requireBackgroundSync() {
@ -175,6 +184,10 @@ internal class DefaultSession @Inject constructor(
isOpen = false isOpen = false
eventBus.unregister(this) eventBus.unregister(this)
shieldTrustUpdater.stop() shieldTrustUpdater.stop()
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
// This has to be done on main thread
defaultIdentityService.stop()
}
} }
override fun getSyncStateLive(): LiveData<SyncState> { override fun getSyncStateLive(): LiveData<SyncState> {
@ -204,7 +217,7 @@ internal class DefaultSession @Inject constructor(
if (globalError is GlobalError.InvalidToken if (globalError is GlobalError.InvalidToken
&& globalError.softLogout) { && globalError.softLogout) {
// Mark the token has invalid // Mark the token has invalid
GlobalScope.launch(Dispatchers.IO) { taskExecutor.executorScope.launch(Dispatchers.IO) {
sessionParamsStore.setTokenInvalid(sessionId) sessionParamsStore.setTokenInvalid(sessionId)
} }
} }
@ -218,6 +231,8 @@ internal class DefaultSession @Inject constructor(
override fun cryptoService(): CryptoService = cryptoService.get() override fun cryptoService(): CryptoService = cryptoService.get()
override fun identityService() = defaultIdentityService
override fun addListener(listener: Session.Listener) { override fun addListener(listener: Session.Listener) {
sessionListeners.addListener(listener) sessionListeners.addListener(listener)
} }
@ -228,6 +243,6 @@ internal class DefaultSession @Inject constructor(
// For easy debugging // For easy debugging
override fun toString(): String { override fun toString(): String {
return "$myUserId - ${sessionParams.credentials.deviceId}" return "$myUserId - ${sessionParams.deviceId}"
} }
} }

View file

@ -36,6 +36,7 @@ import im.vector.matrix.android.internal.session.filter.FilterModule
import im.vector.matrix.android.internal.session.group.GetGroupDataWorker import im.vector.matrix.android.internal.session.group.GetGroupDataWorker
import im.vector.matrix.android.internal.session.group.GroupModule import im.vector.matrix.android.internal.session.group.GroupModule
import im.vector.matrix.android.internal.session.homeserver.HomeServerCapabilitiesModule import im.vector.matrix.android.internal.session.homeserver.HomeServerCapabilitiesModule
import im.vector.matrix.android.internal.session.identity.IdentityModule
import im.vector.matrix.android.internal.session.openid.OpenIdModule import im.vector.matrix.android.internal.session.openid.OpenIdModule
import im.vector.matrix.android.internal.session.profile.ProfileModule import im.vector.matrix.android.internal.session.profile.ProfileModule
import im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker import im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker
@ -51,6 +52,7 @@ import im.vector.matrix.android.internal.session.sync.SyncModule
import im.vector.matrix.android.internal.session.sync.SyncTask import im.vector.matrix.android.internal.session.sync.SyncTask
import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.job.SyncWorker import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import im.vector.matrix.android.internal.session.terms.TermsModule
import im.vector.matrix.android.internal.session.user.UserModule import im.vector.matrix.android.internal.session.user.UserModule
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
@ -72,6 +74,8 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
CryptoModule::class, CryptoModule::class,
PushersModule::class, PushersModule::class,
OpenIdModule::class, OpenIdModule::class,
IdentityModule::class,
TermsModule::class,
AccountDataModule::class, AccountDataModule::class,
ProfileModule::class, ProfileModule::class,
SessionAssistedInjectModule::class, SessionAssistedInjectModule::class,

View file

@ -50,14 +50,15 @@ import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.di.UserMd5 import im.vector.matrix.android.internal.di.UserMd5
import im.vector.matrix.android.internal.eventbus.EventBusTimberLogger import im.vector.matrix.android.internal.eventbus.EventBusTimberLogger
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
import im.vector.matrix.android.internal.network.DefaultNetworkConnectivityChecker import im.vector.matrix.android.internal.network.DefaultNetworkConnectivityChecker
import im.vector.matrix.android.internal.network.FallbackNetworkCallbackStrategy import im.vector.matrix.android.internal.network.FallbackNetworkCallbackStrategy
import im.vector.matrix.android.internal.network.NetworkCallbackStrategy import im.vector.matrix.android.internal.network.NetworkCallbackStrategy
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.network.PreferredNetworkCallbackStrategy import im.vector.matrix.android.internal.network.PreferredNetworkCallbackStrategy
import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor import im.vector.matrix.android.internal.network.httpclient.addAccessTokenInterceptor
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
import im.vector.matrix.android.internal.network.token.HomeserverAccessTokenProvider
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
import im.vector.matrix.android.internal.session.homeserver.DefaultHomeServerCapabilitiesService import im.vector.matrix.android.internal.session.homeserver.DefaultHomeServerCapabilitiesService
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
@ -175,21 +176,8 @@ internal abstract class SessionModule {
@SessionScope @SessionScope
@Authenticated @Authenticated
fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient, fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
accessTokenInterceptor: AccessTokenInterceptor): OkHttpClient { @Authenticated accessTokenProvider: AccessTokenProvider): OkHttpClient {
return okHttpClient.newBuilder() return okHttpClient.addAccessTokenInterceptor(accessTokenProvider)
.apply {
// Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor
val existingCurlInterceptors = interceptors().filterIsInstance<CurlLoggingInterceptor>()
interceptors().removeAll(existingCurlInterceptors)
addInterceptor(accessTokenInterceptor)
// Re add eventually the curl logging interceptors
existingCurlInterceptors.forEach {
addInterceptor(it)
}
}
.build()
} }
@JvmStatic @JvmStatic
@ -233,44 +221,48 @@ internal abstract class SessionModule {
} }
} }
@Binds
@Authenticated
abstract fun bindAccessTokenProvider(provider: HomeserverAccessTokenProvider): AccessTokenProvider
@Binds @Binds
abstract fun bindSession(session: DefaultSession): Session abstract fun bindSession(session: DefaultSession): Session
@Binds @Binds
abstract fun bindNetworkConnectivityChecker(networkConnectivityChecker: DefaultNetworkConnectivityChecker): NetworkConnectivityChecker abstract fun bindNetworkConnectivityChecker(checker: DefaultNetworkConnectivityChecker): NetworkConnectivityChecker
@Binds @Binds
@IntoSet @IntoSet
abstract fun bindGroupSummaryUpdater(groupSummaryUpdater: GroupSummaryUpdater): LiveEntityObserver abstract fun bindGroupSummaryUpdater(updater: GroupSummaryUpdater): LiveEntityObserver
@Binds @Binds
@IntoSet @IntoSet
abstract fun bindEventsPruner(eventsPruner: EventsPruner): LiveEntityObserver abstract fun bindEventsPruner(pruner: EventsPruner): LiveEntityObserver
@Binds @Binds
@IntoSet @IntoSet
abstract fun bindEventRelationsAggregationUpdater(eventRelationsAggregationUpdater: EventRelationsAggregationUpdater): LiveEntityObserver abstract fun bindEventRelationsAggregationUpdater(updater: EventRelationsAggregationUpdater): LiveEntityObserver
@Binds @Binds
@IntoSet @IntoSet
abstract fun bindRoomTombstoneEventLiveObserver(roomTombstoneEventLiveObserver: RoomTombstoneEventLiveObserver): LiveEntityObserver abstract fun bindRoomTombstoneEventLiveObserver(observer: RoomTombstoneEventLiveObserver): LiveEntityObserver
@Binds @Binds
@IntoSet @IntoSet
abstract fun bindRoomCreateEventLiveObserver(roomCreateEventLiveObserver: RoomCreateEventLiveObserver): LiveEntityObserver abstract fun bindRoomCreateEventLiveObserver(observer: RoomCreateEventLiveObserver): LiveEntityObserver
@Binds @Binds
@IntoSet @IntoSet
abstract fun bindVerificationMessageLiveObserver(verificationMessageLiveObserver: VerificationMessageLiveObserver): LiveEntityObserver abstract fun bindVerificationMessageLiveObserver(observer: VerificationMessageLiveObserver): LiveEntityObserver
@Binds @Binds
abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService
@Binds @Binds
abstract fun bindSecureStorageService(secureStorageService: DefaultSecureStorageService): SecureStorageService abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService
@Binds @Binds
abstract fun bindHomeServerCapabilitiesService(homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService abstract fun bindHomeServerCapabilitiesService(service: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService
@Binds @Binds
abstract fun bindAccountDataService(service: DefaultAccountDataService): AccountDataService abstract fun bindAccountDataService(service: DefaultAccountDataService): AccountDataService

View file

@ -19,8 +19,10 @@ package im.vector.matrix.android.internal.session.account
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.cleanup.CleanupSession import im.vector.matrix.android.internal.session.cleanup.CleanupSession
import im.vector.matrix.android.internal.session.identity.IdentityDisconnectTask
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> { internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> {
@ -34,6 +36,7 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
private val accountAPI: AccountAPI, private val accountAPI: AccountAPI,
private val eventBus: EventBus, private val eventBus: EventBus,
@UserId private val userId: String, @UserId private val userId: String,
private val identityDisconnectTask: IdentityDisconnectTask,
private val cleanupSession: CleanupSession private val cleanupSession: CleanupSession
) : DeactivateAccountTask { ) : DeactivateAccountTask {
@ -44,6 +47,10 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
apiCall = accountAPI.deactivate(deactivateAccountParams) apiCall = accountAPI.deactivate(deactivateAccountParams)
} }
// Logout from identity server if any, ignoring errors
runCatching { identityDisconnectTask.execute(Unit) }
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
cleanupSession.handle() cleanupSession.handle()
} }
} }

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