mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 17:35:54 +03:00
Merge branch 'release/0.21.0'
This commit is contained in:
commit
d1c4d4b099
411 changed files with 13503 additions and 1602 deletions
|
@ -12,12 +12,15 @@
|
|||
<w>fdroid</w>
|
||||
<w>gplay</w>
|
||||
<w>hmac</w>
|
||||
<w>homeserver</w>
|
||||
<w>ktlint</w>
|
||||
<w>linkified</w>
|
||||
<w>linkify</w>
|
||||
<w>megolm</w>
|
||||
<w>msisdn</w>
|
||||
<w>msisdns</w>
|
||||
<w>pbkdf</w>
|
||||
<w>pids</w>
|
||||
<w>pkcs</w>
|
||||
<w>riotx</w>
|
||||
<w>signin</w>
|
||||
|
|
27
CHANGES.md
27
CHANGES.md
|
@ -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)
|
||||
===================================================
|
||||
|
||||
|
|
92
docs/identity_server.md
Normal file
92
docs/identity_server.md
Normal 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
|
|
@ -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.group.GroupSummaryQueryParams
|
||||
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.room.RoomSummaryQueryParams
|
||||
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()
|
||||
}
|
||||
|
||||
fun liveThreePIds(refreshData: Boolean): Observable<List<ThreePid>> {
|
||||
return session.getThreePidsLive(refreshData).asObservable()
|
||||
.startWithCallable { session.getThreePids() }
|
||||
}
|
||||
|
||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
||||
session.createRoom(roomParams, it)
|
||||
}
|
||||
|
|
|
@ -158,6 +158,9 @@ dependencies {
|
|||
// Bus
|
||||
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'
|
||||
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
|
||||
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
|
||||
|
|
|
@ -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.session.Session
|
||||
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.room.Room
|
||||
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.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
|
@ -88,7 +88,8 @@ class CommonTestHelper(context: Context) {
|
|||
fun syncSession(session: Session) {
|
||||
val lock = CountDownLatch(1)
|
||||
|
||||
session.open()
|
||||
GlobalScope.launch(Dispatchers.Main) { session.open() }
|
||||
|
||||
session.startSync(true)
|
||||
|
||||
val syncLiveData = runBlocking(Dispatchers.Main) {
|
||||
|
@ -116,7 +117,7 @@ class CommonTestHelper(context: Context) {
|
|||
*/
|
||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> {
|
||||
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
|
||||
val latch = CountDownLatch(nbOfMessages)
|
||||
val latch = CountDownLatch(1)
|
||||
val timelineListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
|
@ -127,7 +128,7 @@ class CommonTestHelper(context: Context) {
|
|||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val newMessages = snapshot
|
||||
.filter { LocalEcho.isLocalEchoId(it.eventId).not() }
|
||||
.filter { it.root.sendState == SendState.SYNCED }
|
||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
|
||||
|
||||
|
@ -143,7 +144,8 @@ class CommonTestHelper(context: Context) {
|
|||
for (i in 0 until nbOfMessages) {
|
||||
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.dispose()
|
||||
|
||||
|
@ -291,6 +293,24 @@ class CommonTestHelper(context: Context) {
|
|||
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
|
||||
*
|
||||
|
@ -349,3 +369,13 @@ class CommonTestHelper(context: Context) {
|
|||
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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,17 +53,19 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
/**
|
||||
* @return alice session
|
||||
*/
|
||||
fun doE2ETestWithAliceInARoom(): CryptoTestData {
|
||||
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it)
|
||||
}
|
||||
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
if (encryptedRoom) {
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
room.enableEncryption(callback = it)
|
||||
mTestHelper.doSync<Unit> {
|
||||
room.enableEncryption(callback = it)
|
||||
}
|
||||
}
|
||||
|
||||
return CryptoTestData(aliceSession, roomId)
|
||||
|
@ -72,8 +74,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
/**
|
||||
* @return alice and bob sessions
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData {
|
||||
val cryptoTestData = doE2ETestWithAliceInARoom()
|
||||
fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData {
|
||||
val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
|
@ -246,7 +248,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
assertNotNull(eventWireContent.get("session_id"))
|
||||
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)
|
||||
assertEquals(roomId, event.roomId)
|
||||
|
|
|
@ -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
|
||||
|
||||
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
|
||||
val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
|
|
|
@ -148,7 +148,7 @@ class KeyShareTests : InstrumentedTest {
|
|||
|
||||
// Mark the device as trusted
|
||||
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
||||
aliceSession2.sessionParams.credentials.deviceId ?: "")
|
||||
aliceSession2.sessionParams.deviceId ?: "")
|
||||
|
||||
// Re request
|
||||
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
||||
|
@ -253,12 +253,12 @@ class KeyShareTests : InstrumentedTest {
|
|||
})
|
||||
|
||||
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)
|
||||
|
||||
mTestHelper.waitWithLatch { 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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -835,7 +835,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertTrue(signature.valid)
|
||||
assertNotNull(signature.device)
|
||||
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)
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
|
@ -997,7 +997,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
keysBackup.backupAllGroupSessions(null, it)
|
||||
}
|
||||
|
||||
val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!!
|
||||
val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId!!
|
||||
val oldKeyBackupVersion = keysBackup.currentBackupVersion
|
||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||
|
||||
|
|
|
@ -468,14 +468,19 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
val aliceSASLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
var matchOnce = true
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||
Log.v("TEST", "== aliceState ${uxState.name}")
|
||||
when (uxState) {
|
||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
aliceSASLatch.countDown()
|
||||
if (matchOnce) {
|
||||
matchOnce = false
|
||||
aliceSASLatch.countDown()
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
|
@ -485,14 +490,23 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
val bobSASLatch = CountDownLatch(1)
|
||||
val bobListener = object : VerificationService.Listener {
|
||||
var acceptOnce = true
|
||||
var matchOnce = true
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||
Log.v("TEST", "== bobState ${uxState.name}")
|
||||
when (uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
tx.performAccept()
|
||||
if (acceptOnce) {
|
||||
acceptOnce = false
|
||||
tx.performAccept()
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
tx.userHasVerifiedShortCode()
|
||||
if (matchOnce) {
|
||||
matchOnce = false
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
bobSASLatch.countDown()
|
||||
|
@ -579,7 +593,7 @@ class SASTest : InstrumentedTest {
|
|||
requestID!!,
|
||||
cryptoTestData.roomId,
|
||||
bobSession.myUserId,
|
||||
bobSession.sessionParams.credentials.deviceId!!,
|
||||
bobSession.sessionParams.deviceId!!,
|
||||
null)
|
||||
|
||||
bobVerificationService.beginKeyVerificationInDMs(
|
||||
|
@ -587,7 +601,7 @@ class SASTest : InstrumentedTest {
|
|||
requestID!!,
|
||||
cryptoTestData.roomId,
|
||||
aliceSession.myUserId,
|
||||
aliceSession.sessionParams.credentials.deviceId!!,
|
||||
aliceSession.sessionParams.deviceId!!,
|
||||
null)
|
||||
|
||||
// we should reach SHOW SAS on both
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -23,7 +23,6 @@ import androidx.work.WorkManager
|
|||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
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.crypto.attachments.ElementToDecrypt
|
||||
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 org.matrix.olm.OlmManager
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
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.
|
||||
* To get the singleton instance, use getInstance static method.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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.HomeServerConnectionConfig
|
||||
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.registration.RegistrationWizard
|
||||
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||
|
@ -37,6 +36,11 @@ interface AuthenticationService {
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -74,15 +78,6 @@ interface AuthenticationService {
|
|||
*/
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -21,7 +21,48 @@ package im.vector.matrix.android.api.auth.data
|
|||
* You don't have to manually instantiate it.
|
||||
*/
|
||||
data class SessionParams(
|
||||
/**
|
||||
* Please consider using shortcuts instead
|
||||
*/
|
||||
val credentials: Credentials,
|
||||
|
||||
/**
|
||||
* Please consider using shortcuts instead
|
||||
*/
|
||||
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
|
||||
)
|
||||
) {
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
data class MXCryptoConfig(
|
||||
data class MXCryptoConfig constructor(
|
||||
// Tell whether the encryption of the event content is enabled for the invited members.
|
||||
// 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".
|
||||
var enableEncryptionForInvitedMembers: Boolean = true,
|
||||
val enableEncryptionForInvitedMembers: Boolean = true,
|
||||
|
||||
/**
|
||||
* 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
|
||||
* case the application can decide to prompt the user.
|
||||
*/
|
||||
var discardRoomKeyRequestsFromUntrustedDevices : Boolean = true
|
||||
|
||||
val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true
|
||||
)
|
||||
|
|
|
@ -39,7 +39,10 @@ data class MatrixError(
|
|||
// For M_LIMIT_EXCEEDED
|
||||
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
|
||||
// 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 {
|
||||
|
@ -129,6 +132,11 @@ data class MatrixError(
|
|||
/** (Not documented yet) */
|
||||
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"
|
||||
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
||||
}
|
||||
|
|
|
@ -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.group.GroupService
|
||||
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.pushers.PushersService
|
||||
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.sync.FilterService
|
||||
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
|
||||
|
||||
/**
|
||||
|
@ -54,6 +56,7 @@ interface Session :
|
|||
SignOutService,
|
||||
FilterService,
|
||||
FileService,
|
||||
TermsService,
|
||||
ProfileService,
|
||||
PushRuleService,
|
||||
PushersService,
|
||||
|
@ -77,7 +80,7 @@ interface Session :
|
|||
* Useful shortcut to get access to the userId
|
||||
*/
|
||||
val myUserId: String
|
||||
get() = sessionParams.credentials.userId
|
||||
get() = sessionParams.userId
|
||||
|
||||
/**
|
||||
* The sessionId
|
||||
|
@ -145,6 +148,11 @@ interface Session :
|
|||
*/
|
||||
fun cryptoService(): CryptoService
|
||||
|
||||
/**
|
||||
* Returns the identity service associated with the session
|
||||
*/
|
||||
fun identityService(): IdentityService
|
||||
|
||||
/**
|
||||
* Add a listener to the session.
|
||||
* @param listener the listener to add.
|
||||
|
|
|
@ -220,3 +220,11 @@ fun Event.isImageMessage(): Boolean {
|
|||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun Event.isVideoMessage(): Boolean {
|
||||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||
MessageType.MSGTYPE_VIDEO -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
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 {
|
||||
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -17,7 +17,9 @@
|
|||
|
||||
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.session.identity.ThreePid
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
|
@ -53,4 +55,15 @@ interface ProfileService {
|
|||
*
|
||||
*/
|
||||
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>>
|
||||
}
|
||||
|
|
|
@ -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.timeline.TimelineService
|
||||
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
|
||||
|
||||
/**
|
||||
|
@ -42,6 +43,7 @@ interface Room :
|
|||
TypingService,
|
||||
MembershipService,
|
||||
StateService,
|
||||
UploadsService,
|
||||
ReportingService,
|
||||
RelationService,
|
||||
RoomCryptoService,
|
||||
|
|
|
@ -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 }
|
||||
}
|
|
@ -34,15 +34,15 @@ data class MessageEmoteContent(
|
|||
@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.
|
||||
*/
|
||||
@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.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
) : MessageContentWithFormattedBody
|
||||
|
|
|
@ -34,15 +34,15 @@ data class MessageNoticeContent(
|
|||
@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.
|
||||
*/
|
||||
@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.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
) : MessageContentWithFormattedBody
|
||||
|
|
|
@ -34,15 +34,15 @@ data class MessageTextContent(
|
|||
@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.
|
||||
*/
|
||||
@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.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
) : MessageContentWithFormattedBody
|
||||
|
|
|
@ -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)"
|
||||
}
|
||||
}
|
|
@ -58,7 +58,7 @@ interface Timeline {
|
|||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun hasMoreToLoad(direction: Direction): Boolean
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
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.EventType
|
||||
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.MessageStickerContent
|
||||
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.internal.crypto.model.event.EncryptedEventContent
|
||||
|
||||
|
@ -38,13 +40,17 @@ data class TimelineEvent(
|
|||
val localId: Long,
|
||||
val eventId: String,
|
||||
val displayIndex: Int,
|
||||
val senderName: String?,
|
||||
val isUniqueDisplayName: Boolean,
|
||||
val senderAvatar: String?,
|
||||
val senderInfo: SenderInfo,
|
||||
val annotations: EventAnnotationsSummary? = null,
|
||||
val readReceipts: List<ReadReceipt> = emptyList()
|
||||
) {
|
||||
|
||||
init {
|
||||
if (BuildConfig.DEBUG) {
|
||||
assert(eventId == root.eventId)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
* @param key the key to get the metadata
|
||||
|
|
|
@ -28,6 +28,10 @@ data class TimelineSettings(
|
|||
* A flag to filter edit events
|
||||
*/
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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>
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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.RoomSummary
|
||||
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 java.util.Locale
|
||||
|
||||
|
@ -154,3 +155,5 @@ fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlia
|
|||
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl)
|
||||
|
||||
fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
||||
|
||||
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
|
||||
|
|
|
@ -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.RealmPendingSessionStore
|
||||
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.wellknown.DefaultGetWellknownTask
|
||||
import im.vector.matrix.android.internal.auth.wellknown.DirectLoginTask
|
||||
import im.vector.matrix.android.internal.auth.wellknown.GetWellknownTask
|
||||
import im.vector.matrix.android.internal.auth.login.DefaultDirectLoginTask
|
||||
import im.vector.matrix.android.internal.auth.login.DirectLoginTask
|
||||
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
||||
import im.vector.matrix.android.internal.wellknown.WellknownModule
|
||||
import io.realm.RealmConfiguration
|
||||
import java.io.File
|
||||
|
||||
@Module
|
||||
@Module(includes = [WellknownModule::class])
|
||||
internal abstract class AuthModule {
|
||||
|
||||
@Module
|
||||
|
@ -74,9 +73,6 @@ internal abstract class AuthModule {
|
|||
@Binds
|
||||
abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator
|
||||
|
||||
@Binds
|
||||
abstract fun bindGetWellknownTask(task: DefaultGetWellknownTask): GetWellknownTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
|
||||
}
|
||||
|
|
|
@ -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.HomeServerConnectionConfig
|
||||
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.isLoginAndRegistrationSupportedBySdk
|
||||
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.session.Session
|
||||
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.auth.data.LoginFlowResponse
|
||||
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.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.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.network.RetrofitFactory
|
||||
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.exhaustive
|
||||
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.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
|
@ -87,14 +86,21 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getSession(sessionParams: SessionParams): Session? {
|
||||
return sessionManager.getOrCreateSession(sessionParams)
|
||||
override fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable {
|
||||
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 {
|
||||
pendingSessionData = null
|
||||
|
||||
return GlobalScope.launch(coroutineDispatchers.main) {
|
||||
return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
pendingSessionStore.delete()
|
||||
|
||||
val result = runCatching {
|
||||
|
@ -246,7 +252,8 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
retrofitFactory,
|
||||
coroutineDispatchers,
|
||||
sessionCreator,
|
||||
pendingSessionStore
|
||||
pendingSessionStore,
|
||||
taskExecutor.executorScope
|
||||
).also {
|
||||
currentRegistrationWizard = it
|
||||
}
|
||||
|
@ -266,7 +273,8 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
retrofitFactory,
|
||||
coroutineDispatchers,
|
||||
sessionCreator,
|
||||
pendingSessionStore
|
||||
pendingSessionStore,
|
||||
taskExecutor.executorScope
|
||||
).also {
|
||||
currentLoginWizard = it
|
||||
}
|
||||
|
@ -283,7 +291,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
pendingSessionData = pendingSessionData?.homeServerConnectionConfig
|
||||
?.let { PendingSessionData(it) }
|
||||
.also {
|
||||
GlobalScope.launch(coroutineDispatchers.main) {
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
if (it == null) {
|
||||
// Should not happen
|
||||
pendingSessionStore.delete()
|
||||
|
@ -300,7 +308,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
|
||||
pendingSessionData = null
|
||||
|
||||
GlobalScope.launch(coroutineDispatchers.main) {
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
pendingSessionStore.delete()
|
||||
}
|
||||
}
|
||||
|
@ -308,7 +316,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials,
|
||||
callback: MatrixCallback<Session>): Cancelable {
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
createSessionFromSso(credentials, homeServerConnectionConfig)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
|||
}
|
||||
return SessionParamsEntity(
|
||||
sessionParams.credentials.sessionId(),
|
||||
sessionParams.credentials.userId,
|
||||
sessionParams.userId,
|
||||
credentialsJson,
|
||||
homeServerConnectionConfigJson,
|
||||
sessionParams.isTokenValid)
|
||||
|
|
|
@ -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.task.launchToCallback
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
|
@ -47,7 +47,8 @@ internal class DefaultLoginWizard(
|
|||
retrofitFactory: RetrofitFactory,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val coroutineScope: CoroutineScope
|
||||
) : LoginWizard {
|
||||
|
||||
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
||||
|
@ -59,7 +60,7 @@ internal class DefaultLoginWizard(
|
|||
password: String,
|
||||
deviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable {
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
loginInternal(login, password, deviceName)
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +81,7 @@ internal class DefaultLoginWizard(
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +109,7 @@ internal class DefaultLoginWizard(
|
|||
callback.onFailure(IllegalStateException("developer error, no reset password in progress"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
resetPasswordMailConfirmedInternal(safeResetPasswordData)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.auth.wellknown
|
||||
package im.vector.matrix.android.internal.auth.login
|
||||
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
|
@ -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.task.launchToCallback
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
|
@ -45,7 +45,8 @@ internal class DefaultRegistrationWizard(
|
|||
private val retrofitFactory: RetrofitFactory,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val coroutineScope: CoroutineScope
|
||||
) : RegistrationWizard {
|
||||
|
||||
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 {
|
||||
val params = RegistrationParams()
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +87,7 @@ internal class DefaultRegistrationWizard(
|
|||
password = password,
|
||||
initialDeviceDisplayName = initialDeviceDisplayName
|
||||
)
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
.also {
|
||||
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
|
||||
|
@ -101,7 +102,7 @@ internal class DefaultRegistrationWizard(
|
|||
return NoOpCancellable
|
||||
}
|
||||
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
}
|
||||
|
@ -112,13 +113,13 @@ internal class DefaultRegistrationWizard(
|
|||
return NoOpCancellable
|
||||
}
|
||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||
|
||||
|
@ -131,7 +132,7 @@ internal class DefaultRegistrationWizard(
|
|||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
sendThreePid(safeCurrentThreePid)
|
||||
}
|
||||
}
|
||||
|
@ -177,13 +178,13 @@ internal class DefaultRegistrationWizard(
|
|||
callback.onFailure(IllegalStateException("developer error, no pending three pid"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(safeParam, delayMillis)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
validateThreePid(code)
|
||||
}
|
||||
}
|
||||
|
@ -199,7 +200,7 @@ internal class DefaultRegistrationWizard(
|
|||
code = code
|
||||
)
|
||||
val validationResponse = validateCodeTask.execute(ValidateCodeTask.Params(url, validationBody))
|
||||
if (validationResponse.success == true) {
|
||||
if (validationResponse.isSuccess()) {
|
||||
// The entered code is correct
|
||||
// Same than validate email
|
||||
return performRegistrationRequest(registrationParams, 3_000)
|
||||
|
@ -214,7 +215,7 @@ internal class DefaultRegistrationWizard(
|
|||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.androidsdk.rest.model.login
|
||||
package im.vector.matrix.android.internal.auth.registration
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
|
|
@ -76,7 +76,7 @@ fun RegistrationFlowResponse.toFlowResult(): FlowResult {
|
|||
this.flows?.forEach { it.stages?.mapTo(allFlowTypes) { type -> 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) {
|
||||
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<*, *>))
|
||||
}
|
||||
|
||||
if (type in completedStages ?: emptyList()) {
|
||||
if (type in completedStages.orEmpty()) {
|
||||
completedStage.add(stage)
|
||||
} else {
|
||||
missingStage.add(stage)
|
||||
|
|
|
@ -18,9 +18,12 @@ package im.vector.matrix.android.internal.auth.registration
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SuccessResult(
|
||||
@Json(name = "success")
|
||||
val success: Boolean?
|
||||
)
|
||||
) {
|
||||
fun isSuccess() = success.orFalse()
|
||||
}
|
||||
|
|
|
@ -262,7 +262,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
|
||||
override fun onSuccess(data: DevicesListResponse) {
|
||||
// Save in local DB
|
||||
cryptoStore.saveMyDevicesInfo(data.devices ?: emptyList())
|
||||
cryptoStore.saveMyDevicesInfo(data.devices.orEmpty())
|
||||
callback.onSuccess(data)
|
||||
}
|
||||
}
|
||||
|
@ -446,7 +446,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
}
|
||||
|
||||
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||
return cryptoStore.getUserDeviceList(userId) ?: emptyList()
|
||||
return cryptoStore.getUserDeviceList(userId).orEmpty()
|
||||
}
|
||||
|
||||
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
|
||||
|
|
|
@ -137,7 +137,7 @@ internal class OneTimeKeysUploader @Inject constructor(
|
|||
private suspend fun uploadOneTimeKeys(oneTimeKeys: Map<String, Map<String, String>>?): KeysUploadResponse {
|
||||
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) ->
|
||||
val k = mutableMapOf<String, Any>()
|
||||
|
|
|
@ -34,7 +34,7 @@ internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val o
|
|||
suspend fun handle(users: List<String>): MXUsersDevicesMap<MXOlmSessionResult> {
|
||||
Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users")
|
||||
val devicesByUser = users.associateWith { userId ->
|
||||
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
|
||||
val devices = cryptoStore.getUserDevices(userId)?.values.orEmpty()
|
||||
|
||||
devices.filter {
|
||||
// Don't bother setting up session to ourself
|
||||
|
|
|
@ -103,7 +103,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
senderCurve25519Key = olmDecryptionResult.senderKey,
|
||||
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
|
||||
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
|
||||
?: emptyList()
|
||||
.orEmpty()
|
||||
)
|
||||
} else {
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||
|
|
|
@ -44,7 +44,7 @@ internal class MXOlmEncryption(
|
|||
ensureSession(userIds)
|
||||
val deviceInfos = ArrayList<CryptoDeviceInfo>()
|
||||
for (userId in userIds) {
|
||||
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
|
||||
val devices = cryptoStore.getUserDevices(userId)?.values.orEmpty()
|
||||
for (device in devices) {
|
||||
val key = device.identityKey()
|
||||
if (key == olmDevice.deviceCurve25519Key) {
|
||||
|
|
|
@ -199,7 +199,7 @@ internal object MXEncryptedAttachments {
|
|||
.replace('_', '/')
|
||||
}
|
||||
|
||||
private fun base64ToBase64Url(base64: String): String {
|
||||
internal fun base64ToBase64Url(base64: String): String {
|
||||
return base64.replace("\n".toRegex(), "")
|
||||
.replace("\\+".toRegex(), "-")
|
||||
.replace('/', '_')
|
||||
|
|
|
@ -47,7 +47,7 @@ internal object CryptoInfoMapper {
|
|||
return CryptoCrossSigningKey(
|
||||
userId = keyInfo.userId,
|
||||
usages = keyInfo.usages,
|
||||
keys = keyInfo.keys ?: emptyMap(),
|
||||
keys = keyInfo.keys.orEmpty(),
|
||||
signatures = keyInfo.signatures,
|
||||
trustLevel = null
|
||||
)
|
||||
|
|
|
@ -450,7 +450,7 @@ internal class RealmCryptoStore @Inject constructor(
|
|||
}
|
||||
)
|
||||
return Transformations.map(liveData) {
|
||||
it.firstOrNull() ?: emptyList()
|
||||
it.firstOrNull().orEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -480,7 +480,7 @@ internal class RealmCryptoStore @Inject constructor(
|
|||
}
|
||||
)
|
||||
return Transformations.map(liveData) {
|
||||
it.firstOrNull() ?: emptyList()
|
||||
it.firstOrNull().orEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -200,6 +200,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
|||
}
|
||||
|
||||
private fun migrateTo3(realm: DynamicRealm) {
|
||||
Timber.d("Step 2 -> 3")
|
||||
Timber.d("Updating CryptoMetadataEntity table")
|
||||
realm.schema.get("CryptoMetadataEntity")
|
||||
?.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) {
|
||||
Timber.d("Step 3 -> 4")
|
||||
Timber.d("Updating KeyInfoEntity table")
|
||||
val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
|
||||
try {
|
||||
|
@ -238,6 +240,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
|||
}
|
||||
|
||||
private fun migrateTo5(realm: DynamicRealm) {
|
||||
Timber.d("Step 4 -> 5")
|
||||
realm.schema.create("MyDeviceLastSeenInfoEntity")
|
||||
.addField(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, String::class.java)
|
||||
.addPrimaryKey(MyDeviceLastSeenInfoEntityFields.DEVICE_ID)
|
||||
|
|
|
@ -78,7 +78,7 @@ internal open class OutgoingGossipingRequestEntity(
|
|||
GossipRequestType.KEY -> {
|
||||
OutgoingRoomKeyRequest(
|
||||
requestBody = getRequestedKeyInfo(),
|
||||
recipients = getRecipients() ?: emptyMap(),
|
||||
recipients = getRecipients().orEmpty(),
|
||||
requestId = requestId ?: "",
|
||||
state = requestState
|
||||
)
|
||||
|
@ -86,7 +86,7 @@ internal open class OutgoingGossipingRequestEntity(
|
|||
GossipRequestType.SECRET -> {
|
||||
OutgoingSecretRequest(
|
||||
secretName = getRequestedSecretName(),
|
||||
recipients = getRecipients() ?: emptyMap(),
|
||||
recipients = getRecipients().orEmpty(),
|
||||
requestId = requestId ?: "",
|
||||
state = requestState
|
||||
)
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.tools
|
|||
import org.matrix.olm.OlmPkDecryption
|
||||
import org.matrix.olm.OlmPkEncryption
|
||||
import org.matrix.olm.OlmPkSigning
|
||||
import org.matrix.olm.OlmUtility
|
||||
|
||||
fun <T> withOlmEncryption(block: (OlmPkEncryption) -> T): T {
|
||||
val olmPkEncryption = OlmPkEncryption()
|
||||
|
@ -46,3 +47,12 @@ fun <T> withOlmSigning(block: (OlmPkSigning) -> T): T {
|
|||
olmPkSigning.releaseSigning()
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> withOlmUtility(block: (OlmUtility) -> T): T {
|
||||
val olmUtility = OlmUtility()
|
||||
try {
|
||||
return block(olmUtility)
|
||||
} finally {
|
||||
olmUtility.releaseUtility()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -198,18 +198,8 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
// using the result as the shared secret.
|
||||
|
||||
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:
|
||||
// - 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)
|
||||
|
||||
shortCodeBytes = calculateSASBytes()
|
||||
|
||||
if (BuildConfig.LOG_PRIVATE_DATA) {
|
||||
Timber.v("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}")
|
||||
|
@ -219,6 +209,35 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
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) {
|
||||
Timber.v("## SAS I: received mac for request id:$transactionId")
|
||||
// Check for state?
|
||||
|
|
|
@ -121,7 +121,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
// }
|
||||
//
|
||||
// val requestMessage = KeyVerificationRequest(
|
||||
// fromDevice = session.sessionParams.credentials.deviceId ?: "",
|
||||
// fromDevice = session.sessionParams.deviceId ?: "",
|
||||
// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
||||
// timestamp = System.currentTimeMillis().toInt(),
|
||||
// transactionId = transactionId
|
||||
|
@ -193,18 +193,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
|
||||
if (accepted!!.commitment.equals(otherCommitment)) {
|
||||
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:
|
||||
// - 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)
|
||||
shortCodeBytes = calculateSASBytes()
|
||||
state = VerificationTxState.ShortCodeReady
|
||||
} else {
|
||||
// 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) {
|
||||
Timber.v("## SAS O: onKeyVerificationMac id:$transactionId")
|
||||
// There is starting to be a huge amount of state / race here :/
|
||||
if (state != VerificationTxState.OnKeyReceived
|
||||
&& state != VerificationTxState.ShortCodeReady
|
||||
&& state != VerificationTxState.ShortCodeAccepted
|
||||
&& state != VerificationTxState.KeySent
|
||||
&& state != VerificationTxState.SendingMac
|
||||
&& 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)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -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.UserId
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
|
@ -104,7 +104,8 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
private val verificationTransportRoomMessageFactory: VerificationTransportRoomMessageFactory,
|
||||
private val verificationTransportToDeviceFactory: VerificationTransportToDeviceFactory,
|
||||
private val crossSigningService: CrossSigningService,
|
||||
private val cryptoCoroutineScope: CoroutineScope
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : DefaultVerificationTransaction.Listener, VerificationService {
|
||||
|
||||
private val uiHandler = Handler(Looper.getMainLooper())
|
||||
|
@ -161,7 +162,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
fun onRoomEvent(event: Event) {
|
||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
when (event.getClearType()) {
|
||||
EventType.KEY_VERIFICATION_START -> {
|
||||
onRoomStartRequestReceived(event)
|
||||
|
@ -301,7 +302,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
// We don't want to block here
|
||||
val otherDeviceId = validRequestInfo.fromDevice
|
||||
|
||||
GlobalScope.launch {
|
||||
cryptoCoroutineScope.launch {
|
||||
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
|
||||
Timber.e("## Verification device $otherDeviceId is not known")
|
||||
}
|
||||
|
@ -340,7 +341,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
// We don't want to block here
|
||||
GlobalScope.launch {
|
||||
taskExecutor.executorScope.launch {
|
||||
if (checkKeysAreDownloaded(senderId, validRequestInfo.fromDevice) == null) {
|
||||
Timber.e("## SAS Verification device ${validRequestInfo.fromDevice} is not known")
|
||||
}
|
||||
|
@ -610,7 +611,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||
|
||||
if (validCancelReq == null) {
|
||||
// ignore
|
||||
Timber.e("## SAS Received invalid key request")
|
||||
Timber.e("## SAS Received invalid cancel request")
|
||||
// TODO should we cancel?
|
||||
return
|
||||
}
|
||||
|
|
|
@ -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.OutgoingGossipingRequestManager
|
||||
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.extensions.toUnsignedInt
|
||||
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 = "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
|
||||
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
|
||||
val KNOWN_HASHES = listOf("sha256")
|
||||
// ordered by preferred order
|
||||
|
|
|
@ -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.WorkManagerProvider
|
||||
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.worker.WorkerParamsFactory
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
|
@ -66,7 +67,8 @@ internal class VerificationTransportRoomMessage(
|
|||
private val userDeviceId: String?,
|
||||
private val roomId: String,
|
||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||
private val tx: DefaultVerificationTransaction?
|
||||
private val tx: DefaultVerificationTransaction?,
|
||||
private val coroutineScope: CoroutineScope
|
||||
) : VerificationTransport {
|
||||
|
||||
override fun <T> sendToOther(type: String,
|
||||
|
@ -131,7 +133,7 @@ internal class VerificationTransportRoomMessage(
|
|||
}
|
||||
|
||||
// TODO listen to DB to get synced info
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
coroutineScope.launch(Dispatchers.Main) {
|
||||
workLiveData.observeForever(observer)
|
||||
}
|
||||
}
|
||||
|
@ -212,7 +214,7 @@ internal class VerificationTransportRoomMessage(
|
|||
}
|
||||
|
||||
// TODO listen to DB to get synced info
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
coroutineScope.launch(Dispatchers.Main) {
|
||||
workLiveData.observeForever(observer)
|
||||
}
|
||||
}
|
||||
|
@ -265,7 +267,7 @@ internal class VerificationTransportRoomMessage(
|
|||
}
|
||||
|
||||
// TODO listen to DB to get synced info
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
coroutineScope.launch(Dispatchers.Main) {
|
||||
workLiveData.observeForever(observer)
|
||||
}
|
||||
}
|
||||
|
@ -384,9 +386,19 @@ internal class VerificationTransportRoomMessageFactory @Inject constructor(
|
|||
private val userId: String,
|
||||
@DeviceId
|
||||
private val deviceId: String?,
|
||||
private val localEchoEventFactory: LocalEchoEventFactory) {
|
||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,10 +60,9 @@ internal fun ChunkEntity.merge(roomId: String, chunkToMerge: ChunkEntity, direct
|
|||
chunkToMerge.stateEvents.forEach { stateEvent ->
|
||||
addStateEvent(roomId, stateEvent, direction)
|
||||
}
|
||||
return eventsToMerge
|
||||
.forEach {
|
||||
addTimelineEventFromMerge(localRealm, it, direction)
|
||||
}
|
||||
eventsToMerge.forEach {
|
||||
addTimelineEventFromMerge(localRealm, it, direction)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity, direction: PaginationDirection) {
|
||||
|
|
|
@ -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.TimelineEventEntityFields
|
||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||
import io.realm.Realm
|
||||
|
||||
internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
|
||||
|
@ -28,3 +29,11 @@ internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
|
|||
currentIdNum.toLong() + 1
|
||||
}
|
||||
}
|
||||
|
||||
internal fun TimelineEventEntity.deleteOnCascade() {
|
||||
assertIsManaged()
|
||||
root?.deleteFromRealm()
|
||||
annotations?.deleteFromRealm()
|
||||
readReceipts?.deleteFromRealm()
|
||||
deleteFromRealm()
|
||||
}
|
||||
|
|
|
@ -20,21 +20,16 @@ import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
|
|||
import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity
|
||||
|
||||
/**
|
||||
* HomeServerCapabilitiesEntity <-> HomeSeverCapabilities
|
||||
* HomeServerCapabilitiesEntity -> HomeSeverCapabilities
|
||||
*/
|
||||
internal object HomeServerCapabilitiesMapper {
|
||||
|
||||
fun map(entity: HomeServerCapabilitiesEntity): HomeServerCapabilities {
|
||||
return HomeServerCapabilities(
|
||||
canChangePassword = entity.canChangePassword,
|
||||
maxUploadFileSize = entity.maxUploadFileSize
|
||||
)
|
||||
}
|
||||
|
||||
fun map(domain: HomeServerCapabilities): HomeServerCapabilitiesEntity {
|
||||
return HomeServerCapabilitiesEntity(
|
||||
canChangePassword = domain.canChangePassword,
|
||||
maxUploadFileSize = domain.maxUploadFileSize
|
||||
maxUploadFileSize = entity.maxUploadFileSize,
|
||||
lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
|
||||
defaultIdentityServerUrl = entity.defaultIdentityServerUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ internal object PushRulesMapper {
|
|||
|
||||
private fun fromActionStr(actionsStr: String?): List<Any> {
|
||||
try {
|
||||
return actionsStr?.let { moshiActionsAdapter.fromJson(it) } ?: emptyList()
|
||||
return actionsStr?.let { moshiActionsAdapter.fromJson(it) }.orEmpty()
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "## failed to map push rule actions <$actionsStr>")
|
||||
return emptyList()
|
||||
|
|
|
@ -49,7 +49,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
|||
membership = roomSummaryEntity.membership,
|
||||
versioningState = roomSummaryEntity.versioningState,
|
||||
readMarkerId = roomSummaryEntity.readMarkerId,
|
||||
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(),
|
||||
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) }.orEmpty(),
|
||||
canonicalAlias = roomSummaryEntity.canonicalAlias,
|
||||
aliases = roomSummaryEntity.aliases.toList(),
|
||||
isEncrypted = roomSummaryEntity.isEncrypted,
|
||||
|
|
|
@ -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.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.internal.database.model.TimelineEventEntity
|
||||
import javax.inject.Inject
|
||||
|
@ -41,15 +41,18 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS
|
|||
annotations = timelineEventEntity.annotations?.asDomain(),
|
||||
localId = timelineEventEntity.localId,
|
||||
displayIndex = timelineEventEntity.displayIndex,
|
||||
senderName = timelineEventEntity.senderName,
|
||||
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
||||
senderAvatar = timelineEventEntity.senderAvatar,
|
||||
senderInfo = SenderInfo(
|
||||
userId = timelineEventEntity.root?.sender ?: "",
|
||||
displayName = timelineEventEntity.senderName,
|
||||
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
||||
avatarUrl = timelineEventEntity.senderAvatar
|
||||
),
|
||||
readReceipts = readReceipts
|
||||
?.distinctBy {
|
||||
it.user
|
||||
}?.sortedByDescending {
|
||||
it.originServerTs
|
||||
} ?: emptyList()
|
||||
}.orEmpty()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,15 +23,20 @@ import io.realm.annotations.Index
|
|||
import io.realm.annotations.LinkingObjects
|
||||
|
||||
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,
|
||||
var stateEvents: RealmList<EventEntity> = RealmList(),
|
||||
var timelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
||||
// Only one chunk will have isLastForward == true
|
||||
@Index var isLastForward: Boolean = false,
|
||||
@Index var isLastBackward: Boolean = false
|
||||
) : RealmObject() {
|
||||
|
||||
fun identifier() = "${prevToken}_$nextToken"
|
||||
|
||||
// If true, then this chunk was previously a last forward chunk
|
||||
fun hasBeenALastForwardChunk() = nextToken == null && !isLastForward
|
||||
|
||||
@LinkingObjects("chunks")
|
||||
val room: RealmResults<RoomEntity>? = null
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ import io.realm.RealmObject
|
|||
internal open class HomeServerCapabilitiesEntity(
|
||||
var canChangePassword: Boolean = true,
|
||||
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
|
||||
var lastVersionIdentityServerSupported: Boolean = false,
|
||||
var defaultIdentityServerUrl: String? = null,
|
||||
var lastUpdatedTimestamp: Long = 0L
|
||||
) : RealmObject() {
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ import io.realm.annotations.RealmModule
|
|||
UserEntity::class,
|
||||
IgnoredUserEntity::class,
|
||||
BreadcrumbsEntity::class,
|
||||
UserThreePidEntity::class,
|
||||
EventAnnotationsSummaryEntity::class,
|
||||
ReactionAggregatedSummaryEntity::class,
|
||||
EditAggregatedSummaryEntity::class,
|
||||
|
|
|
@ -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()
|
|
@ -41,7 +41,7 @@ internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken:
|
|||
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)
|
||||
.equalTo(ChunkEntityFields.IS_LAST_FORWARD, true)
|
||||
.findFirst()
|
||||
|
|
|
@ -35,7 +35,7 @@ internal fun FilterEntity.Companion.get(realm: Realm): FilterEntity? {
|
|||
internal fun FilterEntity.Companion.getOrCreate(realm: Realm): FilterEntity {
|
||||
return get(realm) ?: realm.createObject<FilterEntity>()
|
||||
.apply {
|
||||
filterBodyJson = FilterFactory.createDefaultFilterBody().toJSONString()
|
||||
filterBodyJson = FilterFactory.createDefaultFilter().toJSONString()
|
||||
roomEventFilterJson = FilterFactory.createDefaultRoomFilter().toJSONString()
|
||||
filterId = ""
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ internal fun isEventRead(monarchy: Monarchy,
|
|||
var isEventRead = false
|
||||
|
||||
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)
|
||||
isEventRead = if (eventToCheck == null || eventToCheck.root?.sender == userId) {
|
||||
true
|
||||
|
|
|
@ -59,11 +59,11 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
|
|||
filterTypes: List<String> = emptyList()): TimelineEventEntity? {
|
||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null
|
||||
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) {
|
||||
liveEvents
|
||||
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE)
|
||||
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.RESPONSE_TYPE)
|
||||
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
|
||||
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
|
||||
}
|
||||
val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) {
|
||||
sendingTimelineEvents
|
||||
|
|
|
@ -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":*}"""
|
||||
}
|
||||
}
|
|
@ -20,8 +20,12 @@ import javax.inject.Qualifier
|
|||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Authenticated
|
||||
internal annotation class Authenticated
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Unauthenticated
|
||||
internal annotation class AuthenticatedIdentity
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class Unauthenticated
|
||||
|
|
|
@ -20,12 +20,16 @@ import javax.inject.Qualifier
|
|||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class AuthDatabase
|
||||
internal annotation class AuthDatabase
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class SessionDatabase
|
||||
internal annotation class SessionDatabase
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class CryptoDatabase
|
||||
internal annotation class CryptoDatabase
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class IdentityDatabase
|
||||
|
|
|
@ -20,16 +20,16 @@ import javax.inject.Qualifier
|
|||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class SessionFilesDirectory
|
||||
internal annotation class SessionFilesDirectory
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class SessionCacheDirectory
|
||||
internal annotation class SessionCacheDirectory
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class CacheDirectory
|
||||
internal annotation class CacheDirectory
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class ExternalFilesDirectory
|
||||
internal annotation class ExternalFilesDirectory
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.squareup.moshi.Moshi
|
|||
import dagger.Module
|
||||
import dagger.Provides
|
||||
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.UserAgentInterceptor
|
||||
import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
|
||||
|
@ -64,7 +65,8 @@ internal object NetworkModule {
|
|||
@Provides
|
||||
@JvmStatic
|
||||
@Unauthenticated
|
||||
fun providesOkHttpClient(stethoInterceptor: StethoInterceptor,
|
||||
fun providesOkHttpClient(matrixConfiguration: MatrixConfiguration,
|
||||
stethoInterceptor: StethoInterceptor,
|
||||
timeoutInterceptor: TimeOutInterceptor,
|
||||
userAgentInterceptor: UserAgentInterceptor,
|
||||
httpLoggingInterceptor: HttpLoggingInterceptor,
|
||||
|
@ -82,6 +84,9 @@ internal object NetworkModule {
|
|||
if (BuildConfig.LOG_PRIVATE_DATA) {
|
||||
addInterceptor(curlLoggingInterceptor)
|
||||
}
|
||||
matrixConfiguration.proxy?.let {
|
||||
proxy(it)
|
||||
}
|
||||
}
|
||||
.addInterceptor(okReplayInterceptor)
|
||||
.build()
|
||||
|
|
|
@ -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)
|
|
@ -16,20 +16,16 @@
|
|||
|
||||
package im.vector.matrix.android.internal.network
|
||||
|
||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AccessTokenInterceptor @Inject constructor(
|
||||
@SessionId private val sessionId: String,
|
||||
private val sessionParamsStore: SessionParamsStore) : Interceptor {
|
||||
internal class AccessTokenInterceptor(private val accessTokenProvider: AccessTokenProvider) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
var request = chain.request()
|
||||
|
||||
accessToken?.let {
|
||||
accessTokenProvider.getToken()?.let {
|
||||
val newRequestBuilder = request.newBuilder()
|
||||
// Add the access token to all requests if it is set
|
||||
newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer $it")
|
||||
|
@ -38,7 +34,4 @@ internal class AccessTokenInterceptor @Inject constructor(
|
|||
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
private val accessToken
|
||||
get() = sessionParamsStore.get(sessionId)?.credentials?.accessToken
|
||||
}
|
||||
|
|
|
@ -28,8 +28,9 @@ internal object NetworkConstants {
|
|||
const val URI_API_MEDIA_PREFIX_PATH_R0 = "$URI_API_MEDIA_PREFIX_PATH/r0/"
|
||||
|
||||
// Identity server
|
||||
const val URI_IDENTITY_PATH = "_matrix/identity/api/v1/"
|
||||
const val URI_IDENTITY_PATH_V2 = "_matrix/identity/v2/"
|
||||
const val URI_IDENTITY_PREFIX_PATH = "_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/"
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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?
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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.Unauthenticated
|
||||
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.toCancelable
|
||||
import im.vector.matrix.android.internal.util.writeToFile
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
|
@ -51,7 +51,9 @@ internal class DefaultFileService @Inject constructor(
|
|||
private val contentUrlResolver: ContentUrlResolver,
|
||||
@Unauthenticated
|
||||
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
|
||||
|
@ -63,7 +65,7 @@ internal class DefaultFileService @Inject constructor(
|
|||
url: String?,
|
||||
elementToDecrypt: ElementToDecrypt?,
|
||||
callback: MatrixCallback<File>): Cancelable {
|
||||
return GlobalScope.launch(coroutineDispatchers.main) {
|
||||
return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
withContext(coroutineDispatchers.io) {
|
||||
Try {
|
||||
val folder = File(sessionCacheDirectory, "MF")
|
||||
|
|
|
@ -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.sync.FilterService
|
||||
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.internal.auth.SessionParamsStore
|
||||
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.di.SessionId
|
||||
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.sync.SyncTokenStore
|
||||
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.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
|
@ -82,6 +85,7 @@ internal class DefaultSession @Inject constructor(
|
|||
private val signOutService: Lazy<SignOutService>,
|
||||
private val pushRuleService: Lazy<PushRuleService>,
|
||||
private val pushersService: Lazy<PushersService>,
|
||||
private val termsService: Lazy<TermsService>,
|
||||
private val cryptoService: Lazy<DefaultCryptoService>,
|
||||
private val fileService: Lazy<FileService>,
|
||||
private val secureStorageService: Lazy<SecureStorageService>,
|
||||
|
@ -97,8 +101,11 @@ internal class DefaultSession @Inject constructor(
|
|||
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
||||
private val accountService: Lazy<AccountService>,
|
||||
private val timelineEventDecryptor: TimelineEventDecryptor,
|
||||
private val shieldTrustUpdater: ShieldTrustUpdater)
|
||||
: Session,
|
||||
private val shieldTrustUpdater: ShieldTrustUpdater,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val defaultIdentityService: DefaultIdentityService,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : Session,
|
||||
RoomService by roomService.get(),
|
||||
RoomDirectoryService by roomDirectoryService.get(),
|
||||
GroupService by groupService.get(),
|
||||
|
@ -108,6 +115,7 @@ internal class DefaultSession @Inject constructor(
|
|||
PushRuleService by pushRuleService.get(),
|
||||
PushersService by pushersService.get(),
|
||||
FileService by fileService.get(),
|
||||
TermsService by termsService.get(),
|
||||
InitialSyncProgressService by initialSyncProgressService.get(),
|
||||
SecureStorageService by secureStorageService.get(),
|
||||
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
|
||||
|
@ -133,6 +141,7 @@ internal class DefaultSession @Inject constructor(
|
|||
eventBus.register(this)
|
||||
timelineEventDecryptor.start()
|
||||
shieldTrustUpdater.start()
|
||||
defaultIdentityService.start()
|
||||
}
|
||||
|
||||
override fun requireBackgroundSync() {
|
||||
|
@ -175,6 +184,10 @@ internal class DefaultSession @Inject constructor(
|
|||
isOpen = false
|
||||
eventBus.unregister(this)
|
||||
shieldTrustUpdater.stop()
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
// This has to be done on main thread
|
||||
defaultIdentityService.stop()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSyncStateLive(): LiveData<SyncState> {
|
||||
|
@ -204,7 +217,7 @@ internal class DefaultSession @Inject constructor(
|
|||
if (globalError is GlobalError.InvalidToken
|
||||
&& globalError.softLogout) {
|
||||
// Mark the token has invalid
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
taskExecutor.executorScope.launch(Dispatchers.IO) {
|
||||
sessionParamsStore.setTokenInvalid(sessionId)
|
||||
}
|
||||
}
|
||||
|
@ -218,6 +231,8 @@ internal class DefaultSession @Inject constructor(
|
|||
|
||||
override fun cryptoService(): CryptoService = cryptoService.get()
|
||||
|
||||
override fun identityService() = defaultIdentityService
|
||||
|
||||
override fun addListener(listener: Session.Listener) {
|
||||
sessionListeners.addListener(listener)
|
||||
}
|
||||
|
@ -228,6 +243,6 @@ internal class DefaultSession @Inject constructor(
|
|||
|
||||
// For easy debugging
|
||||
override fun toString(): String {
|
||||
return "$myUserId - ${sessionParams.credentials.deviceId}"
|
||||
return "$myUserId - ${sessionParams.deviceId}"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.GroupModule
|
||||
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.profile.ProfileModule
|
||||
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.SyncTokenStore
|
||||
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.accountdata.AccountDataModule
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
|
@ -72,6 +74,8 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
|||
CryptoModule::class,
|
||||
PushersModule::class,
|
||||
OpenIdModule::class,
|
||||
IdentityModule::class,
|
||||
TermsModule::class,
|
||||
AccountDataModule::class,
|
||||
ProfileModule::class,
|
||||
SessionAssistedInjectModule::class,
|
||||
|
|
|
@ -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.UserMd5
|
||||
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.FallbackNetworkCallbackStrategy
|
||||
import im.vector.matrix.android.internal.network.NetworkCallbackStrategy
|
||||
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
||||
import im.vector.matrix.android.internal.network.PreferredNetworkCallbackStrategy
|
||||
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.homeserver.DefaultHomeServerCapabilitiesService
|
||||
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
|
||||
|
@ -175,21 +176,8 @@ internal abstract class SessionModule {
|
|||
@SessionScope
|
||||
@Authenticated
|
||||
fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
|
||||
accessTokenInterceptor: AccessTokenInterceptor): OkHttpClient {
|
||||
return okHttpClient.newBuilder()
|
||||
.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()
|
||||
@Authenticated accessTokenProvider: AccessTokenProvider): OkHttpClient {
|
||||
return okHttpClient.addAccessTokenInterceptor(accessTokenProvider)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
@ -233,44 +221,48 @@ internal abstract class SessionModule {
|
|||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
@Authenticated
|
||||
abstract fun bindAccessTokenProvider(provider: HomeserverAccessTokenProvider): AccessTokenProvider
|
||||
|
||||
@Binds
|
||||
abstract fun bindSession(session: DefaultSession): Session
|
||||
|
||||
@Binds
|
||||
abstract fun bindNetworkConnectivityChecker(networkConnectivityChecker: DefaultNetworkConnectivityChecker): NetworkConnectivityChecker
|
||||
abstract fun bindNetworkConnectivityChecker(checker: DefaultNetworkConnectivityChecker): NetworkConnectivityChecker
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindGroupSummaryUpdater(groupSummaryUpdater: GroupSummaryUpdater): LiveEntityObserver
|
||||
abstract fun bindGroupSummaryUpdater(updater: GroupSummaryUpdater): LiveEntityObserver
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindEventsPruner(eventsPruner: EventsPruner): LiveEntityObserver
|
||||
abstract fun bindEventsPruner(pruner: EventsPruner): LiveEntityObserver
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindEventRelationsAggregationUpdater(eventRelationsAggregationUpdater: EventRelationsAggregationUpdater): LiveEntityObserver
|
||||
abstract fun bindEventRelationsAggregationUpdater(updater: EventRelationsAggregationUpdater): LiveEntityObserver
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindRoomTombstoneEventLiveObserver(roomTombstoneEventLiveObserver: RoomTombstoneEventLiveObserver): LiveEntityObserver
|
||||
abstract fun bindRoomTombstoneEventLiveObserver(observer: RoomTombstoneEventLiveObserver): LiveEntityObserver
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindRoomCreateEventLiveObserver(roomCreateEventLiveObserver: RoomCreateEventLiveObserver): LiveEntityObserver
|
||||
abstract fun bindRoomCreateEventLiveObserver(observer: RoomCreateEventLiveObserver): LiveEntityObserver
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindVerificationMessageLiveObserver(verificationMessageLiveObserver: VerificationMessageLiveObserver): LiveEntityObserver
|
||||
abstract fun bindVerificationMessageLiveObserver(observer: VerificationMessageLiveObserver): LiveEntityObserver
|
||||
|
||||
@Binds
|
||||
abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService
|
||||
abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService
|
||||
|
||||
@Binds
|
||||
abstract fun bindSecureStorageService(secureStorageService: DefaultSecureStorageService): SecureStorageService
|
||||
abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService
|
||||
|
||||
@Binds
|
||||
abstract fun bindHomeServerCapabilitiesService(homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService
|
||||
abstract fun bindHomeServerCapabilitiesService(service: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService
|
||||
|
||||
@Binds
|
||||
abstract fun bindAccountDataService(service: DefaultAccountDataService): AccountDataService
|
||||
|
|
|
@ -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.network.executeRequest
|
||||
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 org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> {
|
||||
|
@ -34,6 +36,7 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
|
|||
private val accountAPI: AccountAPI,
|
||||
private val eventBus: EventBus,
|
||||
@UserId private val userId: String,
|
||||
private val identityDisconnectTask: IdentityDisconnectTask,
|
||||
private val cleanupSession: CleanupSession
|
||||
) : DeactivateAccountTask {
|
||||
|
||||
|
@ -44,6 +47,10 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue