diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml index a34f4219d9..10c12796c0 100644 --- a/.idea/dictionaries/bmarty.xml +++ b/.idea/dictionaries/bmarty.xml @@ -20,6 +20,7 @@ signin signout signup + threepid \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 9be8d85871..628d9759b1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,22 +1,41 @@ -Changes in RiotX 0.12.0 (2019-XX-XX) +Changes in RiotX 0.13.0 (2020-XX-XX) =================================================== Features ✨: + - Send and render typing events (#564) + +Improvements 🙌: + - Render events m.room.encryption and m.room.guest_access in the timeline + +Other changes: - +Bugfix 🐛: + - + +Translations 🗣: + - + +Build 🧱: + - Change the way versionCode is computed (#827) + +Changes in RiotX 0.12.0 (2020-01-09) +=================================================== + Improvements 🙌: - The initial sync is now handled by a foreground service - Render aliases and canonical alias change in the timeline - - Fix autocompletion issues and add support for rooms and groups - Introduce developer mode in the settings (#745, #796) - Improve devices list screen - Add settings for rageshake sensibility - Fix autocompletion issues and add support for rooms, groups, and emoji (#780) - Show skip to bottom FAB while scrolling down (#752) + - Enable encryption on a room, SDK part (#212) Other changes: - Change the way RiotX identifies a session to allow the SDK to support several sessions with the same user (#800) - Exclude play-services-oss-licenses library from F-Droid build (#814) + - Email domain can be limited on some homeservers, i18n of the displayed error (#754) Bugfix 🐛: - Fix crash when opening room creation screen from the room filtering screen @@ -26,12 +45,6 @@ Bugfix 🐛: - Fix matrix.org room directory not being browsable (#807) - Hide non working settings (#751) -Translations 🗣: - - - -Build 🧱: - - - Changes in RiotX 0.11.0 (2019-12-19) =================================================== @@ -290,7 +303,7 @@ Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-a ======================================================= -Changes in RiotX 0.0.0 (2019-XX-XX) +Changes in RiotX 0.0.0 (2020-XX-XX) =================================================== Features ✨: diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/account/AccountCreationTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/account/AccountCreationTest.kt new file mode 100644 index 0000000000..c44ac9c47b --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/account/AccountCreationTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright 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.account + +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.common.CommonTestHelper +import im.vector.matrix.android.common.CryptoTestHelper +import im.vector.matrix.android.common.SessionTestParams +import im.vector.matrix.android.common.TestConstants +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class AccountCreationTest : InstrumentedTest { + + private val commonTestHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + + @Test + fun createAccountTest() { + val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) + + commonTestHelper.signout(session) + + session.close() + } + + @Test + fun createAccountAndLoginAgainTest() { + val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) + + // Log again to the same account + val session2 = commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true)) + + session.close() + session2.close() + } + + @Test + fun simpleE2eTest() { + val res = cryptoTestHelper.doE2ETestWithAliceInARoom() + + res.close() + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt new file mode 100644 index 0000000000..b16c865765 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt @@ -0,0 +1,278 @@ +/* + * Copyright 2016 OpenMarket Ltd + * Copyright 2018 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.common + +import android.content.Context +import android.net.Uri +import im.vector.matrix.android.api.Matrix +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.MatrixConfiguration +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.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.room.Room +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 +import org.junit.Assert.* +import java.util.* +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +/** + * This class exposes methods to be used in common cases + * Registration, login, Sync, Sending messages... + */ +class CommonTestHelper(context: Context) { + + val matrix: Matrix + + init { + Matrix.initialize(context, MatrixConfiguration("TestFlavor")) + + matrix = Matrix.getInstance(context) + } + + fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session { + return createAccount(userNamePrefix, TestConstants.PASSWORD, testParams) + } + + fun logIntoAccount(userId: String, testParams: SessionTestParams): Session { + return logIntoAccount(userId, TestConstants.PASSWORD, testParams) + } + + /** + * Create a Home server configuration, with Http connection allowed for test + */ + fun createHomeServerConfig(): HomeServerConnectionConfig { + return HomeServerConnectionConfig.Builder() + .withHomeServerUri(Uri.parse(TestConstants.TESTS_HOME_SERVER_URL)) + .build() + } + + /** + * This methods init the event stream and check for initial sync + * + * @param session the session to sync + */ + fun syncSession(session: Session) { + // val lock = CountDownLatch(1) + + // val observer = androidx.lifecycle.Observer { syncState -> + // if (syncState is SyncState.Idle) { + // lock.countDown() + // } + // } + + // TODO observe? + // while (session.syncState().value !is SyncState.Idle) { + // sleep(100) + // } + + session.open() + session.startSync(true) + // await(lock) + // session.syncState().removeObserver(observer) + } + + /** + * Sends text messages in a room + * + * @param room the room where to send the messages + * @param message the message to send + * @param nbOfMessages the number of time the message will be sent + */ + fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List { + val sentEvents = ArrayList(nbOfMessages) + val latch = CountDownLatch(nbOfMessages) + val onEventSentListener = object : Timeline.Listener { + override fun onTimelineFailure(throwable: Throwable) { + } + + override fun onTimelineUpdated(snapshot: List) { + // TODO Count only new messages? + if (snapshot.count { it.root.type == EventType.MESSAGE } == nbOfMessages) { + sentEvents.addAll(snapshot.filter { it.root.type == EventType.MESSAGE }) + latch.countDown() + } + } + } + val timeline = room.createTimeline(null, TimelineSettings(10)) + timeline.addListener(onEventSentListener) + for (i in 0 until nbOfMessages) { + room.sendTextMessage(message + " #" + (i + 1)) + } + await(latch) + timeline.removeListener(onEventSentListener) + + // Check that all events has been created + assertEquals(nbOfMessages.toLong(), sentEvents.size.toLong()) + + return sentEvents + } + + // PRIVATE METHODS ***************************************************************************** + + /** + * Creates a unique account + * + * @param userNamePrefix the user name prefix + * @param password the password + * @param testParams test params about the session + * @return the session associated with the newly created account + */ + private fun createAccount(userNamePrefix: String, + password: String, + testParams: SessionTestParams): Session { + val session = createAccountAndSync( + userNamePrefix + "_" + System.currentTimeMillis() + UUID.randomUUID(), + password, + testParams + ) + assertNotNull(session) + return session + } + + /** + * Logs into an existing account + * + * @param userId the userId to log in + * @param password the password to log in + * @param testParams test params about the session + * @return the session associated with the existing account + */ + private fun logIntoAccount(userId: String, + password: String, + testParams: SessionTestParams): Session { + val session = logAccountAndSync(userId, password, testParams) + assertNotNull(session) + return session + } + + /** + * Create an account and a dedicated session + * + * @param userName the account username + * @param password the password + * @param sessionTestParams parameters for the test + */ + private fun createAccountAndSync(userName: String, + password: String, + sessionTestParams: SessionTestParams): Session { + val hs = createHomeServerConfig() + + doSync { + matrix.authenticationService + .getLoginFlow(hs, it) + } + + doSync { + matrix.authenticationService + .getRegistrationWizard() + .createAccount(userName, password, null, it) + } + + // Preform dummy step + val registrationResult = doSync { + matrix.authenticationService + .getRegistrationWizard() + .dummy(it) + } + + assertTrue(registrationResult is RegistrationResult.Success) + val session = (registrationResult as RegistrationResult.Success).session + if (sessionTestParams.withInitialSync) { + syncSession(session) + } + + return session + } + + /** + * Start an account login + * + * @param userName the account username + * @param password the password + * @param sessionTestParams session test params + */ + private fun logAccountAndSync(userName: String, + password: String, + sessionTestParams: SessionTestParams): Session { + val hs = createHomeServerConfig() + + doSync { + matrix.authenticationService + .getLoginFlow(hs, it) + } + + val session = doSync { + matrix.authenticationService + .getLoginWizard() + .login(userName, password, "myDevice", it) + } + + if (sessionTestParams.withInitialSync) { + syncSession(session) + } + + return session + } + + /** + * Await for a latch and ensure the result is true + * + * @param latch + * @throws InterruptedException + */ + fun await(latch: CountDownLatch) { + assertTrue(latch.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)) + } + + // Transform a method with a MatrixCallback to a synchronous method + inline fun doSync(block: (MatrixCallback) -> Unit): T { + val lock = CountDownLatch(1) + var result: T? = null + + val callback = object : TestMatrixCallback(lock) { + override fun onSuccess(data: T) { + result = data + super.onSuccess(data) + } + } + + block.invoke(callback) + + await(lock) + + assertNotNull(result) + return result!! + } + + /** + * Clear all provided sessions + */ + fun Iterable.close() = forEach { it.close() } + + fun signout(session: Session) { + val lock = CountDownLatch(1) + session.signOut(true, object : TestMatrixCallback(lock) {}) + await(lock) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestData.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestData.kt new file mode 100644 index 0000000000..8ad9f1ec6f --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestData.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2018 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.common + +import im.vector.matrix.android.api.session.Session + +data class CryptoTestData(val firstSession: Session, + val roomId: String, + val secondSession: Session? = null, + val thirdSession: Session? = null) { + + fun close() { + firstSession.close() + secondSession?.close() + secondSession?.close() + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt new file mode 100644 index 0000000000..df45249265 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt @@ -0,0 +1,335 @@ +/* + * Copyright 2018 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.common + +import android.os.SystemClock +import im.vector.matrix.android.api.session.Session +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.toContent +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams +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 +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP +import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData +import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo +import org.junit.Assert.* +import java.util.* +import java.util.concurrent.CountDownLatch + +class CryptoTestHelper(val mTestHelper: CommonTestHelper) { + + val messagesFromAlice: List = Arrays.asList("0 - Hello I'm Alice!", "4 - Go!") + val messagesFromBob: List = Arrays.asList("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.") + + val defaultSessionParams = SessionTestParams(true) + + /** + * @return alice session + */ + fun doE2ETestWithAliceInARoom(): CryptoTestData { + val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) + + var roomId: String? = null + val lock1 = CountDownLatch(1) + + aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, object : TestMatrixCallback(lock1) { + override fun onSuccess(data: String) { + roomId = data + super.onSuccess(data) + } + }) + + mTestHelper.await(lock1) + assertNotNull(roomId) + + val room = aliceSession.getRoom(roomId!!)!! + + val lock2 = CountDownLatch(1) + room.enableEncryptionWithAlgorithm(MXCRYPTO_ALGORITHM_MEGOLM, object : TestMatrixCallback(lock2) {}) + mTestHelper.await(lock2) + + return CryptoTestData(aliceSession, roomId!!) + } + + /** + * @return alice and bob sessions + */ + fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData { + val statuses = HashMap() + + val cryptoTestData = doE2ETestWithAliceInARoom() + val aliceSession = cryptoTestData.firstSession + val aliceRoomId = cryptoTestData.roomId + + val room = aliceSession.getRoom(aliceRoomId)!! + + val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams) + + val lock1 = CountDownLatch(2) + +// val bobEventListener = object : MXEventListener() { +// override fun onNewRoom(roomId: String) { +// if (TextUtils.equals(roomId, aliceRoomId)) { +// if (!statuses.containsKey("onNewRoom")) { +// statuses["onNewRoom"] = "onNewRoom" +// lock1.countDown() +// } +// } +// } +// } +// +// bobSession.dataHandler.addListener(bobEventListener) + + room.invite(bobSession.myUserId, callback = object : TestMatrixCallback(lock1) { + override fun onSuccess(data: Unit) { + statuses["invite"] = "invite" + super.onSuccess(data) + } + }) + + mTestHelper.await(lock1) + + assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom")) + +// bobSession.dataHandler.removeListener(bobEventListener) + + val lock2 = CountDownLatch(2) + + bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2)) + +// room.addEventListener(object : MXEventListener() { +// override fun onLiveEvent(event: Event, roomState: RoomState) { +// if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_STATE_ROOM_MEMBER)) { +// val contentToConsider = event.contentAsJsonObject +// val member = JsonUtils.toRoomMember(contentToConsider) +// +// if (TextUtils.equals(member.membership, RoomMember.MEMBERSHIP_JOIN)) { +// statuses["AliceJoin"] = "AliceJoin" +// lock2.countDown() +// } +// } +// } +// }) + + mTestHelper.await(lock2) + + // Ensure bob can send messages to the room +// val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! +// assertNotNull(roomFromBobPOV.powerLevels) +// assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId)) + + assertTrue(statuses.toString() + "", statuses.containsKey("AliceJoin")) + +// bobSession.dataHandler.removeListener(bobEventListener) + + return CryptoTestData(aliceSession, aliceRoomId, bobSession) + } + + /** + * @return Alice, Bob and Sam session + */ + fun doE2ETestWithAliceAndBobAndSamInARoom(): CryptoTestData { + val statuses = HashMap() + + val cryptoTestData = doE2ETestWithAliceAndBobInARoom() + val aliceSession = cryptoTestData.firstSession + val aliceRoomId = cryptoTestData.roomId + + val room = aliceSession.getRoom(aliceRoomId)!! + + val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams) + + val lock1 = CountDownLatch(2) + +// val samEventListener = object : MXEventListener() { +// override fun onNewRoom(roomId: String) { +// if (TextUtils.equals(roomId, aliceRoomId)) { +// if (!statuses.containsKey("onNewRoom")) { +// statuses["onNewRoom"] = "onNewRoom" +// lock1.countDown() +// } +// } +// } +// } +// +// samSession.dataHandler.addListener(samEventListener) + + room.invite(samSession.myUserId, null, object : TestMatrixCallback(lock1) { + override fun onSuccess(data: Unit) { + statuses["invite"] = "invite" + super.onSuccess(data) + } + }) + + mTestHelper.await(lock1) + + assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom")) + +// samSession.dataHandler.removeListener(samEventListener) + + val lock2 = CountDownLatch(1) + + samSession.joinRoom(aliceRoomId, null, object : TestMatrixCallback(lock2) { + override fun onSuccess(data: Unit) { + statuses["joinRoom"] = "joinRoom" + super.onSuccess(data) + } + }) + + mTestHelper.await(lock2) + assertTrue(statuses.containsKey("joinRoom")) + + // wait the initial sync + SystemClock.sleep(1000) + +// samSession.dataHandler.removeListener(samEventListener) + + return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession) + } + + /** + * @return Alice and Bob sessions + */ + fun doE2ETestWithAliceAndBobInARoomWithEncryptedMessages(): CryptoTestData { + val cryptoTestData = doE2ETestWithAliceAndBobInARoom() + val aliceSession = cryptoTestData.firstSession + val aliceRoomId = cryptoTestData.roomId + val bobSession = cryptoTestData.secondSession!! + + bobSession.setWarnOnUnknownDevices(false) + + aliceSession.setWarnOnUnknownDevices(false) + + val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! + val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! + + var lock = CountDownLatch(1) + + val bobEventsListener = object : Timeline.Listener { + override fun onTimelineFailure(throwable: Throwable) { + } + + override fun onTimelineUpdated(snapshot: List) { + val size = snapshot.filter { it.root.senderId != bobSession.myUserId && it.root.getClearType() == EventType.MESSAGE } + .size + + if (size == 3) { + lock.countDown() + } + } + } + + val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(10)) + bobTimeline.addListener(bobEventsListener) + + val results = HashMap() + + // bobSession.dataHandler.addListener(object : MXEventListener() { + // override fun onToDeviceEvent(event: Event) { + // results["onToDeviceEvent"] = event + // lock.countDown() + // } + // }) + + // Alice sends a message + roomFromAlicePOV.sendTextMessage(messagesFromAlice[0]) + assertTrue(results.containsKey("onToDeviceEvent")) +// assertEquals(1, messagesReceivedByBobCount) + + // Bob send a message + lock = CountDownLatch(1) + roomFromBobPOV.sendTextMessage(messagesFromBob[0]) + // android does not echo the messages sent from itself +// messagesReceivedByBobCount++ + mTestHelper.await(lock) +// assertEquals(2, messagesReceivedByBobCount) + + // Bob send a message + lock = CountDownLatch(1) + roomFromBobPOV.sendTextMessage(messagesFromBob[1]) + // android does not echo the messages sent from itself +// messagesReceivedByBobCount++ + mTestHelper.await(lock) +// assertEquals(3, messagesReceivedByBobCount) + + // Bob send a message + lock = CountDownLatch(1) + roomFromBobPOV.sendTextMessage(messagesFromBob[2]) + // android does not echo the messages sent from itself +// messagesReceivedByBobCount++ + mTestHelper.await(lock) +// assertEquals(4, messagesReceivedByBobCount) + + // Alice sends a message + lock = CountDownLatch(2) + roomFromAlicePOV.sendTextMessage(messagesFromAlice[1]) + mTestHelper.await(lock) +// assertEquals(5, messagesReceivedByBobCount) + + bobTimeline.removeListener(bobEventsListener) + + return cryptoTestData + } + + fun checkEncryptedEvent(event: Event, roomId: String, clearMessage: String, senderSession: Session) { + assertEquals(EventType.ENCRYPTED, event.type) + assertNotNull(event.content) + + val eventWireContent = event.content.toContent() + assertNotNull(eventWireContent) + + assertNull(eventWireContent.get("body")) + assertEquals(MXCRYPTO_ALGORITHM_MEGOLM, eventWireContent.get("algorithm")) + + assertNotNull(eventWireContent.get("ciphertext")) + assertNotNull(eventWireContent.get("session_id")) + assertNotNull(eventWireContent.get("sender_key")) + + assertEquals(senderSession.sessionParams.credentials.deviceId, eventWireContent.get("device_id")) + + assertNotNull(event.eventId) + assertEquals(roomId, event.roomId) + assertEquals(EventType.MESSAGE, event.getClearType()) + // TODO assertTrue(event.getAge() < 10000) + + val eventContent = event.toContent() + assertNotNull(eventContent) + assertEquals(clearMessage, eventContent.get("body")) + assertEquals(senderSession.myUserId, event.senderId) + } + + fun createFakeMegolmBackupAuthData(): MegolmBackupAuthData { + return MegolmBackupAuthData( + publicKey = "abcdefg", + signatures = HashMap>().apply { + this["something"] = HashMap().apply { + this["ed25519:something"] = "hijklmnop" + } + } + ) + } + + fun createFakeMegolmBackupCreationInfo(): MegolmBackupCreationInfo { + return MegolmBackupCreationInfo().apply { + algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP + authData = createFakeMegolmBackupAuthData() + } + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/MockOkHttpInterceptor.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/MockOkHttpInterceptor.kt new file mode 100644 index 0000000000..a1f95424a6 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/MockOkHttpInterceptor.kt @@ -0,0 +1,83 @@ +/* + * 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.common + +import okhttp3.Interceptor +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import javax.net.ssl.HttpsURLConnection + +/** + * Allows to intercept network requests for test purpose by + * - re-writing the response + * - changing the response code (200/404/etc..). + * - Test delays.. + * + * Basic usage: + * + * val mockInterceptor = MockOkHttpInterceptor() + * mockInterceptor.addRule(MockOkHttpInterceptor.SimpleRule(".well-known/matrix/client", 200, "{}")) + * + * RestHttpClientFactoryProvider.defaultProvider = RestClientHttpClientFactory(mockInterceptor) + * AutoDiscovery().findClientConfig("matrix.org", ) + * + */ +class MockOkHttpInterceptor : Interceptor { + + private var rules: ArrayList = ArrayList() + + fun addRule(rule: Rule) { + rules.add(rule) + } + + override fun intercept(chain: Interceptor.Chain): Response { + val originalRequest = chain.request() + + rules.forEach { rule -> + if (originalRequest.url.toString().contains(rule.match)) { + rule.process(originalRequest)?.let { + return it + } + } + } + + return chain.proceed(originalRequest) + } + + abstract class Rule(val match: String) { + abstract fun process(originalRequest: Request): Response? + } + + /** + * Simple rule that reply with the given body for any request that matches the match param + */ + class SimpleRule(match: String, + private val code: Int = HttpsURLConnection.HTTP_OK, + private val body: String = "{}") : Rule(match) { + + override fun process(originalRequest: Request): Response? { + return Response.Builder() + .protocol(Protocol.HTTP_1_1) + .request(originalRequest) + .message("mocked answer") + .body(body.toResponseBody(null)) + .code(code) + .build() + } + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/SessionTestParams.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/SessionTestParams.kt new file mode 100644 index 0000000000..7d1d23e951 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/SessionTestParams.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2018 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.common + +data class SessionTestParams @JvmOverloads constructor(val withInitialSync: Boolean = false) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestAssertUtil.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestAssertUtil.kt new file mode 100644 index 0000000000..2a62165210 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestAssertUtil.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2018 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.common + +import org.junit.Assert.* + +/** + * Compare two lists and their content + */ +fun assertListEquals(list1: List?, list2: List?) { + if (list1 == null) { + assertNull(list2) + } else { + assertNotNull(list2) + + assertEquals("List sizes must match", list1.size, list2!!.size) + + for (i in list1.indices) { + assertEquals("Elements at index $i are not equal", list1[i], list2[i]) + } + } +} + +/** + * Compare two maps and their content + */ +fun assertDictEquals(dict1: Map?, dict2: Map?) { + if (dict1 == null) { + assertNull(dict2) + } else { + assertNotNull(dict2) + + assertEquals("Map sizes must match", dict1.size, dict2!!.size) + + for (i in dict1.keys) { + assertEquals("Values for key $i are not equal", dict1[i], dict2[i]) + } + } +} + +/** + * Compare two byte arrays content. + * Note that if the arrays have not the same size, it also fails. + */ +fun assertByteArrayNotEqual(a1: ByteArray, a2: ByteArray) { + if (a1.size != a2.size) { + fail("Arrays have not the same size.") + } + + for (index in a1.indices) { + if (a1[index] != a2[index]) { + // Difference found! + return + } + } + + fail("Arrays are equals.") +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt new file mode 100644 index 0000000000..60cc87d330 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2018 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.common + +import android.os.Debug + +object TestConstants { + + const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080" + + // Time out to use when waiting for server response. 60s + private const val AWAIT_TIME_OUT_MILLIS = 60000 + + // Time out to use when waiting for server response, when the debugger is connected. 10 minutes + private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60000 + + const val USER_ALICE = "Alice" + const val USER_BOB = "Bob" + const val USER_SAM = "Sam" + + const val PASSWORD = "password" + + val timeOutMillis: Long + get() = if (Debug.isDebuggerConnected()) { + // Wait more + AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS.toLong() + } else { + AWAIT_TIME_OUT_MILLIS.toLong() + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestMatrixCallback.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestMatrixCallback.kt new file mode 100644 index 0000000000..c04777440b --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestMatrixCallback.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2018 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.common + +import androidx.annotation.CallSuper +import im.vector.matrix.android.api.MatrixCallback +import org.junit.Assert.fail +import timber.log.Timber +import java.util.concurrent.CountDownLatch + +/** + * Simple implementation of MatrixCallback, which count down the CountDownLatch on each API callback + * @param onlySuccessful true to fail if an error occurs. This is the default behavior + * @param + */ +open class TestMatrixCallback(private val countDownLatch: CountDownLatch, + private val onlySuccessful: Boolean = true) : MatrixCallback { + + @CallSuper + override fun onSuccess(data: T) { + countDownLatch.countDown() + } + + @CallSuper + override fun onFailure(failure: Throwable) { + Timber.e(failure, "TestApiCallback") + + if (onlySuccessful) { + fail("onFailure " + failure.localizedMessage) + } + + countDownLatch.countDown() + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/AttachmentEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/AttachmentEncryptionTest.kt new file mode 100644 index 0000000000..84b3f24191 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/AttachmentEncryptionTest.kt @@ -0,0 +1,148 @@ +/* + * 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.crypto + +import android.os.MemoryFile +import android.util.Base64 +import androidx.test.ext.junit.runners.AndroidJUnit4 +import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments +import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo +import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileKey +import org.junit.Assert.* +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import java.io.ByteArrayInputStream +import java.io.InputStream + +/** + * Unit tests AttachmentEncryptionTest. + */ +@Suppress("SpellCheckingInspection") +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class AttachmentEncryptionTest { + + private fun checkDecryption(input: String, encryptedFileInfo: EncryptedFileInfo): String { + val `in` = Base64.decode(input, Base64.DEFAULT) + + val inputStream: InputStream + + inputStream = if (`in`.isEmpty()) { + ByteArrayInputStream(`in`) + } else { + val memoryFile = MemoryFile("file" + System.currentTimeMillis(), `in`.size) + memoryFile.outputStream.write(`in`) + memoryFile.inputStream + } + + val decryptedStream = MXEncryptedAttachments.decryptAttachment(inputStream, encryptedFileInfo) + + assertNotNull(decryptedStream) + + inputStream.close() + + val buffer = ByteArray(100) + + val len = decryptedStream!!.read(buffer) + + decryptedStream.close() + + return Base64.encodeToString(buffer, 0, len, Base64.DEFAULT).replace("\n".toRegex(), "").replace("=".toRegex(), "") + } + + @Test + fun checkDecrypt1() { + val encryptedFileInfo = EncryptedFileInfo( + v = "v2", + hashes = mapOf("sha256" to "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU"), + key = EncryptedFileKey( + alg = "A256CTR", + k = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + key_ops = listOf("encrypt", "decrypt"), + kty = "oct", + ext = true + ), + iv = "AAAAAAAAAAAAAAAAAAAAAA", + url = "dummyUrl" + ) + + assertEquals("", checkDecryption("", encryptedFileInfo)) + } + + @Test + fun checkDecrypt2() { + val encryptedFileInfo = EncryptedFileInfo( + v = "v2", + hashes = mapOf("sha256" to "YzF08lARDdOCzJpzuSwsjTNlQc4pHxpdHcXiD/wpK6k"), + key = EncryptedFileKey( + alg = "A256CTR", + k = "__________________________________________8", + key_ops = listOf("encrypt", "decrypt"), + kty = "oct", + ext = true + ), + iv = "//////////8AAAAAAAAAAA", + url = "dummyUrl" + ) + + assertEquals("SGVsbG8sIFdvcmxk", checkDecryption("5xJZTt5cQicm+9f4", encryptedFileInfo)) + } + + @Test + fun checkDecrypt3() { + val encryptedFileInfo = EncryptedFileInfo( + v = "v2", + hashes = mapOf("sha256" to "IOq7/dHHB+mfHfxlRY5XMeCWEwTPmlf4cJcgrkf6fVU"), + key = EncryptedFileKey( + alg = "A256CTR", + k = "__________________________________________8", + key_ops = listOf("encrypt", "decrypt"), + kty = "oct", + ext = true + ), + iv = "//////////8AAAAAAAAAAA", + url = "dummyUrl" + ) + + assertEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ", + checkDecryption("zhtFStAeFx0s+9L/sSQO+WQMtldqYEHqTxMduJrCIpnkyer09kxJJuA4K+adQE4w+7jZe/vR9kIcqj9rOhDR8Q", + encryptedFileInfo)) + } + + @Test + fun checkDecrypt4() { + val encryptedFileInfo = EncryptedFileInfo( + v = "v2", + hashes = mapOf("sha256" to "LYG/orOViuFwovJpv2YMLSsmVKwLt7pY3f8SYM7KU5E"), + key = EncryptedFileKey( + alg = "A256CTR", + k = "__________________________________________8", + key_ops = listOf("encrypt", "decrypt"), + kty = "oct", + ext = true + ), + iv = "/////////////////////w", + url = "dummyUrl" + ) + + assertNotEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ", + checkDecryption("tJVNBVJ/vl36UQt4Y5e5m84bRUrQHhcdLPvS/7EkDvlkDLZXamBB6k8THbiawiKZ5Mnq9PZMSSbgOCvmnUBOMA", + encryptedFileInfo)) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ExportEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ExportEncryptionTest.kt new file mode 100644 index 0000000000..89ed3c2e65 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ExportEncryptionTest.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2018 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.crypto + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.* +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters + +/** + * Unit tests ExportEncryptionTest. + */ +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class ExportEncryptionTest { + + @Test + fun checkExportError1() { + val password = "password" + val input = "-----" + var failed = false + + try { + MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password) + } catch (e: Exception) { + failed = true + } + + assertTrue(failed) + } + + @Test + fun checkExportError2() { + val password = "password" + val input = "-----BEGIN MEGOLM SESSION DATA-----\n" + "-----" + var failed = false + + try { + MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password) + } catch (e: Exception) { + failed = true + } + + assertTrue(failed) + } + + @Test + fun checkExportError3() { + val password = "password" + val input = "-----BEGIN MEGOLM SESSION DATA-----\n" + + " AXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" + + " cissyYBxjsfsAn\n" + + " -----END MEGOLM SESSION DATA-----" + var failed = false + + try { + MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password) + } catch (e: Exception) { + failed = true + } + + assertTrue(failed) + } + + @Test + fun checkExportDecrypt1() { + val password = "password" + val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" + "cissyYBxjsfsAndErh065A8=\n-----END MEGOLM SESSION DATA-----" + val expectedString = "plain" + + var decodedString: String? = null + try { + decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password) + } catch (e: Exception) { + fail("## checkExportDecrypt1() failed : " + e.message) + } + + assertEquals("## checkExportDecrypt1() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } + + @Test + fun checkExportDecrypt2() { + val password = "betterpassword" + val input = "-----BEGIN MEGOLM SESSION DATA-----\nAW1vcmVzYWx0bW9yZXNhbHT//////////wAAAAAAAAAAAAAD6KyBpe1Niv5M5NPm4ZATsJo5nghk\n" + "KYu63a0YQ5DRhUWEKk7CcMkrKnAUiZny\n-----END MEGOLM SESSION DATA-----" + val expectedString = "Hello, World" + + var decodedString: String? = null + try { + decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password) + } catch (e: Exception) { + fail("## checkExportDecrypt2() failed : " + e.message) + } + + assertEquals("## checkExportDecrypt2() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } + + @Test + fun checkExportDecrypt3() { + val password = "SWORDFISH" + val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXllc3NhbHR5Z29vZG5lc3P//////////wAAAAAAAAAAAAAD6OIW+Je7gwvjd4kYrb+49gKCfExw\n" + "MgJBMD4mrhLkmgAngwR1pHjbWXaoGybtiAYr0moQ93GrBQsCzPbvl82rZhaXO3iH5uHo/RCEpOqp\nPgg29363BGR+/Ripq/VCLKGNbw==\n-----END MEGOLM SESSION DATA-----" + val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically" + + var decodedString: String? = null + try { + decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password) + } catch (e: Exception) { + fail("## checkExportDecrypt3() failed : " + e.message) + } + + assertEquals("## checkExportDecrypt3() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } + + @Test + fun checkExportEncrypt1() { + val password = "password" + val expectedString = "plain" + var decodedString: String? = null + + try { + decodedString = MXMegolmExportEncryption + .decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password) + } catch (e: Exception) { + fail("## checkExportEncrypt1() failed : " + e.message) + } + + assertEquals("## checkExportEncrypt1() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } + + @Test + fun checkExportEncrypt2() { + val password = "betterpassword" + val expectedString = "Hello, World" + var decodedString: String? = null + + try { + decodedString = MXMegolmExportEncryption + .decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password) + } catch (e: Exception) { + fail("## checkExportEncrypt2() failed : " + e.message) + } + + assertEquals("## checkExportEncrypt2() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } + + @Test + fun checkExportEncrypt3() { + val password = "SWORDFISH" + val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically" + var decodedString: String? = null + + try { + decodedString = MXMegolmExportEncryption + .decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password) + } catch (e: Exception) { + fail("## checkExportEncrypt3() failed : " + e.message) + } + + assertEquals("## checkExportEncrypt3() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } + + @Test + fun checkExportEncrypt4() { + val password = "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" + val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically" + var decodedString: String? = null + + try { + decodedString = MXMegolmExportEncryption + .decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password) + } catch (e: Exception) { + fail("## checkExportEncrypt4() failed : " + e.message) + } + + assertEquals("## checkExportEncrypt4() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPasswordTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPasswordTest.kt new file mode 100644 index 0000000000..53e68383ee --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPasswordTest.kt @@ -0,0 +1,178 @@ +/* + * 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.crypto.keysbackup + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.listeners.ProgressListener +import im.vector.matrix.android.common.assertByteArrayNotEqual +import org.junit.Assert.* +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.matrix.olm.OlmManager +import org.matrix.olm.OlmPkDecryption + +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class KeysBackupPasswordTest : InstrumentedTest { + + @Before + fun ensureLibLoaded() { + OlmManager() + } + + /** + * Check KeysBackupPassword utilities + */ + @Test + fun passwordConverter_ok() { + val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null) + + assertEquals(32, generatePrivateKeyResult.salt.length) + assertEquals(500_000, generatePrivateKeyResult.iterations) + assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) + + // Reverse operation + val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD, + generatePrivateKeyResult.salt, + generatePrivateKeyResult.iterations) + + assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) + assertArrayEquals(generatePrivateKeyResult.privateKey, retrievedPrivateKey) + } + + /** + * Check generatePrivateKeyWithPassword progress listener behavior + */ + @Test + fun passwordConverter_progress_ok() { + val progressValues = ArrayList(101) + var lastTotal = 0 + + generatePrivateKeyWithPassword(PASSWORD, object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + if (!progressValues.contains(progress)) { + progressValues.add(progress) + } + + lastTotal = total + } + }) + + assertEquals(100, lastTotal) + + // Ensure all values are here + assertEquals(101, progressValues.size) + + for (i in 0..100) { + assertTrue(progressValues[i] == i) + } + } + + /** + * Check KeysBackupPassword utilities, with bad password + */ + @Test + fun passwordConverter_badPassword_ok() { + val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null) + + assertEquals(32, generatePrivateKeyResult.salt.length) + assertEquals(500_000, generatePrivateKeyResult.iterations) + assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) + + // Reverse operation, with bad password + val retrievedPrivateKey = retrievePrivateKeyWithPassword(BAD_PASSWORD, + generatePrivateKeyResult.salt, + generatePrivateKeyResult.iterations) + + assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) + assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey) + } + + /** + * Check KeysBackupPassword utilities, with bad password + */ + @Test + fun passwordConverter_badIteration_ok() { + val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null) + + assertEquals(32, generatePrivateKeyResult.salt.length) + assertEquals(500_000, generatePrivateKeyResult.iterations) + assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) + + // Reverse operation, with bad iteration + val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD, + generatePrivateKeyResult.salt, + 500_001) + + assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) + assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey) + } + + /** + * Check KeysBackupPassword utilities, with bad salt + */ + @Test + fun passwordConverter_badSalt_ok() { + val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null) + + assertEquals(32, generatePrivateKeyResult.salt.length) + assertEquals(500_000, generatePrivateKeyResult.iterations) + assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) + + // Reverse operation, with bad iteration + val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD, + BAD_SALT, + generatePrivateKeyResult.iterations) + + assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) + assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey) + } + + /** + * Check [retrievePrivateKeyWithPassword] with data coming from another platform (RiotWeb). + */ + @Test + fun passwordConverter_crossPlatform_ok() { + val password = "This is a passphrase!" + val salt = "TO0lxhQ9aYgGfMsclVWPIAublg8h9Nlu" + val iteration = 500_000 + + val retrievedPrivateKey = retrievePrivateKeyWithPassword(password, salt, iteration) + + assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) + + // Data from RiotWeb + val privateKeyBytes = byteArrayOf( + 116.toByte(), 224.toByte(), 229.toByte(), 224.toByte(), 9.toByte(), 3.toByte(), 178.toByte(), 162.toByte(), + 120.toByte(), 23.toByte(), 108.toByte(), 218.toByte(), 22.toByte(), 61.toByte(), 241.toByte(), 200.toByte(), + 235.toByte(), 173.toByte(), 236.toByte(), 100.toByte(), 115.toByte(), 247.toByte(), 33.toByte(), 132.toByte(), + 195.toByte(), 154.toByte(), 64.toByte(), 158.toByte(), 184.toByte(), 148.toByte(), 20.toByte(), 85.toByte()) + + assertArrayEquals(privateKeyBytes, retrievedPrivateKey) + } + + companion object { + private const val PASSWORD = "password" + private const val BAD_PASSWORD = "passw0rd" + + private const val BAD_SALT = "AA0lxhQ9aYgGfMsclVWPIAublg8h9Nlu" + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt new file mode 100644 index 0000000000..15deebdab1 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt @@ -0,0 +1,1417 @@ +/* + * Copyright 2018 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.crypto.keysbackup + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.listeners.ProgressListener +import im.vector.matrix.android.api.listeners.StepProgressListener +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener +import im.vector.matrix.android.common.* +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP +import im.vector.matrix.android.internal.crypto.MegolmSessionData +import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest +import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust +import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult +import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult +import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo +import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper +import org.junit.Assert.* +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import java.util.* +import java.util.concurrent.CountDownLatch + +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class KeysBackupTest : InstrumentedTest { + + private val mTestHelper = CommonTestHelper(context()) + private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + + private val defaultSessionParams = SessionTestParams(withInitialSync = false) + private val defaultSessionParamsWithInitialSync = SessionTestParams(withInitialSync = true) + + /** + * - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys + * - Check backup keys after having marked one as backed up + * - Reset keys backup markers + */ + @Test + fun roomKeysTest_testBackupStore_ok() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + // From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys + val cryptoStore = (cryptoTestData.firstSession.getKeysBackupService() as KeysBackup).store + val sessions = cryptoStore.inboundGroupSessionsToBackup(100) + val sessionsCount = sessions.size + + assertFalse(sessions.isEmpty()) + assertEquals(sessionsCount, cryptoTestData.firstSession.inboundGroupSessionsCount(false)) + assertEquals(0, cryptoTestData.firstSession.inboundGroupSessionsCount(true)) + + // - Check backup keys after having marked one as backed up + val session = sessions[0] + + cryptoStore.markBackupDoneForInboundGroupSessions(Collections.singletonList(session)) + + assertEquals(sessionsCount, cryptoTestData.firstSession.inboundGroupSessionsCount(false)) + assertEquals(1, cryptoTestData.firstSession.inboundGroupSessionsCount(true)) + + val sessions2 = cryptoStore.inboundGroupSessionsToBackup(100) + assertEquals(sessionsCount - 1, sessions2.size) + + // - Reset keys backup markers + cryptoStore.resetBackupMarkers() + + val sessions3 = cryptoStore.inboundGroupSessionsToBackup(100) + assertEquals(sessionsCount, sessions3.size) + assertEquals(sessionsCount, cryptoTestData.firstSession.inboundGroupSessionsCount(false)) + assertEquals(0, cryptoTestData.firstSession.inboundGroupSessionsCount(true)) + } + + /** + * Check that prepareKeysBackupVersionWithPassword returns valid data + */ + @Test + fun prepareKeysBackupVersionTest() { + val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams) + + assertNotNull(bobSession.getKeysBackupService()) + + val keysBackup = bobSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + assertFalse(keysBackup.isEnabled) + + val latch = CountDownLatch(1) + + keysBackup.prepareKeysBackupVersion(null, null, object : MatrixCallback { + override fun onSuccess(data: MegolmBackupCreationInfo) { + assertNotNull(data) + + assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, data.algorithm) + assertNotNull(data.authData) + assertNotNull(data.authData!!.publicKey) + assertNotNull(data.authData!!.signatures) + assertNotNull(data.recoveryKey) + + latch.countDown() + } + + override fun onFailure(failure: Throwable) { + fail(failure.localizedMessage) + + latch.countDown() + } + }) + mTestHelper.await(latch) + + stateObserver.stopAndCheckStates(null) + bobSession.close() + } + + /** + * Test creating a keys backup version and check that createKeysBackupVersion() returns valid data + */ + @Test + fun createKeysBackupVersionTest() { + val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams) + + val keysBackup = bobSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + assertFalse(keysBackup.isEnabled) + + var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null + val latch = CountDownLatch(1) + keysBackup.prepareKeysBackupVersion(null, null, object : MatrixCallback { + override fun onSuccess(data: MegolmBackupCreationInfo) { + megolmBackupCreationInfo = data + + latch.countDown() + } + + override fun onFailure(failure: Throwable) { + fail(failure.localizedMessage) + + latch.countDown() + } + }) + mTestHelper.await(latch) + + assertNotNull(megolmBackupCreationInfo) + + assertFalse(keysBackup.isEnabled) + + val latch2 = CountDownLatch(1) + + // Create the version + keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : TestMatrixCallback(latch2) { + override fun onSuccess(data: KeysVersion) { + assertNotNull(data) + assertNotNull(data.version) + + super.onSuccess(data) + } + }) + mTestHelper.await(latch2) + + // Backup must be enable now + assertTrue(keysBackup.isEnabled) + + stateObserver.stopAndCheckStates(null) + bobSession.close() + } + + /** + * - Check that createKeysBackupVersion() launches the backup + * - Check the backup completes + */ + @Test + fun backupAfterCreateKeysBackupVersionTest() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val latch = CountDownLatch(1) + + assertEquals(2, cryptoTestData.firstSession.inboundGroupSessionsCount(false)) + assertEquals(0, cryptoTestData.firstSession.inboundGroupSessionsCount(true)) + + val stateObserver = StateObserver(keysBackup, latch, 5) + + prepareAndCreateKeysBackupData(keysBackup) + + mTestHelper.await(latch) + + val nbOfKeys = cryptoTestData.firstSession.inboundGroupSessionsCount(false) + val backedUpKeys = cryptoTestData.firstSession.inboundGroupSessionsCount(true) + + assertEquals(2, nbOfKeys) + assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) + + // Check the several backup state changes + stateObserver.stopAndCheckStates( + listOf( + KeysBackupState.Enabling, + KeysBackupState.ReadyToBackUp, + KeysBackupState.WillBackUp, + KeysBackupState.BackingUp, + KeysBackupState.ReadyToBackUp + ) + ) + cryptoTestData.close() + } + + /** + * Check that backupAllGroupSessions() returns valid data + */ + @Test + fun backupAllGroupSessionsTest() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + prepareAndCreateKeysBackupData(keysBackup) + + // Check that backupAllGroupSessions returns valid data + val nbOfKeys = cryptoTestData.firstSession.inboundGroupSessionsCount(false) + + assertEquals(2, nbOfKeys) + + val latch = CountDownLatch(1) + + var lastBackedUpKeysProgress = 0 + + keysBackup.backupAllGroupSessions(object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + assertEquals(nbOfKeys, total) + lastBackedUpKeysProgress = progress + } + }, TestMatrixCallback(latch)) + + mTestHelper.await(latch) + assertEquals(nbOfKeys, lastBackedUpKeysProgress) + + val backedUpKeys = cryptoTestData.firstSession.inboundGroupSessionsCount(true) + + assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) + + stateObserver.stopAndCheckStates(null) + cryptoTestData.close() + } + + /** + * Check encryption and decryption of megolm keys in the backup. + * - Pick a megolm key + * - Check [MXKeyBackup encryptGroupSession] returns stg + * - Check [MXKeyBackup pkDecryptionFromRecoveryKey] is able to create a OLMPkDecryption + * - Check [MXKeyBackup decryptKeyBackupData] returns stg + * - Compare the decrypted megolm key with the original one + */ + @Test + fun testEncryptAndDecryptKeysBackupData() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() as KeysBackup + + val stateObserver = StateObserver(keysBackup) + + // - Pick a megolm key + val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0] + + val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo + + // - Check encryptGroupSession() returns stg + val keyBackupData = keysBackup.encryptGroupSession(session) + assertNotNull(keyBackupData) + assertNotNull(keyBackupData.sessionData) + + // - Check pkDecryptionFromRecoveryKey() is able to create a OlmPkDecryption + val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey) + assertNotNull(decryption) + // - Check decryptKeyBackupData() returns stg + val sessionData = keysBackup.decryptKeyBackupData(keyBackupData, session.olmInboundGroupSession!!.sessionIdentifier(), cryptoTestData.roomId, decryption!!) + assertNotNull(sessionData) + // - Compare the decrypted megolm key with the original one + assertKeysEquals(session.exportKeys(), sessionData) + + stateObserver.stopAndCheckStates(null) + cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - Log Alice on a new device + * - Restore the e2e backup from the homeserver with the recovery key + * - Restore must be successful + */ + @Test + fun restoreKeysBackupTest() { + val testData = createKeysBackupScenarioWithPassword(null) + + // - Restore the e2e backup from the homeserver + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + testData.aliceSession2.getKeysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, + null, + null, + null, + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) + + testData.cryptoTestData.close() + } + + /** + * + * This is the same as `testRestoreKeyBackup` but this test checks that pending key + * share requests are cancelled. + * + * - Do an e2e backup to the homeserver with a recovery key + * - Log Alice on a new device + * - *** Check the SDK sent key share requests + * - Restore the e2e backup from the homeserver with the recovery key + * - Restore must be successful + * - *** There must be no more pending key share requests + */ + @Test + fun restoreKeysBackupAndKeyShareRequestTest() { + val testData = createKeysBackupScenarioWithPassword(null) + + // - Check the SDK sent key share requests + val cryptoStore2 = (testData.aliceSession2.getKeysBackupService() as KeysBackup).store + val unsentRequest = cryptoStore2 + .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT)) + val sentRequest = cryptoStore2 + .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT)) + + // Request is either sent or unsent + assertTrue(unsentRequest != null || sentRequest != null) + + // - Restore the e2e backup from the homeserver + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + testData.aliceSession2.getKeysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, + null, + null, + null, + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) + + // - There must be no more pending key share requests + val unsentRequestAfterRestoration = cryptoStore2 + .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT)) + val sentRequestAfterRestoration = cryptoStore2 + .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT)) + + // Request is either sent or unsent + assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null) + + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - And log Alice on a new device + * - The new device must see the previous backup as not trusted + * - Trust the backup from the new device + * - Backup must be enabled on the new device + * - Retrieve the last version from the server + * - It must be the same + * - It must be trusted and must have with 2 signatures now + */ + @Test + fun trustKeyBackupVersionTest() { + // - Do an e2e backup to the homeserver with a recovery key + // - And log Alice on a new device + val testData = createKeysBackupScenarioWithPassword(null) + + val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService()) + + // - The new device must see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + // - Trust the backup from the new device + val latch = CountDownLatch(1) + testData.aliceSession2.getKeysBackupService().trustKeysBackupVersion( + testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + true, + TestMatrixCallback(latch) + ) + mTestHelper.await(latch) + + // Wait for backup state to be ReadyToBackUp + waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) + + // - Backup must be enabled on the new device, on the same version + assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.getKeysBackupService().keysBackupVersion?.version) + assertTrue(testData.aliceSession2.getKeysBackupService().isEnabled) + + // - Retrieve the last version from the server + val latch2 = CountDownLatch(1) + var keysVersionResult: KeysVersionResult? = null + testData.aliceSession2.getKeysBackupService().getCurrentVersion( + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: KeysVersionResult?) { + keysVersionResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // - It must be the same + assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) + + val latch3 = CountDownLatch(1) + var keysBackupVersionTrust: KeysBackupVersionTrust? = null + testData.aliceSession2.getKeysBackupService().getKeysBackupTrust(keysVersionResult!!, + object : TestMatrixCallback(latch3) { + override fun onSuccess(data: KeysBackupVersionTrust) { + keysBackupVersionTrust = data + super.onSuccess(data) + } + }) + mTestHelper.await(latch3) + + // - It must be trusted and must have 2 signatures now + assertTrue(keysBackupVersionTrust!!.usable) + assertEquals(2, keysBackupVersionTrust!!.signatures.size) + + stateObserver.stopAndCheckStates(null) + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - And log Alice on a new device + * - The new device must see the previous backup as not trusted + * - Trust the backup from the new device with the recovery key + * - Backup must be enabled on the new device + * - Retrieve the last version from the server + * - It must be the same + * - It must be trusted and must have with 2 signatures now + */ + @Test + fun trustKeyBackupVersionWithRecoveryKeyTest() { + // - Do an e2e backup to the homeserver with a recovery key + // - And log Alice on a new device + val testData = createKeysBackupScenarioWithPassword(null) + + val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService()) + + // - The new device must see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + // - Trust the backup from the new device with the recovery key + val latch = CountDownLatch(1) + testData.aliceSession2.getKeysBackupService().trustKeysBackupVersionWithRecoveryKey( + testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, + TestMatrixCallback(latch) + ) + mTestHelper.await(latch) + + // Wait for backup state to be ReadyToBackUp + waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) + + // - Backup must be enabled on the new device, on the same version + assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.getKeysBackupService().keysBackupVersion?.version) + assertTrue(testData.aliceSession2.getKeysBackupService().isEnabled) + + // - Retrieve the last version from the server + val latch2 = CountDownLatch(1) + var keysVersionResult: KeysVersionResult? = null + testData.aliceSession2.getKeysBackupService().getCurrentVersion( + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: KeysVersionResult?) { + keysVersionResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // - It must be the same + assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) + + val latch3 = CountDownLatch(1) + var keysBackupVersionTrust: KeysBackupVersionTrust? = null + testData.aliceSession2.getKeysBackupService().getKeysBackupTrust(keysVersionResult!!, + object : TestMatrixCallback(latch3) { + override fun onSuccess(data: KeysBackupVersionTrust) { + keysBackupVersionTrust = data + super.onSuccess(data) + } + }) + mTestHelper.await(latch3) + + // - It must be trusted and must have 2 signatures now + assertTrue(keysBackupVersionTrust!!.usable) + assertEquals(2, keysBackupVersionTrust!!.signatures.size) + + stateObserver.stopAndCheckStates(null) + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - And log Alice on a new device + * - The new device must see the previous backup as not trusted + * - Try to trust the backup from the new device with a wrong recovery key + * - It must fail + * - The backup must still be untrusted and disabled + */ + @Test + fun trustKeyBackupVersionWithWrongRecoveryKeyTest() { + // - Do an e2e backup to the homeserver with a recovery key + // - And log Alice on a new device + val testData = createKeysBackupScenarioWithPassword(null) + + val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService()) + + // - The new device must see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + // - Try to trust the backup from the new device with a wrong recovery key + val latch = CountDownLatch(1) + testData.aliceSession2.getKeysBackupService().trustKeysBackupVersionWithRecoveryKey( + testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + "Bad recovery key", + TestMatrixCallback(latch, false) + ) + mTestHelper.await(latch) + + // - The new device must still see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + stateObserver.stopAndCheckStates(null) + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a password + * - And log Alice on a new device + * - The new device must see the previous backup as not trusted + * - Trust the backup from the new device with the password + * - Backup must be enabled on the new device + * - Retrieve the last version from the server + * - It must be the same + * - It must be trusted and must have with 2 signatures now + */ + @Test + fun trustKeyBackupVersionWithPasswordTest() { + val password = "Password" + + // - Do an e2e backup to the homeserver with a password + // - And log Alice on a new device + val testData = createKeysBackupScenarioWithPassword(password) + + val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService()) + + // - The new device must see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + // - Trust the backup from the new device with the password + val latch = CountDownLatch(1) + testData.aliceSession2.getKeysBackupService().trustKeysBackupVersionWithPassphrase( + testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + password, + TestMatrixCallback(latch) + ) + mTestHelper.await(latch) + + // Wait for backup state to be ReadyToBackUp + waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) + + // - Backup must be enabled on the new device, on the same version + assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.getKeysBackupService().keysBackupVersion?.version) + assertTrue(testData.aliceSession2.getKeysBackupService().isEnabled) + + // - Retrieve the last version from the server + val latch2 = CountDownLatch(1) + var keysVersionResult: KeysVersionResult? = null + testData.aliceSession2.getKeysBackupService().getCurrentVersion( + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: KeysVersionResult?) { + keysVersionResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // - It must be the same + assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) + + val latch3 = CountDownLatch(1) + var keysBackupVersionTrust: KeysBackupVersionTrust? = null + testData.aliceSession2.getKeysBackupService().getKeysBackupTrust(keysVersionResult!!, + object : TestMatrixCallback(latch3) { + override fun onSuccess(data: KeysBackupVersionTrust) { + keysBackupVersionTrust = data + super.onSuccess(data) + } + }) + mTestHelper.await(latch3) + + // - It must be trusted and must have 2 signatures now + assertTrue(keysBackupVersionTrust!!.usable) + assertEquals(2, keysBackupVersionTrust!!.signatures.size) + + stateObserver.stopAndCheckStates(null) + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a password + * - And log Alice on a new device + * - The new device must see the previous backup as not trusted + * - Try to trust the backup from the new device with a wrong password + * - It must fail + * - The backup must still be untrusted and disabled + */ + @Test + fun trustKeyBackupVersionWithWrongPasswordTest() { + val password = "Password" + val badPassword = "Bad Password" + + // - Do an e2e backup to the homeserver with a password + // - And log Alice on a new device + val testData = createKeysBackupScenarioWithPassword(password) + + val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService()) + + // - The new device must see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + // - Try to trust the backup from the new device with a wrong password + val latch = CountDownLatch(1) + testData.aliceSession2.getKeysBackupService().trustKeysBackupVersionWithPassphrase( + testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + badPassword, + TestMatrixCallback(latch, false) + ) + mTestHelper.await(latch) + + // - The new device must still see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + stateObserver.stopAndCheckStates(null) + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - Log Alice on a new device + * - Try to restore the e2e backup with a wrong recovery key + * - It must fail + */ + @Test + fun restoreKeysBackupWithAWrongRecoveryKeyTest() { + val testData = createKeysBackupScenarioWithPassword(null) + + // - Try to restore the e2e backup with a wrong recovery key + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + testData.aliceSession2.getKeysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + "EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d", + null, + null, + null, + object : TestMatrixCallback(latch2, false) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // onSuccess may not have been called + assertNull(importRoomKeysResult) + + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a password + * - Log Alice on a new device + * - Restore the e2e backup with the password + * - Restore must be successful + */ + @Test + fun testBackupWithPassword() { + val password = "password" + + val testData = createKeysBackupScenarioWithPassword(password) + + // - Restore the e2e backup with the password + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + val steps = ArrayList() + + testData.aliceSession2.getKeysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + password, + null, + null, + object : StepProgressListener { + override fun onStepProgress(step: StepProgressListener.Step) { + steps.add(step) + } + }, + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // Check steps + assertEquals(105, steps.size) + + for (i in 0..100) { + assertTrue(steps[i] is StepProgressListener.Step.ComputingKey) + assertEquals(i, (steps[i] as StepProgressListener.Step.ComputingKey).progress) + assertEquals(100, (steps[i] as StepProgressListener.Step.ComputingKey).total) + } + + assertTrue(steps[101] is StepProgressListener.Step.DownloadingKey) + + // 2 Keys to import, value will be 0%, 50%, 100% + for (i in 102..104) { + assertTrue(steps[i] is StepProgressListener.Step.ImportingKey) + assertEquals(100, (steps[i] as StepProgressListener.Step.ImportingKey).total) + } + + assertEquals(0, (steps[102] as StepProgressListener.Step.ImportingKey).progress) + assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress) + assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress) + + checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) + + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a password + * - Log Alice on a new device + * - Try to restore the e2e backup with a wrong password + * - It must fail + */ + @Test + fun restoreKeysBackupWithAWrongPasswordTest() { + val password = "password" + val wrongPassword = "passw0rd" + + val testData = createKeysBackupScenarioWithPassword(password) + + // - Try to restore the e2e backup with a wrong password + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + testData.aliceSession2.getKeysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + wrongPassword, + null, + null, + null, + object : TestMatrixCallback(latch2, false) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // onSuccess may not have been called + assertNull(importRoomKeysResult) + + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a password + * - Log Alice on a new device + * - Restore the e2e backup with the recovery key. + * - Restore must be successful + */ + @Test + fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() { + val password = "password" + + val testData = createKeysBackupScenarioWithPassword(password) + + // - Restore the e2e backup with the recovery key. + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + testData.aliceSession2.getKeysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, + null, + null, + null, + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) + + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - And log Alice on a new device + * - Try to restore the e2e backup with a password + * - It must fail + */ + @Test + fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() { + val testData = createKeysBackupScenarioWithPassword(null) + + // - Try to restore the e2e backup with a password + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + testData.aliceSession2.getKeysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + "password", + null, + null, + null, + object : TestMatrixCallback(latch2, false) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // onSuccess may not have been called + assertNull(importRoomKeysResult) + + testData.cryptoTestData.close() + } + + /** + * - Create a backup version + * - Check the returned KeysVersionResult is trusted + */ + @Test + fun testIsKeysBackupTrusted() { + // - Create a backup version + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + // - Do an e2e backup to the homeserver + prepareAndCreateKeysBackupData(keysBackup) + + // Get key backup version from the home server + var keysVersionResult: KeysVersionResult? = null + val lock = CountDownLatch(1) + keysBackup.getCurrentVersion(object : TestMatrixCallback(lock) { + override fun onSuccess(data: KeysVersionResult?) { + keysVersionResult = data + super.onSuccess(data) + } + }) + mTestHelper.await(lock) + + assertNotNull(keysVersionResult) + + // - Check the returned KeyBackupVersion is trusted + val latch = CountDownLatch(1) + var keysBackupVersionTrust: KeysBackupVersionTrust? = null + keysBackup.getKeysBackupTrust(keysVersionResult!!, object : MatrixCallback { + override fun onSuccess(data: KeysBackupVersionTrust) { + keysBackupVersionTrust = data + latch.countDown() + } + + override fun onFailure(failure: Throwable) { + super.onFailure(failure) + latch.countDown() + } + }) + mTestHelper.await(latch) + + assertNotNull(keysBackupVersionTrust) + assertTrue(keysBackupVersionTrust!!.usable) + assertEquals(1, keysBackupVersionTrust!!.signatures.size) + + val signature = keysBackupVersionTrust!!.signatures[0] + assertTrue(signature.valid) + assertNotNull(signature.device) + assertEquals(cryptoTestData.firstSession.getMyDevice().deviceId, signature.deviceId) + assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.credentials.deviceId) + + stateObserver.stopAndCheckStates(null) + cryptoTestData.close() + } + + /** + * Check backup starts automatically if there is an existing and compatible backup + * version on the homeserver. + * - Create a backup version + * - Restart alice session + * -> The new alice session must back up to the same version + */ + @Test + fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() { + // - Create a backup version + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + assertFalse(keysBackup.isEnabled) + + val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup) + + assertTrue(keysBackup.isEnabled) + + // - Restart alice session + // - Log Alice on a new device + val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, defaultSessionParamsWithInitialSync) + + cryptoTestData.close() + + val keysBackup2 = aliceSession2.getKeysBackupService() + + val stateObserver2 = StateObserver(keysBackup2) + + // -> The new alice session must back up to the same version + val latch = CountDownLatch(1) + var count = 0 + keysBackup2.addListener(object : KeysBackupStateListener { + override fun onStateChange(newState: KeysBackupState) { + // Check the backup completes + if (keysBackup.state == KeysBackupState.ReadyToBackUp) { + count++ + + if (count == 2) { + // Remove itself from the list of listeners + keysBackup.removeListener(this) + + latch.countDown() + } + } + } + }) + mTestHelper.await(latch) + + assertEquals(keyBackupCreationInfo.version, keysBackup2.currentBackupVersion) + + stateObserver.stopAndCheckStates(null) + stateObserver2.stopAndCheckStates(null) + aliceSession2.close() + } + + /** + * Check WrongBackUpVersion state + * + * - Make alice back up her keys to her homeserver + * - Create a new backup with fake data on the homeserver + * - Make alice back up all her keys again + * -> That must fail and her backup state must be WrongBackUpVersion + */ + @Test + fun testBackupWhenAnotherBackupWasCreated() { + // - Create a backup version + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + assertFalse(keysBackup.isEnabled) + + // Wait for keys backup to be finished + val latch0 = CountDownLatch(1) + var count = 0 + keysBackup.addListener(object : KeysBackupStateListener { + override fun onStateChange(newState: KeysBackupState) { + // Check the backup completes + if (newState == KeysBackupState.ReadyToBackUp) { + count++ + + if (count == 2) { + // Remove itself from the list of listeners + keysBackup.removeListener(this) + + latch0.countDown() + } + } + } + }) + + // - Make alice back up her keys to her homeserver + prepareAndCreateKeysBackupData(keysBackup) + + assertTrue(keysBackup.isEnabled) + + mTestHelper.await(latch0) + + // - Create a new backup with fake data on the homeserver, directly using the rest client + val latch = CountDownLatch(1) + + val megolmBackupCreationInfo = mCryptoTestHelper.createFakeMegolmBackupCreationInfo() + (keysBackup as KeysBackup).createFakeKeysBackupVersion(megolmBackupCreationInfo, TestMatrixCallback(latch)) + mTestHelper.await(latch) + + // Reset the store backup status for keys + (cryptoTestData.firstSession.getKeysBackupService() as KeysBackup).store.resetBackupMarkers() + + // - Make alice back up all her keys again + val latch2 = CountDownLatch(1) + keysBackup.backupAllGroupSessions(object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + } + }, TestMatrixCallback(latch2, false)) + mTestHelper.await(latch2) + + // -> That must fail and her backup state must be WrongBackUpVersion + assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.state) + assertFalse(keysBackup.isEnabled) + + stateObserver.stopAndCheckStates(null) + cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver + * - Log Alice on a new device + * - Post a message to have a new megolm session + * - Try to backup all + * -> It must fail. Backup state must be NotTrusted + * - Validate the old device from the new one + * -> Backup should automatically enable on the new device + * -> It must use the same backup version + * - Try to backup all again + * -> It must success + */ + @Test + fun testBackupAfterVerifyingADevice() { + // - Create a backup version + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + // - Make alice back up her keys to her homeserver + prepareAndCreateKeysBackupData(keysBackup) + + // Wait for keys backup to finish by asking again to backup keys. + val latch = CountDownLatch(1) + keysBackup.backupAllGroupSessions(object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + } + }, TestMatrixCallback(latch)) + mTestHelper.await(latch) + + val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!! + val oldKeyBackupVersion = keysBackup.currentBackupVersion + val aliceUserId = cryptoTestData.firstSession.myUserId + + // Close first Alice session, else they will share the same Crypto store and the test fails. + cryptoTestData.firstSession.close() + + // - Log Alice on a new device + val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync) + + // - Post a message to have a new megolm session + aliceSession2.setWarnOnUnknownDevices(false) + + val room2 = aliceSession2.getRoom(cryptoTestData.roomId)!! + + mTestHelper.sendTextMessage(room2, "New key", 1) + + // - Try to backup all in aliceSession2, it must fail + val keysBackup2 = aliceSession2.getKeysBackupService() + + val stateObserver2 = StateObserver(keysBackup2) + + var isSuccessful = false + val latch2 = CountDownLatch(1) + keysBackup2.backupAllGroupSessions(object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + } + }, object : TestMatrixCallback(latch2, false) { + override fun onSuccess(data: Unit) { + isSuccessful = true + super.onSuccess(data) + } + }) + mTestHelper.await(latch2) + + assertFalse(isSuccessful) + + // Backup state must be NotTrusted + assertEquals(KeysBackupState.NotTrusted, keysBackup2.state) + assertFalse(keysBackup2.isEnabled) + + // - Validate the old device from the new one + aliceSession2.setDeviceVerification(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED, oldDeviceId, aliceSession2.myUserId) + + // -> Backup should automatically enable on the new device + val latch4 = CountDownLatch(1) + keysBackup2.addListener(object : KeysBackupStateListener { + override fun onStateChange(newState: KeysBackupState) { + // Check the backup completes + if (keysBackup2.state == KeysBackupState.ReadyToBackUp) { + // Remove itself from the list of listeners + keysBackup2.removeListener(this) + + latch4.countDown() + } + } + }) + mTestHelper.await(latch4) + + // -> It must use the same backup version + assertEquals(oldKeyBackupVersion, aliceSession2.getKeysBackupService().currentBackupVersion) + + val latch5 = CountDownLatch(1) + aliceSession2.getKeysBackupService().backupAllGroupSessions(null, TestMatrixCallback(latch5)) + mTestHelper.await(latch5) + + // -> It must success + assertTrue(aliceSession2.getKeysBackupService().isEnabled) + + stateObserver.stopAndCheckStates(null) + stateObserver2.stopAndCheckStates(null) + aliceSession2.close() + cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - Delete the backup + */ + @Test + fun deleteKeysBackupTest() { + // - Create a backup version + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + assertFalse(keysBackup.isEnabled) + + val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup) + + assertTrue(keysBackup.isEnabled) + + val latch = CountDownLatch(1) + + // Delete the backup + keysBackup.deleteBackup(keyBackupCreationInfo.version, TestMatrixCallback(latch)) + + mTestHelper.await(latch) + + // Backup is now disabled + assertFalse(keysBackup.isEnabled) + + stateObserver.stopAndCheckStates(null) + cryptoTestData.close() + } + + /* ========================================================================================== + * Private + * ========================================================================================== */ + + /** + * As KeysBackup is doing asynchronous call to update its internal state, this method help to wait for the + * KeysBackup object to be in the specified state + */ + private fun waitForKeysBackupToBeInState(session: Session, state: KeysBackupState) { + // If already in the wanted state, return + if (session.getKeysBackupService().state == state) { + return + } + + // Else observe state changes + val latch = CountDownLatch(1) + + session.getKeysBackupService().addListener(object : KeysBackupStateListener { + override fun onStateChange(newState: KeysBackupState) { + if (newState == state) { + session.getKeysBackupService().removeListener(this) + latch.countDown() + } + } + }) + + mTestHelper.await(latch) + } + + private data class PrepareKeysBackupDataResult(val megolmBackupCreationInfo: MegolmBackupCreationInfo, + val version: String) + + private fun prepareAndCreateKeysBackupData(keysBackup: KeysBackupService, + password: String? = null): PrepareKeysBackupDataResult { + val stateObserver = StateObserver(keysBackup) + + var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null + val latch = CountDownLatch(1) + keysBackup.prepareKeysBackupVersion(password, null, object : MatrixCallback { + override fun onSuccess(data: MegolmBackupCreationInfo) { + megolmBackupCreationInfo = data + + latch.countDown() + } + + override fun onFailure(failure: Throwable) { + fail(failure.localizedMessage) + + latch.countDown() + } + }) + mTestHelper.await(latch) + + assertNotNull(megolmBackupCreationInfo) + + assertFalse(keysBackup.isEnabled) + + val latch2 = CountDownLatch(1) + + // Create the version + var version: String? = null + keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : TestMatrixCallback(latch2) { + override fun onSuccess(data: KeysVersion) { + assertNotNull(data) + assertNotNull(data.version) + + version = data.version + + super.onSuccess(data) + } + }) + mTestHelper.await(latch2) + + // Backup must be enable now + assertTrue(keysBackup.isEnabled) + assertNotNull(version) + + stateObserver.stopAndCheckStates(null) + return PrepareKeysBackupDataResult(megolmBackupCreationInfo!!, version!!) + } + + private fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) { + assertNotNull(keys1) + assertNotNull(keys2) + + assertEquals(keys1?.algorithm, keys2?.algorithm) + assertEquals(keys1?.roomId, keys2?.roomId) + // No need to compare the shortcut + // assertEquals(keys1?.sender_claimed_ed25519_key, keys2?.sender_claimed_ed25519_key) + assertEquals(keys1?.senderKey, keys2?.senderKey) + assertEquals(keys1?.sessionId, keys2?.sessionId) + assertEquals(keys1?.sessionKey, keys2?.sessionKey) + + assertListEquals(keys1?.forwardingCurve25519KeyChain, keys2?.forwardingCurve25519KeyChain) + assertDictEquals(keys1?.senderClaimedKeys, keys2?.senderClaimedKeys) + } + + /** + * Data class to store result of [createKeysBackupScenarioWithPassword] + */ + private data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData, + val aliceKeys: List, + val prepareKeysBackupDataResult: PrepareKeysBackupDataResult, + val aliceSession2: Session) + + /** + * Common initial condition + * - Do an e2e backup to the homeserver + * - Log Alice on a new device, and wait for its keysBackup object to be ready (in state NotTrusted) + * + * @param password optional password + */ + private fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val cryptoStore = (cryptoTestData.firstSession.getKeysBackupService() as KeysBackup).store + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100) + + // - Do an e2e backup to the homeserver + val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password) + + val latch = CountDownLatch(1) + var lastProgress = 0 + var lastTotal = 0 + keysBackup.backupAllGroupSessions(object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + lastProgress = progress + lastTotal = total + } + }, TestMatrixCallback(latch)) + mTestHelper.await(latch) + + assertEquals(2, lastProgress) + assertEquals(2, lastTotal) + + val aliceUserId = cryptoTestData.firstSession.myUserId + + // Logout first Alice session, else they will share the same Crypto store and some tests may fail. + val latch2 = CountDownLatch(1) + cryptoTestData.firstSession.signOut(true, TestMatrixCallback(latch2)) + mTestHelper.await(latch2) + + // - Log Alice on a new device + val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync) + + // Test check: aliceSession2 has no keys at login + assertEquals(0, aliceSession2.inboundGroupSessionsCount(false)) + + // Wait for backup state to be NotTrusted + waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted) + + stateObserver.stopAndCheckStates(null) + + return KeysBackupScenarioData(cryptoTestData, + aliceKeys, + prepareKeysBackupDataResult, + aliceSession2) + } + + /** + * Common restore success check after [createKeysBackupScenarioWithPassword]: + * - Imported keys number must be correct + * - The new device must have the same count of megolm keys + * - Alice must have the same keys on both devices + */ + private fun checkRestoreSuccess(testData: KeysBackupScenarioData, + total: Int, + imported: Int) { + // - Imported keys number must be correct + assertEquals(testData.aliceKeys.size, total) + assertEquals(total, imported) + + // - The new device must have the same count of megolm keys + assertEquals(testData.aliceKeys.size, testData.aliceSession2.inboundGroupSessionsCount(false)) + + // - Alice must have the same keys on both devices + for (aliceKey1 in testData.aliceKeys) { + val aliceKey2 = (testData.aliceSession2.getKeysBackupService() as KeysBackup).store + .getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!) + assertNotNull(aliceKey2) + assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys()) + } + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/StateObserver.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/StateObserver.kt new file mode 100644 index 0000000000..3f2e33d73b --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/StateObserver.kt @@ -0,0 +1,104 @@ +/* + * 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.crypto.keysbackup + +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import java.util.concurrent.CountDownLatch + +/** + * This class observe the state change of a KeysBackup object and provide a method to check the several state change + * It checks all state transitions and detected forbidden transition + */ +internal class StateObserver(private val keysBackup: KeysBackupService, + private val latch: CountDownLatch? = null, + private val expectedStateChange: Int = -1) : KeysBackupStateListener { + + private val allowedStateTransitions = listOf( + KeysBackupState.BackingUp to KeysBackupState.ReadyToBackUp, + KeysBackupState.BackingUp to KeysBackupState.WrongBackUpVersion, + + KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.Disabled, + KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.NotTrusted, + KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.ReadyToBackUp, + KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.Unknown, + KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.WrongBackUpVersion, + + KeysBackupState.Disabled to KeysBackupState.Enabling, + + KeysBackupState.Enabling to KeysBackupState.Disabled, + KeysBackupState.Enabling to KeysBackupState.ReadyToBackUp, + + KeysBackupState.NotTrusted to KeysBackupState.CheckingBackUpOnHomeserver, + // This transition happens when we trust the device + KeysBackupState.NotTrusted to KeysBackupState.ReadyToBackUp, + + KeysBackupState.ReadyToBackUp to KeysBackupState.WillBackUp, + + KeysBackupState.Unknown to KeysBackupState.CheckingBackUpOnHomeserver, + + KeysBackupState.WillBackUp to KeysBackupState.BackingUp, + + KeysBackupState.WrongBackUpVersion to KeysBackupState.CheckingBackUpOnHomeserver, + + // FIXME These transitions are observed during test, and I'm not sure they should occur. Don't have time to investigate now + KeysBackupState.ReadyToBackUp to KeysBackupState.BackingUp, + KeysBackupState.ReadyToBackUp to KeysBackupState.ReadyToBackUp, + KeysBackupState.WillBackUp to KeysBackupState.ReadyToBackUp, + KeysBackupState.WillBackUp to KeysBackupState.Unknown + ) + + private val stateList = ArrayList() + private var lastTransitionError: String? = null + + init { + keysBackup.addListener(this) + } + + // TODO Make expectedStates mandatory to enforce test + fun stopAndCheckStates(expectedStates: List?) { + keysBackup.removeListener(this) + + expectedStates?.let { + assertEquals(it.size, stateList.size) + + for (i in it.indices) { + assertEquals("The state $i is not correct. states: " + stateList.joinToString(separator = " "), it[i], stateList[i]) + } + } + + assertNull("states: " + stateList.joinToString(separator = " "), lastTransitionError) + } + + override fun onStateChange(newState: KeysBackupState) { + stateList.add(newState) + + // Check that state transition is valid + if (stateList.size >= 2 + && !allowedStateTransitions.contains(stateList[stateList.size - 2] to newState)) { + // Forbidden transition detected + lastTransitionError = "Forbidden transition detected from " + stateList[stateList.size - 2] + " to " + newState + } + + if (expectedStateChange == stateList.size) { + latch?.countDown() + } + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt new file mode 100644 index 0000000000..c05523f009 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt @@ -0,0 +1,525 @@ +/* + * 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.crypto.verification + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.sas.* +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.common.CommonTestHelper +import im.vector.matrix.android.common.CryptoTestHelper +import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import org.junit.Assert.* +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import java.util.* +import java.util.concurrent.CountDownLatch + +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class SASTest : InstrumentedTest { + private val mTestHelper = CommonTestHelper(context()) + private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + + @Test + fun test_aliceStartThenAliceCancel() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceSasMgr = aliceSession.getSasVerificationService() + val bobSasMgr = bobSession!!.getSasVerificationService() + + val bobTxCreatedLatch = CountDownLatch(1) + val bobListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + bobTxCreatedLatch.countDown() + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + bobSasMgr.addListener(bobListener) + + val txID = aliceSasMgr.beginKeyVerificationSAS(bobSession.myUserId, bobSession.getMyDevice().deviceId) + assertNotNull("Alice should have a started transaction", txID) + + val aliceKeyTx = aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID!!) + assertNotNull("Alice should have a started transaction", aliceKeyTx) + + mTestHelper.await(bobTxCreatedLatch) + bobSasMgr.removeListener(bobListener) + + val bobKeyTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID) + + assertNotNull("Bob should have started verif transaction", bobKeyTx) + assertTrue(bobKeyTx is SASVerificationTransaction) + assertNotNull("Bob should have starting a SAS transaction", bobKeyTx) + assertTrue(aliceKeyTx is SASVerificationTransaction) + assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId) + + val aliceSasTx = aliceKeyTx as SASVerificationTransaction? + val bobSasTx = bobKeyTx as SASVerificationTransaction? + + assertEquals("Alice state should be started", SasVerificationTxState.Started, aliceSasTx!!.state) + assertEquals("Bob state should be started by alice", SasVerificationTxState.OnStarted, bobSasTx!!.state) + + // Let's cancel from alice side + val cancelLatch = CountDownLatch(1) + + val bobListener2 = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + if (tx.transactionId == txID) { + if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnCancelled) { + cancelLatch.countDown() + } + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + bobSasMgr.addListener(bobListener2) + + aliceSasTx.cancel(CancelCode.User) + mTestHelper.await(cancelLatch) + + assertEquals("Should be cancelled on alice side", + SasVerificationTxState.Cancelled, aliceSasTx.state) + assertEquals("Should be cancelled on bob side", + SasVerificationTxState.OnCancelled, bobSasTx.state) + + assertEquals("Should be User cancelled on alice side", + CancelCode.User, aliceSasTx.cancelledReason) + assertEquals("Should be User cancelled on bob side", + CancelCode.User, aliceSasTx.cancelledReason) + + assertNull(bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID)) + assertNull(aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID)) + + cryptoTestData.close() + } + + @Test + fun test_key_agreement_protocols_must_include_curve25519() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val bobSession = cryptoTestData.secondSession!! + + val protocols = listOf("meh_dont_know") + val tid = "00000000" + + // Bob should receive a cancel + var canceledToDeviceEvent: Event? = null + val cancelLatch = CountDownLatch(1) + // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() { + // TODO override fun onToDeviceEvent(event: Event?) { + // TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) { + // TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) { + // TODO canceledToDeviceEvent = event + // TODO cancelLatch.countDown() + // TODO } + // TODO } + // TODO } + // TODO }) + + val aliceSession = cryptoTestData.firstSession + val aliceUserID = aliceSession.myUserId + val aliceDevice = aliceSession.getMyDevice().deviceId + + val aliceListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) { + (tx as IncomingSASVerificationTransaction).performAccept() + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + aliceSession.getSasVerificationService().addListener(aliceListener) + + fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols) + + mTestHelper.await(cancelLatch) + + val cancelReq = canceledToDeviceEvent!!.content.toModel()!! + assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) + + cryptoTestData.close() + } + + @Test + fun test_key_agreement_macs_Must_include_hmac_sha256() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val bobSession = cryptoTestData.secondSession!! + + val mac = listOf("shaBit") + val tid = "00000000" + + // Bob should receive a cancel + var canceledToDeviceEvent: Event? = null + val cancelLatch = CountDownLatch(1) + // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() { + // TODO override fun onToDeviceEvent(event: Event?) { + // TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) { + // TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) { + // TODO canceledToDeviceEvent = event + // TODO cancelLatch.countDown() + // TODO } + // TODO } + // TODO } + // TODO }) + + val aliceSession = cryptoTestData.firstSession + val aliceUserID = aliceSession.myUserId + val aliceDevice = aliceSession.getMyDevice().deviceId + + fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac) + + mTestHelper.await(cancelLatch) + + val cancelReq = canceledToDeviceEvent!!.content.toModel()!! + assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) + + cryptoTestData.close() + } + + @Test + fun test_key_agreement_short_code_include_decimal() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val bobSession = cryptoTestData.secondSession!! + + val codes = listOf("bin", "foo", "bar") + val tid = "00000000" + + // Bob should receive a cancel + var canceledToDeviceEvent: Event? = null + val cancelLatch = CountDownLatch(1) + // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() { + // TODO override fun onToDeviceEvent(event: Event?) { + // TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) { + // TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) { + // TODO canceledToDeviceEvent = event + // TODO cancelLatch.countDown() + // TODO } + // TODO } + // TODO } + // TODO }) + + val aliceSession = cryptoTestData.firstSession + val aliceUserID = aliceSession.myUserId + val aliceDevice = aliceSession.getMyDevice().deviceId + + fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes) + + mTestHelper.await(cancelLatch) + + val cancelReq = canceledToDeviceEvent!!.content.toModel()!! + assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) + + cryptoTestData.close() + } + + private fun fakeBobStart(bobSession: Session, + aliceUserID: String?, + aliceDevice: String?, + tid: String, + protocols: List = SASVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS, + hashes: List = SASVerificationTransaction.KNOWN_HASHES, + mac: List = SASVerificationTransaction.KNOWN_MACS, + codes: List = SASVerificationTransaction.KNOWN_SHORT_CODES) { + val startMessage = KeyVerificationStart() + startMessage.fromDevice = bobSession.getMyDevice().deviceId + startMessage.method = KeyVerificationStart.VERIF_METHOD_SAS + startMessage.transactionID = tid + startMessage.keyAgreementProtocols = protocols + startMessage.hashes = hashes + startMessage.messageAuthenticationCodes = mac + startMessage.shortAuthenticationStrings = codes + + val contentMap = MXUsersDevicesMap() + contentMap.setObject(aliceUserID, aliceDevice, startMessage) + + // TODO val sendLatch = CountDownLatch(1) + // TODO bobSession.cryptoRestClient.sendToDevice( + // TODO EventType.KEY_VERIFICATION_START, + // TODO contentMap, + // TODO tid, + // TODO TestMatrixCallback(sendLatch) + // TODO ) + } + + // any two devices may only have at most one key verification in flight at a time. + // If a device has two verifications in progress with the same device, then it should cancel both verifications. + @Test + fun test_aliceStartTwoRequests() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceSasMgr = aliceSession.getSasVerificationService() + + val aliceCreatedLatch = CountDownLatch(2) + val aliceCancelledLatch = CountDownLatch(2) + val createdTx = ArrayList() + val aliceListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) { + createdTx.add(tx as SASVerificationTransaction) + aliceCreatedLatch.countDown() + } + + override fun transactionUpdated(tx: SasVerificationTransaction) { + if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnCancelled) { + aliceCancelledLatch.countDown() + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + aliceSasMgr.addListener(aliceListener) + + val bobUserId = bobSession!!.myUserId + val bobDeviceId = bobSession.getMyDevice().deviceId + aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId) + aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId) + + mTestHelper.await(aliceCreatedLatch) + mTestHelper.await(aliceCancelledLatch) + + cryptoTestData.close() + } + + /** + * Test that when alice starts a 'correct' request, bob agrees. + */ + @Test + fun test_aliceAndBobAgreement() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceSasMgr = aliceSession.getSasVerificationService() + val bobSasMgr = bobSession!!.getSasVerificationService() + + var accepted: KeyVerificationAccept? = null + var startReq: KeyVerificationStart? = null + + val aliceAcceptedLatch = CountDownLatch(1) + val aliceListener = object : SasVerificationService.SasVerificationListener { + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnAccepted) { + val at = tx as SASVerificationTransaction + accepted = at.accepted + startReq = at.startReq + aliceAcceptedLatch.countDown() + } + } + } + aliceSasMgr.addListener(aliceListener) + + val bobListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) { + val at = tx as IncomingSASVerificationTransaction + at.performAccept() + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + bobSasMgr.addListener(bobListener) + + val bobUserId = bobSession.myUserId + val bobDeviceId = bobSession.getMyDevice().deviceId + aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId) + mTestHelper.await(aliceAcceptedLatch) + + assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false) + + // check that agreement is valid + assertTrue("Agreed Protocol should be Valid", accepted!!.isValid()) + assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols!!.contains(accepted!!.keyAgreementProtocol)) + assertTrue("Hash should be known by alice", startReq!!.hashes!!.contains(accepted!!.hash)) + assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes!!.contains(accepted!!.messageAuthenticationCode)) + + accepted!!.shortAuthenticationStrings?.forEach { + assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it)) + } + + cryptoTestData.close() + } + + @Test + fun test_aliceAndBobSASCode() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceSasMgr = aliceSession.getSasVerificationService() + val bobSasMgr = bobSession!!.getSasVerificationService() + + val aliceSASLatch = CountDownLatch(1) + val aliceListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + val uxState = (tx as OutgoingSASVerificationRequest).uxState + when (uxState) { + OutgoingSasVerificationRequest.UxState.SHOW_SAS -> { + aliceSASLatch.countDown() + } + else -> Unit + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + aliceSasMgr.addListener(aliceListener) + + val bobSASLatch = CountDownLatch(1) + val bobListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + val uxState = (tx as IncomingSASVerificationTransaction).uxState + when (uxState) { + IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { + tx.performAccept() + } + else -> Unit + } + if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) { + bobSASLatch.countDown() + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + bobSasMgr.addListener(bobListener) + + val bobUserId = bobSession.myUserId + val bobDeviceId = bobSession.getMyDevice().deviceId + val verificationSAS = aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId) + mTestHelper.await(aliceSASLatch) + mTestHelper.await(bobSASLatch) + + val aliceTx = aliceSasMgr.getExistingTransaction(bobUserId, verificationSAS!!) as SASVerificationTransaction + val bobTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASVerificationTransaction + + assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL), + bobTx.getShortCodeRepresentation(SasMode.DECIMAL)) + + cryptoTestData.close() + } + + @Test + fun test_happyPath() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceSasMgr = aliceSession.getSasVerificationService() + val bobSasMgr = bobSession!!.getSasVerificationService() + + val aliceSASLatch = CountDownLatch(1) + val aliceListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + val uxState = (tx as OutgoingSASVerificationRequest).uxState + when (uxState) { + OutgoingSasVerificationRequest.UxState.SHOW_SAS -> { + tx.userHasVerifiedShortCode() + } + OutgoingSasVerificationRequest.UxState.VERIFIED -> { + aliceSASLatch.countDown() + } + else -> Unit + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + aliceSasMgr.addListener(aliceListener) + + val bobSASLatch = CountDownLatch(1) + val bobListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + val uxState = (tx as IncomingSASVerificationTransaction).uxState + when (uxState) { + IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { + tx.performAccept() + } + IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { + tx.userHasVerifiedShortCode() + } + IncomingSasVerificationTransaction.UxState.VERIFIED -> { + bobSASLatch.countDown() + } + else -> Unit + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + bobSasMgr.addListener(bobListener) + + val bobUserId = bobSession.myUserId + val bobDeviceId = bobSession.getMyDevice().deviceId + aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId) + mTestHelper.await(aliceSASLatch) + mTestHelper.await(bobSASLatch) + + // Assert that devices are verified + val bobDeviceInfoFromAlicePOV: MXDeviceInfo? = aliceSession.getDeviceInfo(bobUserId, bobDeviceId) + val aliceDeviceInfoFromBobPOV: MXDeviceInfo? = bobSession.getDeviceInfo(aliceSession.myUserId, aliceSession.getMyDevice().deviceId) + + // latch wait a bit again + Thread.sleep(1000) + + assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified) + assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified) + cryptoTestData.close() + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt index cf0302166f..72affe24bb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.api.auth.data import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.util.md5 /** * This data class hold credentials user data. @@ -34,3 +35,7 @@ data class Credentials( // Optional data that may contain info to override home server and/or identity server @Json(name = "well_known") val wellKnown: WellKnown? = null ) + +internal fun Credentials.sessionId(): String { + return (if (deviceId.isNullOrBlank()) userId else "$userId|$deviceId").md5() +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index a0ac751a72..5bd219247c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -75,6 +75,11 @@ interface Session : val myUserId: String get() = sessionParams.credentials.userId + /** + * The sessionId + */ + val sessionId: String + /** * This method allow to open a session. It does start some service on the background. */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt index 22bf564a8a..224b4262ab 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt @@ -25,7 +25,6 @@ object EventType { const val MESSAGE = "m.room.message" const val STICKER = "m.sticker" const val ENCRYPTED = "m.room.encrypted" - const val ENCRYPTION = "m.room.encryption" const val FEEDBACK = "m.room.message.feedback" const val TYPING = "m.typing" const val REDACTION = "m.room.redaction" @@ -54,6 +53,7 @@ object EventType { const val STATE_ROOM_HISTORY_VISIBILITY = "m.room.history_visibility" const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups" const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events" + const val STATE_ROOM_ENCRYPTION = "m.room.encryption" // Call Events @@ -91,7 +91,8 @@ object EventType { STATE_ROOM_CANONICAL_ALIAS, STATE_ROOM_HISTORY_VISIBILITY, STATE_ROOM_RELATED_GROUPS, - STATE_ROOM_PINNED_EVENT + STATE_ROOM_PINNED_EVENT, + STATE_ROOM_ENCRYPTION ) fun isStateEvent(type: String): Boolean { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index 3221c355e8..0c3316e802 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt @@ -22,12 +22,13 @@ import im.vector.matrix.android.api.session.room.members.MembershipService import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.relation.RelationService import im.vector.matrix.android.api.session.room.notification.RoomPushRuleService -import im.vector.matrix.android.api.session.room.reporting.ReportingService import im.vector.matrix.android.api.session.room.read.ReadService +import im.vector.matrix.android.api.session.room.reporting.ReportingService import im.vector.matrix.android.api.session.room.send.DraftService 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.util.Optional /** @@ -38,6 +39,7 @@ interface Room : SendService, DraftService, ReadService, + TypingService, MembershipService, StateService, ReportingService, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/crypto/RoomCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/crypto/RoomCryptoService.kt index f8c15fde47..124b2aef17 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/crypto/RoomCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/crypto/RoomCryptoService.kt @@ -16,6 +16,8 @@ package im.vector.matrix.android.api.session.room.crypto +import im.vector.matrix.android.api.MatrixCallback + interface RoomCryptoService { fun isEncrypted(): Boolean @@ -23,4 +25,6 @@ interface RoomCryptoService { fun encryptionAlgorithm(): String? fun shouldEncryptForInvitedMembers(): Boolean + + fun enableEncryptionWithAlgorithm(algorithm: String, callback: MatrixCallback) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/RoomMemberQueryParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/RoomMemberQueryParams.kt index 7a8518c5de..b3260c28fd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/RoomMemberQueryParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/RoomMemberQueryParams.kt @@ -29,7 +29,8 @@ fun roomMemberQueryParams(init: (RoomMemberQueryParams.Builder.() -> Unit) = {}) data class RoomMemberQueryParams( val displayName: QueryStringValue, val memberships: List, - val userId: QueryStringValue + val userId: QueryStringValue, + val excludeSelf: Boolean ) { class Builder { @@ -37,11 +38,13 @@ data class RoomMemberQueryParams( var userId: QueryStringValue = QueryStringValue.NoCondition var displayName: QueryStringValue = QueryStringValue.IsNotEmpty var memberships: List = Membership.all() + var excludeSelf: Boolean = false fun build() = RoomMemberQueryParams( displayName = displayName, memberships = memberships, - userId = userId + userId = userId, + excludeSelf = excludeSelf ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomGuestAccessContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomGuestAccessContent.kt new file mode 100644 index 0000000000..4c814f7914 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomGuestAccessContent.kt @@ -0,0 +1,37 @@ +/* + * 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.api.session.room.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Class representing the EventType.STATE_ROOM_GUEST_ACCESS state event content + * Ref: https://matrix.org/docs/spec/client_server/latest#m-room-guest-access + */ +@JsonClass(generateAdapter = true) +data class RoomGuestAccessContent( + // Required. Whether guests can join the room. One of: ["can_join", "forbidden"] + @Json(name = "guest_access") val guestAccess: GuestAccess? = null +) + +enum class GuestAccess(val value: String) { + @Json(name = "can_join") + CanJoin("can_join"), + @Json(name = "forbidden") + Forbidden("forbidden") +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt index 582a10a4f3..1f7a7b144a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt @@ -44,7 +44,8 @@ data class RoomSummary( val versioningState: VersioningState = VersioningState.NONE, val readMarkerId: String? = null, val userDrafts: List = emptyList(), - var isEncrypted: Boolean + var isEncrypted: Boolean, + val typingRoomMemberIds: List = emptyList() ) { val isVersioned: Boolean diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt index 74298b42d5..2119c586db 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt @@ -28,6 +28,8 @@ import im.vector.matrix.android.api.session.room.model.PowerLevelsContent import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility import im.vector.matrix.android.internal.auth.data.ThreePidMedium +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import timber.log.Timber /** * Parameter to create a room, with facilities functions to configure it @@ -88,7 +90,7 @@ class CreateRoomParams { * A list of state events to set in the new room. * This allows the user to override the default state events set in the new room. * The expected format of the state events are an object with type, state_key and content keys set. - * Takes precedence over events set by presets, but gets overriden by name and topic keys. + * Takes precedence over events set by presets, but gets overridden by name and topic keys. */ @Json(name = "initial_state") var initialStates: MutableList? = null @@ -120,14 +122,14 @@ class CreateRoomParams { * * @param algorithm the algorithm */ - fun addCryptoAlgorithm(algorithm: String) { - if (algorithm.isNotBlank()) { - val contentMap = HashMap() - contentMap["algorithm"] = algorithm + fun enableEncryptionWithAlgorithm(algorithm: String) { + if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) { + val contentMap = mapOf("algorithm" to algorithm) - val algoEvent = Event(type = EventType.ENCRYPTION, - stateKey = "", - content = contentMap.toContent() + val algoEvent = Event( + type = EventType.STATE_ROOM_ENCRYPTION, + stateKey = "", + content = contentMap.toContent() ) if (null == initialStates) { @@ -135,6 +137,8 @@ class CreateRoomParams { } else { initialStates!!.add(algoEvent) } + } else { + Timber.e("Unsupported algorithm: $algorithm") } } @@ -145,15 +149,15 @@ class CreateRoomParams { */ fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?) { // Remove the existing value if any. - initialStates?.removeAll { it.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY } + initialStates?.removeAll { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY } if (historyVisibility != null) { - val contentMap = HashMap() - contentMap["history_visibility"] = historyVisibility + val contentMap = mapOf("history_visibility" to historyVisibility) - val historyVisibilityEvent = Event(type = EventType.STATE_ROOM_HISTORY_VISIBILITY, - stateKey = "", - content = contentMap.toContent()) + val historyVisibilityEvent = Event( + type = EventType.STATE_ROOM_HISTORY_VISIBILITY, + stateKey = "", + content = contentMap.toContent()) if (null == initialStates) { initialStates = mutableListOf(historyVisibilityEvent) @@ -192,8 +196,8 @@ class CreateRoomParams { */ fun isDirect(): Boolean { return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT - && isDirect == true - && (1 == getInviteCount() || 1 == getInvite3PidCount()) + && isDirect == true + && (1 == getInviteCount() || 1 == getInvite3PidCount()) } /** @@ -218,8 +222,8 @@ class CreateRoomParams { invite3pids = ArrayList() } val pid = Invite3Pid(idServer = hsConfig.identityServerUri.host!!, - medium = ThreePidMedium.EMAIL, - address = id) + medium = ThreePidMedium.EMAIL, + address = id) invite3pids!!.add(pid) } else if (isUserId(id)) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt index 09c77e9860..55bf816b7a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt @@ -28,6 +28,11 @@ interface StateService { */ fun updateTopic(topic: String, callback: MatrixCallback) + /** + * Enable encryption of the room + */ + fun enableEncryption(algorithm: String, callback: MatrixCallback) + fun getStateEvent(eventType: String): Event? fun getStateEventLive(eventType: String): LiveData> diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/typing/TypingService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/typing/TypingService.kt new file mode 100644 index 0000000000..8ef550531e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/typing/TypingService.kt @@ -0,0 +1,38 @@ +/* + * Copyright 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.typing + +/** + * This interface defines methods to handle typing data. It's implemented at the room level. + */ +interface TypingService { + + /** + * To call when user is typing a message in the room + * The SDK will handle the requests scheduling to the homeserver: + * - No more than one typing request per 10s + * - If not called after 10s, the SDK will notify the homeserver that the user is not typing anymore + */ + fun userIsTyping() + + /** + * To call when user stops typing in the room + * Notify immediately the homeserver that the user is not typing anymore in the room, for + * instance when user has emptied the composer, or when the user quits the timeline screen. + */ + fun userStopsTyping() +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt index 76ca9291ec..3fb086ac45 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt @@ -40,8 +40,8 @@ interface SignOutService { /** * Sign out, and release the session, clear all the session data, including crypto data - * @param sigOutFromHomeserver true if the sign out request has to be done + * @param signOutFromHomeserver true if the sign out request has to be done */ - fun signOut(sigOutFromHomeserver: Boolean, + fun signOut(signOutFromHomeserver: Boolean, callback: MatrixCallback): Cancelable } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt index c813a6813f..918f5f2f55 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal import im.vector.matrix.android.api.auth.data.SessionParams +import im.vector.matrix.android.api.auth.data.sessionId import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.di.MatrixComponent @@ -29,10 +30,11 @@ import javax.inject.Inject internal class SessionManager @Inject constructor(private val matrixComponent: MatrixComponent, private val sessionParamsStore: SessionParamsStore) { + // SessionId -> SessionComponent private val sessionComponents = HashMap() - fun getSessionComponent(userId: String): SessionComponent? { - val sessionParams = sessionParamsStore.get(userId) ?: return null + fun getSessionComponent(sessionId: String): SessionComponent? { + val sessionParams = sessionParamsStore.get(sessionId) ?: return null return getOrCreateSessionComponent(sessionParams) } @@ -40,17 +42,17 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M return getOrCreateSessionComponent(sessionParams).session() } - fun releaseSession(userId: String) { - if (sessionComponents.containsKey(userId).not()) { - throw RuntimeException("You don't have a session for the user $userId") + fun releaseSession(sessionId: String) { + if (sessionComponents.containsKey(sessionId).not()) { + throw RuntimeException("You don't have a session for id $sessionId") } - sessionComponents.remove(userId)?.also { + sessionComponents.remove(sessionId)?.also { it.session().close() } } private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent { - return sessionComponents.getOrPut(sessionParams.credentials.userId) { + return sessionComponents.getOrPut(sessionParams.credentials.sessionId()) { DaggerSessionComponent .factory() .create(matrixComponent, sessionParams) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt index 93349f4bbc..d5dd7e2959 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt @@ -45,14 +45,15 @@ import okhttp3.OkHttpClient import javax.inject.Inject import javax.net.ssl.HttpsURLConnection -internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated - private val okHttpClient: Lazy, - private val retrofitFactory: RetrofitFactory, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val sessionParamsStore: SessionParamsStore, - private val sessionManager: SessionManager, - private val sessionCreator: SessionCreator, - private val pendingSessionStore: PendingSessionStore +internal class DefaultAuthenticationService @Inject constructor( + @Unauthenticated + private val okHttpClient: Lazy, + private val retrofitFactory: RetrofitFactory, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val sessionParamsStore: SessionParamsStore, + private val sessionManager: SessionManager, + private val sessionCreator: SessionCreator, + private val pendingSessionStore: PendingSessionStore ) : AuthenticationService { private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData() @@ -112,7 +113,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated // First check the homeserver version runCatching { - executeRequest { + executeRequest(null) { apiCall = authAPI.versions() } } @@ -141,7 +142,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated val authAPI = buildAuthAPI(homeServerConnectionConfig) // Ok, try to get the config.json file of a RiotWeb client - val riotConfig = executeRequest { + val riotConfig = executeRequest(null) { apiCall = authAPI.getRiotConfig() } @@ -153,7 +154,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) - val versions = executeRequest { + val versions = executeRequest(null) { apiCall = newAuthAPI.versions() } @@ -167,7 +168,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult { return if (versions.isSupportedBySdk()) { // Get the login flow - val loginFlowResponse = executeRequest { + val loginFlowResponse = executeRequest(null) { apiCall = authAPI.getLoginFlows() } LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt index 57c22b0053..f99b95c2b3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt @@ -21,7 +21,7 @@ import im.vector.matrix.android.api.auth.data.SessionParams internal interface SessionParamsStore { - fun get(userId: String): SessionParams? + fun get(sessionId: String): SessionParams? fun getLast(): SessionParams? @@ -29,11 +29,11 @@ internal interface SessionParamsStore { suspend fun save(sessionParams: SessionParams) - suspend fun setTokenInvalid(userId: String) + suspend fun setTokenInvalid(sessionId: String) suspend fun updateCredentials(newCredentials: Credentials) - suspend fun delete(userId: String) + suspend fun delete(sessionId: String) suspend fun deleteAll() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt index 7d7f8cc22c..e5e77cb14a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt @@ -17,7 +17,7 @@ package im.vector.matrix.android.internal.auth.db import im.vector.matrix.android.api.auth.data.Credentials -import im.vector.matrix.android.internal.auth.createSessionId +import im.vector.matrix.android.api.auth.data.sessionId import im.vector.matrix.android.internal.di.MoshiProvider import io.realm.DynamicRealm import io.realm.RealmMigration @@ -71,14 +71,13 @@ internal object AuthRealmMigration : RealmMigration { ?.addField(SessionParamsEntityFields.SESSION_ID, String::class.java) ?.setRequired(SessionParamsEntityFields.SESSION_ID, true) ?.transform { - val userId = it.getString(SessionParamsEntityFields.USER_ID) val credentialsJson = it.getString(SessionParamsEntityFields.CREDENTIALS_JSON) val credentials = MoshiProvider.providesMoshi() .adapter(Credentials::class.java) .fromJson(credentialsJson) - it.set(SessionParamsEntityFields.SESSION_ID, createSessionId(userId, credentials?.deviceId)) + it.set(SessionParamsEntityFields.SESSION_ID, credentials!!.sessionId()) } ?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt index a4774c632a..9491d5737c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.auth.db import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.SessionParams +import im.vector.matrix.android.api.auth.data.sessionId import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.database.awaitTransaction import im.vector.matrix.android.internal.di.AuthDatabase @@ -42,11 +43,11 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S } } - override fun get(userId: String): SessionParams? { + override fun get(sessionId: String): SessionParams? { return Realm.getInstance(realmConfiguration).use { realm -> realm .where(SessionParamsEntity::class.java) - .equalTo(SessionParamsEntityFields.USER_ID, userId) + .equalTo(SessionParamsEntityFields.SESSION_ID, sessionId) .findAll() .map { mapper.map(it) } .firstOrNull() @@ -76,17 +77,17 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S } } - override suspend fun setTokenInvalid(userId: String) { + override suspend fun setTokenInvalid(sessionId: String) { awaitTransaction(realmConfiguration) { realm -> val currentSessionParams = realm .where(SessionParamsEntity::class.java) - .equalTo(SessionParamsEntityFields.USER_ID, userId) + .equalTo(SessionParamsEntityFields.SESSION_ID, sessionId) .findAll() .firstOrNull() if (currentSessionParams == null) { // Should not happen - "Session param not found for user $userId" + "Session param not found for id $sessionId" .let { Timber.w(it) } .also { error(it) } } else { @@ -99,14 +100,14 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S awaitTransaction(realmConfiguration) { realm -> val currentSessionParams = realm .where(SessionParamsEntity::class.java) - .equalTo(SessionParamsEntityFields.USER_ID, newCredentials.userId) + .equalTo(SessionParamsEntityFields.SESSION_ID, newCredentials.sessionId()) .findAll() .map { mapper.map(it) } .firstOrNull() if (currentSessionParams == null) { // Should not happen - "Session param not found for user ${newCredentials.userId}" + "Session param not found for id ${newCredentials.sessionId()}" .let { Timber.w(it) } .also { error(it) } } else { @@ -123,10 +124,10 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S } } - override suspend fun delete(userId: String) { + override suspend fun delete(sessionId: String) { awaitTransaction(realmConfiguration) { it.where(SessionParamsEntity::class.java) - .equalTo(SessionParamsEntityFields.USER_ID, userId) + .equalTo(SessionParamsEntityFields.SESSION_ID, sessionId) .findAll() .deleteAllFromRealm() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt index d4ba1eb818..ebd50a6924 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Moshi 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.SessionParams -import im.vector.matrix.android.internal.auth.createSessionId +import im.vector.matrix.android.api.auth.data.sessionId import javax.inject.Inject internal class SessionParamsMapper @Inject constructor(moshi: Moshi) { @@ -50,7 +50,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) { return null } return SessionParamsEntity( - createSessionId(sessionParams.credentials.userId, sessionParams.credentials.deviceId), + sessionParams.credentials.sessionId(), sessionParams.credentials.userId, credentialsJson, homeServerConnectionConfigJson, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt index b847773682..4d98ddcf08 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt @@ -72,7 +72,7 @@ internal class DefaultLoginWizard( } else { PasswordLoginParams.userIdentifier(login, password, deviceName) } - val credentials = executeRequest { + val credentials = executeRequest(null) { apiCall = authAPI.login(loginParams) } @@ -95,7 +95,7 @@ internal class DefaultLoginWizard( pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1) .also { pendingSessionStore.savePendingSessionData(it) } - val result = executeRequest { + val result = executeRequest(null) { apiCall = authAPI.resetPassword(AddThreePidRegistrationParams.from(param)) } @@ -120,7 +120,7 @@ internal class DefaultLoginWizard( resetPasswordData.newPassword ) - executeRequest { + executeRequest(null) { apiCall = authAPI.resetPasswordMailConfirmed(param) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegisterAddThreePidTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegisterAddThreePidTask.kt index 0246075153..c455ccf48c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegisterAddThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegisterAddThreePidTask.kt @@ -29,11 +29,12 @@ internal interface RegisterAddThreePidTask : Task { ) } -internal class DefaultRegisterTask(private val authAPI: AuthAPI) - : RegisterTask { +internal class DefaultRegisterTask( + private val authAPI: AuthAPI +) : RegisterTask { override suspend fun execute(params: RegisterTask.Params): Credentials { try { - return executeRequest { + return executeRequest(null) { apiCall = authAPI.register(params.registrationParams) } } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/ValidateCodeTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/ValidateCodeTask.kt index da75b839a6..30f9aaa705 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/ValidateCodeTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/ValidateCodeTask.kt @@ -27,11 +27,12 @@ internal interface ValidateCodeTask : Task onRoomEncryptionEvent(roomId, event) + event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) } @@ -155,7 +155,7 @@ internal class DefaultCryptoService @Inject constructor( fun onLiveEvent(roomId: String, event: Event) { when { - event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event) + event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) } @@ -482,7 +482,7 @@ internal class DefaultCryptoService @Inject constructor( */ override fun isRoomEncrypted(roomId: String): Boolean { val encryptionEvent = monarchy.fetchCopied { - EventEntity.where(it, roomId = roomId, type = EventType.ENCRYPTION).findFirst() + EventEntity.where(it, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION).findFirst() } return encryptionEvent != null } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt index 3eafa73fab..2d0c77c768 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt @@ -22,9 +22,10 @@ import im.vector.matrix.android.internal.di.UserId import timber.log.Timber import javax.inject.Inject -internal class SetDeviceVerificationAction @Inject constructor(private val cryptoStore: IMXCryptoStore, - @UserId private val userId: String, - private val keysBackup: KeysBackup) { +internal class SetDeviceVerificationAction @Inject constructor( + private val cryptoStore: IMXCryptoStore, + @UserId private val userId: String, + private val keysBackup: KeysBackup) { fun handle(verificationStatus: Int, deviceId: String, userId: String) { val device = cryptoStore.getUserDevice(deviceId, userId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt index 91b3d6b056..99267ee89c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt @@ -1221,7 +1221,7 @@ internal class KeysBackup @Inject constructor( // Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver checkAndStartKeysBackup() } - else -> + else -> // Come back to the ready state so that we will retry on the next received key keysBackupStateManager.state = KeysBackupState.ReadyToBackUp } @@ -1339,6 +1339,31 @@ internal class KeysBackup @Inject constructor( return sessionBackupData } + /* ========================================================================================== + * For test only + * ========================================================================================== */ + + // Direct access for test only + @VisibleForTesting + val store + get() = cryptoStore + + @VisibleForTesting + fun createFakeKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, + callback: MatrixCallback) { + val createKeysBackupVersionBody = CreateKeysBackupVersionBody() + createKeysBackupVersionBody.algorithm = keysBackupCreationInfo.algorithm + @Suppress("UNCHECKED_CAST") + createKeysBackupVersionBody.authData = MoshiProvider.providesMoshi().adapter(Map::class.java) + .fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict? + + createKeysBackupVersionTask + .configureWith(createKeysBackupVersionBody) { + this.callback = callback + } + .executeBy(taskExecutor) + } + companion object { // Maximum delay in ms in {@link maybeBackupKeys} private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10_000L diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt index 78ae0f7ea6..9b8183bd02 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt @@ -21,15 +21,18 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.CreateKeys import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface CreateKeysBackupVersionTask : Task -internal class DefaultCreateKeysBackupVersionTask @Inject constructor(private val roomKeysApi: RoomKeysApi) - : CreateKeysBackupVersionTask { +internal class DefaultCreateKeysBackupVersionTask @Inject constructor( + private val roomKeysApi: RoomKeysApi, + private val eventBus: EventBus +) : CreateKeysBackupVersionTask { override suspend fun execute(params: CreateKeysBackupVersionBody): KeysVersion { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomKeysApi.createKeysBackupVersion(params) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt index 2b1f3df353..9712bb099b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup.tasks import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface DeleteBackupTask : Task { @@ -27,11 +28,13 @@ internal interface DeleteBackupTask : Task { ) } -internal class DefaultDeleteBackupTask @Inject constructor(private val roomKeysApi: RoomKeysApi) - : DeleteBackupTask { +internal class DefaultDeleteBackupTask @Inject constructor( + private val roomKeysApi: RoomKeysApi, + private val eventBus: EventBus +) : DeleteBackupTask { override suspend fun execute(params: DeleteBackupTask.Params) { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomKeysApi.deleteBackup(params.version) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt index ccb3645ef3..72173ec7f4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup.tasks import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface DeleteRoomSessionDataTask : Task { @@ -29,11 +30,13 @@ internal interface DeleteRoomSessionDataTask : Task { @@ -28,11 +29,13 @@ internal interface DeleteRoomSessionsDataTask : Task { @@ -27,11 +28,13 @@ internal interface DeleteSessionsDataTask : Task -internal class DefaultGetKeysBackupLastVersionTask @Inject constructor(private val roomKeysApi: RoomKeysApi) - : GetKeysBackupLastVersionTask { +internal class DefaultGetKeysBackupLastVersionTask @Inject constructor( + private val roomKeysApi: RoomKeysApi, + private val eventBus: EventBus +) : GetKeysBackupLastVersionTask { override suspend fun execute(params: Unit): KeysVersionResult { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomKeysApi.getKeysBackupLastVersion() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt index ea0b9b9f3a..70cc7472a9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt @@ -20,15 +20,18 @@ import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetKeysBackupVersionTask : Task -internal class DefaultGetKeysBackupVersionTask @Inject constructor(private val roomKeysApi: RoomKeysApi) - : GetKeysBackupVersionTask { +internal class DefaultGetKeysBackupVersionTask @Inject constructor( + private val roomKeysApi: RoomKeysApi, + private val eventBus: EventBus +) : GetKeysBackupVersionTask { override suspend fun execute(params: String): KeysVersionResult { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomKeysApi.getKeysBackupVersion(params) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt index a36850ba08..327836ed5f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeyBackupData import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetRoomSessionDataTask : Task { @@ -30,11 +31,13 @@ internal interface GetRoomSessionDataTask : Task { @@ -29,11 +30,13 @@ internal interface GetRoomSessionsDataTask : Task { @@ -28,11 +29,13 @@ internal interface GetSessionsDataTask : Task { @@ -32,11 +33,13 @@ internal interface StoreRoomSessionDataTask : Task { @@ -31,11 +32,13 @@ internal interface StoreRoomSessionsDataTask : Task { @@ -30,11 +31,13 @@ internal interface StoreSessionsDataTask : Task { @@ -29,11 +30,13 @@ internal interface UpdateKeysBackupVersionTask : Task { val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map) - val keysClaimResponse = executeRequest { + val keysClaimResponse = executeRequest(eventBus) { apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body) } val map = MXUsersDevicesMap() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt index 0e52c118d9..fbbaa0e0f7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface DeleteDeviceTask : Task { @@ -31,12 +32,14 @@ internal interface DeleteDeviceTask : Task { ) } -internal class DefaultDeleteDeviceTask @Inject constructor(private val cryptoApi: CryptoApi) - : DeleteDeviceTask { +internal class DefaultDeleteDeviceTask @Inject constructor( + private val cryptoApi: CryptoApi, + private val eventBus: EventBus +) : DeleteDeviceTask { override suspend fun execute(params: DeleteDeviceTask.Params) { try { - executeRequest { + executeRequest(eventBus) { apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams()) } } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt index eb23f02275..19e0f6efb5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface DeleteDeviceWithUserPasswordTask : Task { @@ -33,12 +34,14 @@ internal interface DeleteDeviceWithUserPasswordTask : Task { @@ -31,8 +32,10 @@ internal interface DownloadKeysForUsersTask : Task() }.orEmpty() @@ -45,7 +48,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor(private val crypt body.token = params.token } - return executeRequest { + return executeRequest(eventBus) { apiCall = cryptoApi.downloadKeysForUsers(body) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDeviceInfoTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDeviceInfoTask.kt index f97e86a57d..9d9513b773 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDeviceInfoTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDeviceInfoTask.kt @@ -20,17 +20,20 @@ import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetDeviceInfoTask : Task { data class Params(val deviceId: String) } -internal class DefaultGetDeviceInfoTask @Inject constructor(private val cryptoApi: CryptoApi) - : GetDeviceInfoTask { +internal class DefaultGetDeviceInfoTask @Inject constructor( + private val cryptoApi: CryptoApi, + private val eventBus: EventBus +) : GetDeviceInfoTask { override suspend fun execute(params: GetDeviceInfoTask.Params): DeviceInfo { - return executeRequest { + return executeRequest(eventBus) { apiCall = cryptoApi.getDeviceInfo(params.deviceId) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDevicesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDevicesTask.kt index d6e82adb4e..7a805f6a08 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDevicesTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDevicesTask.kt @@ -20,15 +20,18 @@ import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetDevicesTask : Task -internal class DefaultGetDevicesTask @Inject constructor(private val cryptoApi: CryptoApi) - : GetDevicesTask { +internal class DefaultGetDevicesTask @Inject constructor( + private val cryptoApi: CryptoApi, + private val eventBus: EventBus +) : GetDevicesTask { override suspend fun execute(params: Unit): DevicesListResponse { - return executeRequest { + return executeRequest(eventBus) { apiCall = cryptoApi.getDevices() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetKeyChangesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetKeyChangesTask.kt index 42c36bd1e7..84e2c293b9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetKeyChangesTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetKeyChangesTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.model.rest.KeyChangesResponse import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetKeyChangesTask : Task { @@ -31,11 +32,13 @@ internal interface GetKeyChangesTask : Task { ) } -internal class DefaultSendToDeviceTask @Inject constructor(private val cryptoApi: CryptoApi) - : SendToDeviceTask { +internal class DefaultSendToDeviceTask @Inject constructor( + private val cryptoApi: CryptoApi, + private val eventBus: EventBus +) : SendToDeviceTask { override suspend fun execute(params: SendToDeviceTask.Params) { val sendToDeviceBody = SendToDeviceBody() sendToDeviceBody.messages = params.contentMap.map - return executeRequest { + return executeRequest(eventBus) { apiCall = cryptoApi.sendToDevice( params.eventType, params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(), diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SetDeviceNameTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SetDeviceNameTask.kt index 47f3050b88..74757c5cb3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SetDeviceNameTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SetDeviceNameTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.model.rest.UpdateDeviceInfoBody import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface SetDeviceNameTask : Task { @@ -31,14 +32,16 @@ internal interface SetDeviceNameTask : Task { ) } -internal class DefaultSetDeviceNameTask @Inject constructor(private val cryptoApi: CryptoApi) - : SetDeviceNameTask { +internal class DefaultSetDeviceNameTask @Inject constructor( + private val cryptoApi: CryptoApi, + private val eventBus: EventBus +) : SetDeviceNameTask { override suspend fun execute(params: SetDeviceNameTask.Params) { val body = UpdateDeviceInfoBody( displayName = params.deviceName ) - return executeRequest { + return executeRequest(eventBus) { apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt index db05f473b1..d8bfe73eda 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.convertToUTF8 +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface UploadKeysTask : Task { @@ -36,8 +37,10 @@ internal interface UploadKeysTask : Task = RealmList(), // this is required for querying var flatAliases: String = "", - var isEncrypted: Boolean = false + var isEncrypted: Boolean = false, + var typingUserIds: RealmList = RealmList() ) : RealmObject() { private var membershipStr: String = Membership.NONE.name diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/AccessTokenInterceptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/AccessTokenInterceptor.kt index e0257bfc83..c802d4b63a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/AccessTokenInterceptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/AccessTokenInterceptor.kt @@ -17,13 +17,13 @@ package im.vector.matrix.android.internal.network import im.vector.matrix.android.internal.auth.SessionParamsStore -import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.di.SessionId import okhttp3.Interceptor import okhttp3.Response import javax.inject.Inject internal class AccessTokenInterceptor @Inject constructor( - @UserId private val userId: String, + @SessionId private val sessionId: String, private val sessionParamsStore: SessionParamsStore) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { @@ -40,5 +40,5 @@ internal class AccessTokenInterceptor @Inject constructor( } private val accessToken - get() = sessionParamsStore.get(userId)?.credentials?.accessToken + get() = sessionParamsStore.get(sessionId)?.credentials?.accessToken } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt index 7f8e6643c3..074a97662b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt @@ -18,12 +18,14 @@ package im.vector.matrix.android.internal.network import im.vector.matrix.android.api.failure.Failure import kotlinx.coroutines.CancellationException +import org.greenrobot.eventbus.EventBus import retrofit2.Call import java.io.IOException -internal suspend inline fun executeRequest(block: Request.() -> Unit) = Request().apply(block).execute() +internal suspend inline fun executeRequest(eventBus: EventBus?, + block: Request.() -> Unit) = Request(eventBus).apply(block).execute() -internal class Request { +internal class Request(private val eventBus: EventBus?) { lateinit var apiCall: Call @@ -34,7 +36,7 @@ internal class Request { response.body() ?: throw IllegalStateException("The request returned a null body") } else { - throw response.toFailure() + throw response.toFailure(eventBus) } } catch (exception: Throwable) { throw when (exception) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt index 2018868059..f33ec2f88a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt @@ -74,18 +74,18 @@ internal suspend fun okhttp3.Call.awaitResponse(): okhttp3.Response { /** * Convert a retrofit Response to a Failure, and eventually parse errorBody to convert it to a MatrixError */ -internal fun Response.toFailure(): Failure { - return toFailure(errorBody(), code()) +internal fun Response.toFailure(eventBus: EventBus?): Failure { + return toFailure(errorBody(), code(), eventBus) } /** * Convert a okhttp3 Response to a Failure, and eventually parse errorBody to convert it to a MatrixError */ -internal fun okhttp3.Response.toFailure(): Failure { - return toFailure(body, code) +internal fun okhttp3.Response.toFailure(eventBus: EventBus?): Failure { + return toFailure(body, code, eventBus) } -private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure { +private fun toFailure(errorBody: ResponseBody?, httpCode: Int, eventBus: EventBus?): Failure { if (errorBody == null) { return Failure.Unknown(RuntimeException("errorBody should not be null")) } @@ -100,11 +100,11 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure { if (matrixError != null) { if (matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank()) { // Also send this error to the bus, for a global management - EventBus.getDefault().post(GlobalError.ConsentNotGivenError(matrixError.consentUri)) + eventBus?.post(GlobalError.ConsentNotGivenError(matrixError.consentUri)) } else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED /* 401 */ && matrixError.code == MatrixError.M_UNKNOWN_TOKEN) { // Also send this error to the bus, for a global management - EventBus.getDefault().post(GlobalError.InvalidToken(matrixError.isSoftLogout)) + eventBus?.post(GlobalError.InvalidToken(matrixError.isSoftLogout)) } return Failure.ServerError(matrixError, httpCode) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 483c7d7acf..e22e47bc1c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.session import android.content.Context -import android.os.Looper import androidx.annotation.MainThread import androidx.lifecycle.LiveData import dagger.Lazy @@ -46,6 +45,7 @@ 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 import im.vector.matrix.android.internal.database.LiveEntityObserver +import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.session.sync.SyncTaskSequencer import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.job.SyncThread @@ -61,69 +61,72 @@ import javax.inject.Inject import javax.inject.Provider @SessionScope -internal class DefaultSession @Inject constructor(override val sessionParams: SessionParams, - private val context: Context, - private val liveEntityObservers: Set<@JvmSuppressWildcards LiveEntityObserver>, - private val sessionListeners: SessionListeners, - private val roomService: Lazy, - private val roomDirectoryService: Lazy, - private val groupService: Lazy, - private val userService: Lazy, - private val filterService: Lazy, - private val cacheService: Lazy, - private val signOutService: Lazy, - private val pushRuleService: Lazy, - private val pushersService: Lazy, - private val cryptoService: Lazy, - private val fileService: Lazy, - private val secureStorageService: Lazy, - private val profileService: Lazy, - private val syncThreadProvider: Provider, - private val contentUrlResolver: ContentUrlResolver, - private val syncTokenStore: SyncTokenStore, - private val syncTaskSequencer: SyncTaskSequencer, - private val sessionParamsStore: SessionParamsStore, - private val contentUploadProgressTracker: ContentUploadStateTracker, - private val initialSyncProgressService: Lazy, - private val homeServerCapabilitiesService: Lazy) +internal class DefaultSession @Inject constructor( + override val sessionParams: SessionParams, + private val context: Context, + private val eventBus: EventBus, + @SessionId + override val sessionId: String, + private val liveEntityObservers: Set<@JvmSuppressWildcards LiveEntityObserver>, + private val sessionListeners: SessionListeners, + private val roomService: Lazy, + private val roomDirectoryService: Lazy, + private val groupService: Lazy, + private val userService: Lazy, + private val filterService: Lazy, + private val cacheService: Lazy, + private val signOutService: Lazy, + private val pushRuleService: Lazy, + private val pushersService: Lazy, + private val cryptoService: Lazy, + private val fileService: Lazy, + private val secureStorageService: Lazy, + private val profileService: Lazy, + private val syncThreadProvider: Provider, + private val contentUrlResolver: ContentUrlResolver, + private val syncTokenStore: SyncTokenStore, + private val syncTaskSequencer: SyncTaskSequencer, + private val sessionParamsStore: SessionParamsStore, + private val contentUploadProgressTracker: ContentUploadStateTracker, + private val initialSyncProgressService: Lazy, + private val homeServerCapabilitiesService: Lazy) : Session, - RoomService by roomService.get(), - RoomDirectoryService by roomDirectoryService.get(), - GroupService by groupService.get(), - UserService by userService.get(), - CryptoService by cryptoService.get(), - SignOutService by signOutService.get(), - FilterService by filterService.get(), - PushRuleService by pushRuleService.get(), - PushersService by pushersService.get(), - FileService by fileService.get(), - InitialSyncProgressService by initialSyncProgressService.get(), - SecureStorageService by secureStorageService.get(), - HomeServerCapabilitiesService by homeServerCapabilitiesService.get(), - ProfileService by profileService.get() { + RoomService by roomService.get(), + RoomDirectoryService by roomDirectoryService.get(), + GroupService by groupService.get(), + UserService by userService.get(), + CryptoService by cryptoService.get(), + SignOutService by signOutService.get(), + FilterService by filterService.get(), + PushRuleService by pushRuleService.get(), + PushersService by pushersService.get(), + FileService by fileService.get(), + InitialSyncProgressService by initialSyncProgressService.get(), + SecureStorageService by secureStorageService.get(), + HomeServerCapabilitiesService by homeServerCapabilitiesService.get(), + ProfileService by profileService.get() { private var isOpen = false private var syncThread: SyncThread? = null override val isOpenable: Boolean - get() = sessionParamsStore.get(myUserId)?.isTokenValid ?: false + get() = sessionParamsStore.get(sessionId)?.isTokenValid ?: false @MainThread override fun open() { - assertMainThread() assert(!isOpen) isOpen = true liveEntityObservers.forEach { it.start() } - EventBus.getDefault().register(this) + eventBus.register(this) } override fun requireBackgroundSync() { - SyncWorker.requireBackgroundSync(context, myUserId) + SyncWorker.requireBackgroundSync(context, sessionId) } override fun startAutomaticBackgroundSync(repeatDelay: Long) { - SyncWorker.automaticallyBackgroundSync(context, myUserId, 0, repeatDelay) + SyncWorker.automaticallyBackgroundSync(context, sessionId, 0, repeatDelay) } override fun stopAnyBackgroundSync() { @@ -155,7 +158,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se liveEntityObservers.forEach { it.dispose() } cryptoService.get().close() isOpen = false - EventBus.getDefault().unregister(this) + eventBus.unregister(this) syncTaskSequencer.close() } @@ -183,10 +186,10 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se @Subscribe(threadMode = ThreadMode.MAIN) fun onGlobalError(globalError: GlobalError) { if (globalError is GlobalError.InvalidToken - && globalError.softLogout) { + && globalError.softLogout) { // Mark the token has invalid GlobalScope.launch(Dispatchers.IO) { - sessionParamsStore.setTokenInvalid(myUserId) + sessionParamsStore.setTokenInvalid(sessionId) } } @@ -204,12 +207,4 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se override fun removeListener(listener: Session.Listener) { sessionListeners.removeListener(listener) } - - // Private methods ***************************************************************************** - - private fun assertMainThread() { - if (Looper.getMainLooper().thread !== Thread.currentThread()) { - throw IllegalStateException("This method can only be called on the main thread!") - } - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 437a559ea1..2c72d93ebb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -26,11 +26,11 @@ import dagger.multibindings.IntoSet 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.SessionParams +import im.vector.matrix.android.api.auth.data.sessionId import im.vector.matrix.android.api.session.InitialSyncProgressService import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService import im.vector.matrix.android.api.session.securestorage.SecureStorageService -import im.vector.matrix.android.internal.auth.createSessionId import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory import im.vector.matrix.android.internal.di.* @@ -47,6 +47,7 @@ import im.vector.matrix.android.internal.session.securestorage.DefaultSecureStor import im.vector.matrix.android.internal.util.md5 import io.realm.RealmConfiguration import okhttp3.OkHttpClient +import org.greenrobot.eventbus.EventBus import retrofit2.Retrofit import java.io.File @@ -87,7 +88,7 @@ internal abstract class SessionModule { @SessionId @Provides fun providesSessionId(credentials: Credentials): String { - return createSessionId(credentials.userId, credentials.deviceId) + return credentials.sessionId() } @JvmStatic @@ -154,6 +155,13 @@ internal abstract class SessionModule { return retrofitFactory .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString()) } + + @JvmStatic + @Provides + @SessionScope + fun providesEventBus(): EventBus { + return EventBus.builder().build() + } } @Binds diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt index 2f4e991e62..4071c9224f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt @@ -29,12 +29,14 @@ import okhttp3.Request import okhttp3.RequestBody import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody +import org.greenrobot.eventbus.EventBus import java.io.File import java.io.IOException import javax.inject.Inject internal class FileUploader @Inject constructor(@Authenticated private val okHttpClient: OkHttpClient, + private val eventBus: EventBus, sessionParams: SessionParams, moshi: Moshi) { @@ -73,7 +75,7 @@ internal class FileUploader @Inject constructor(@Authenticated return okHttpClient.newCall(request).awaitResponse().use { response -> if (!response.isSuccessful) { - throw response.toFailure() + throw response.toFailure(eventBus) } else { response.body?.source()?.let { responseAdapter.fromJson(it) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt index a05edb7b0f..1725ef99aa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt @@ -42,7 +42,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : @JsonClass(generateAdapter = true) internal data class Params( - override val userId: String, + override val sessionId: String, val roomId: String, val event: Event, val attachment: ContentAttachmentData, @@ -64,7 +64,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : return Result.success(inputData) } - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val eventId = params.event.eventId ?: return Result.success() @@ -169,7 +169,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : Timber.v("handleSuccess $attachmentUrl, work is stopped $isStopped") contentUploadStateTracker.setSuccess(params.event.eventId!!) val event = updateEvent(params.event, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo) - val sendParams = SendEventWorker.Params(params.userId, params.roomId, event) + val sendParams = SendEventWorker.Params(params.sessionId, params.roomId, event) return Result.success(WorkerParamsFactory.toData(sendParams)) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt index 08985bf17d..47c5e4a08a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject /** @@ -32,9 +33,11 @@ internal interface SaveFilterTask : Task { ) } -internal class DefaultSaveFilterTask @Inject constructor(@UserId private val userId: String, - private val filterAPI: FilterApi, - private val filterRepository: FilterRepository +internal class DefaultSaveFilterTask @Inject constructor( + @UserId private val userId: String, + private val filterAPI: FilterApi, + private val filterRepository: FilterRepository, + private val eventBus: EventBus ) : SaveFilterTask { override suspend fun execute(params: SaveFilterTask.Params) { @@ -56,7 +59,7 @@ internal class DefaultSaveFilterTask @Inject constructor(@UserId private val use } val updated = filterRepository.storeFilter(filterBody, roomFilter) if (updated) { - val filterResponse = executeRequest { + val filterResponse = executeRequest(eventBus) { // TODO auto retry apiCall = filterAPI.uploadFilter(userId, filterBody) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt index 40dc0a44c0..069c9b8d21 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.session.group.model.GroupRooms import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse import im.vector.matrix.android.internal.session.group.model.GroupUsers import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetGroupDataTask : Task { @@ -34,18 +35,19 @@ internal interface GetGroupDataTask : Task { internal class DefaultGetGroupDataTask @Inject constructor( private val groupAPI: GroupAPI, - private val monarchy: Monarchy + private val monarchy: Monarchy, + private val eventBus: EventBus ) : GetGroupDataTask { override suspend fun execute(params: GetGroupDataTask.Params) { val groupId = params.groupId - val groupSummary = executeRequest { + val groupSummary = executeRequest(eventBus) { apiCall = groupAPI.getSummary(groupId) } - val groupRooms = executeRequest { + val groupRooms = executeRequest(eventBus) { apiCall = groupAPI.getRooms(groupId) } - val groupUsers = executeRequest { + val groupUsers = executeRequest(eventBus) { apiCall = groupAPI.getUsers(groupId) } insertInDb(groupSummary, groupRooms, groupUsers, groupId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt index 76a7d5a48d..93705774e6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt @@ -29,7 +29,7 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) : @JsonClass(generateAdapter = true) internal data class Params( - override val userId: String, + override val sessionId: String, val groupIds: List, override val lastFailureMessage: String? = null ) : SessionWorkerParams @@ -40,7 +40,7 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) : val params = WorkerParamsFactory.fromData(inputData) ?: return Result.failure() - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val results = params.groupIds.map { groupId -> runCatching { fetchGroupData(groupId) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt index 553a0387c5..a60bc78b6c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt @@ -26,7 +26,7 @@ import im.vector.matrix.android.internal.database.awaitTransaction import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupSummaryEntity import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkerParamsFactory @@ -37,9 +37,10 @@ import javax.inject.Inject private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" -internal class GroupSummaryUpdater @Inject constructor(private val context: Context, - @UserId private val userId: String, - private val monarchy: Monarchy) +internal class GroupSummaryUpdater @Inject constructor( + private val context: Context, + @SessionId private val sessionId: String, + private val monarchy: Monarchy) : RealmLiveEntityObserver(monarchy.realmConfiguration) { override val query = Monarchy.Query { GroupEntity.where(it) } @@ -51,9 +52,9 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont .mapNotNull { results[it] } fetchGroupsData(modifiedGroupEntity - .filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE } - .map { it.groupId } - .toList()) + .filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE } + .map { it.groupId } + .toList()) modifiedGroupEntity .filter { it.membership == Membership.LEAVE } @@ -67,7 +68,7 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont } private fun fetchGroupsData(groupIds: List) { - val getGroupDataWorkerParams = GetGroupDataWorker.Params(userId, groupIds) + val getGroupDataWorkerParams = GetGroupDataWorker.Params(sessionId, groupIds) val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt index 45571286b9..3837d893f9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt @@ -23,14 +23,16 @@ import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction -import java.util.Date +import org.greenrobot.eventbus.EventBus +import java.util.* import javax.inject.Inject internal interface GetHomeServerCapabilitiesTask : Task internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( private val capabilitiesAPI: CapabilitiesAPI, - private val monarchy: Monarchy + private val monarchy: Monarchy, + private val eventBus: EventBus ) : GetHomeServerCapabilitiesTask { override suspend fun execute(params: Unit) { @@ -45,7 +47,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( return } - val uploadCapabilities = executeRequest { + val uploadCapabilities = executeRequest(eventBus) { apiCall = capabilitiesAPI.getUploadCapabilities() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/GetProfileInfoTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/GetProfileInfoTask.kt index 69db2d7b99..2309628a8a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/GetProfileInfoTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/GetProfileInfoTask.kt @@ -20,6 +20,7 @@ package im.vector.matrix.android.internal.session.profile import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal abstract class GetProfileInfoTask : Task { @@ -28,10 +29,11 @@ internal abstract class GetProfileInfoTask : Task(inputData) ?: return Result.failure() - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val pusher = params.pusher @@ -76,7 +80,7 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters) } private suspend fun setPusher(pusher: JsonPusher) { - executeRequest { + executeRequest(eventBus) { apiCall = pushersAPI.setPusher(pusher) } monarchy.awaitTransaction { realm -> diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddPushRuleTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddPushRuleTask.kt index 99992ef4dc..b310ba7cd1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddPushRuleTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddPushRuleTask.kt @@ -19,6 +19,7 @@ import im.vector.matrix.android.api.pushrules.RuleKind import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface AddPushRuleTask : Task { @@ -28,11 +29,13 @@ internal interface AddPushRuleTask : Task { ) } -internal class DefaultAddPushRuleTask @Inject constructor(private val pushRulesApi: PushRulesApi) - : AddPushRuleTask { +internal class DefaultAddPushRuleTask @Inject constructor( + private val pushRulesApi: PushRulesApi, + private val eventBus: EventBus +) : AddPushRuleTask { override suspend fun execute(params: AddPushRuleTask.Params) { - return executeRequest { + return executeRequest(eventBus) { apiCall = pushRulesApi.addRule(params.kind.value, params.pushRule.ruleId, params.pushRule) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt index fcce69c2fc..cdbf6aeee4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt @@ -26,22 +26,23 @@ import im.vector.matrix.android.api.session.pushers.PushersService import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkerParamsFactory -import java.util.UUID +import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject -internal class DefaultPusherService @Inject constructor(private val context: Context, - private val monarchy: Monarchy, - @UserId private val userId: String, - private val getPusherTask: GetPushersTask, - private val removePusherTask: RemovePusherTask, - private val taskExecutor: TaskExecutor +internal class DefaultPusherService @Inject constructor( + private val context: Context, + private val monarchy: Monarchy, + @SessionId private val sessionId: String, + private val getPusherTask: GetPushersTask, + private val removePusherTask: RemovePusherTask, + private val taskExecutor: TaskExecutor ) : PushersService { override fun refreshPushers() { @@ -65,7 +66,7 @@ internal class DefaultPusherService @Inject constructor(private val context: Con data = JsonPusherData(url, if (withEventIdOnly) PushersService.EVENT_ID_ONLY else null), append = append) - val params = AddHttpPusherWorker.Params(pusher, userId) + val params = AddHttpPusherWorker.Params(sessionId, pusher) val request = matrixOneTimeWorkRequestBuilder() .setConstraints(WorkManagerUtil.workConstraints) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt index d135c36543..4f3b39d1a7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.pushers import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetPushRulesTask : Task { @@ -27,11 +28,14 @@ internal interface GetPushRulesTask : Task { /** * We keep this task, but it should not be used anymore, the push rules comes from the sync response */ -internal class DefaultGetPushRulesTask @Inject constructor(private val pushRulesApi: PushRulesApi, - private val savePushRulesTask: SavePushRulesTask) : GetPushRulesTask { +internal class DefaultGetPushRulesTask @Inject constructor( + private val pushRulesApi: PushRulesApi, + private val savePushRulesTask: SavePushRulesTask, + private val eventBus: EventBus +) : GetPushRulesTask { override suspend fun execute(params: GetPushRulesTask.Params) { - val response = executeRequest { + val response = executeRequest(eventBus) { apiCall = pushRulesApi.getAllRules() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt index 045db56786..64e982914a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt @@ -22,15 +22,19 @@ import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetPushersTask : Task -internal class DefaultGetPushersTask @Inject constructor(private val pushersAPI: PushersAPI, - private val monarchy: Monarchy) : GetPushersTask { +internal class DefaultGetPushersTask @Inject constructor( + private val pushersAPI: PushersAPI, + private val monarchy: Monarchy, + private val eventBus: EventBus +) : GetPushersTask { override suspend fun execute(params: Unit) { - val response = executeRequest { + val response = executeRequest(eventBus) { apiCall = pushersAPI.getPushers() } monarchy.awaitTransaction { realm -> diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePushRuleTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePushRuleTask.kt index c4938fa0cc..d0edda9cfb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePushRuleTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePushRuleTask.kt @@ -19,6 +19,7 @@ import im.vector.matrix.android.api.pushrules.RuleKind import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface RemovePushRuleTask : Task { @@ -28,11 +29,13 @@ internal interface RemovePushRuleTask : Task { ) } -internal class DefaultRemovePushRuleTask @Inject constructor(private val pushRulesApi: PushRulesApi) - : RemovePushRuleTask { +internal class DefaultRemovePushRuleTask @Inject constructor( + private val pushRulesApi: PushRulesApi, + private val eventBus: EventBus +) : RemovePushRuleTask { override suspend fun execute(params: RemovePushRuleTask.Params) { - return executeRequest { + return executeRequest(eventBus) { apiCall = pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt index 297375454a..48e3f40a21 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface RemovePusherTask : Task { @@ -34,7 +35,8 @@ internal interface RemovePusherTask : Task { internal class DefaultRemovePusherTask @Inject constructor( private val pushersAPI: PushersAPI, - private val monarchy: Monarchy + private val monarchy: Monarchy, + private val eventBus: EventBus ) : RemovePusherTask { override suspend fun execute(params: RemovePusherTask.Params) { @@ -59,7 +61,7 @@ internal class DefaultRemovePusherTask @Inject constructor( data = JsonPusherData(existing.data.url, existing.data.format), append = false ) - executeRequest { + executeRequest(eventBus) { apiCall = pushersAPI.setPusher(deleteBody) } monarchy.awaitTransaction { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt index 91ed65d833..c5d593c263 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt @@ -19,6 +19,7 @@ import im.vector.matrix.android.api.pushrules.RuleKind import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface UpdatePushRuleEnableStatusTask : Task { @@ -27,11 +28,13 @@ internal interface UpdatePushRuleEnableStatusTask : Task) { + if (isEncrypted()) { + callback.onFailure(IllegalStateException("Encryption is already enabled for this room")) + } else { + stateService.enableEncryption(algorithm, callback) + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt index 4a14005fe9..c6bcdf396e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt @@ -36,9 +36,10 @@ import javax.inject.Inject * The summaries can then be extracted and added (as a decoration) to a TimelineEvent for final display. */ -internal class EventRelationsAggregationUpdater @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, - @UserId private val userId: String, - private val task: EventRelationsAggregationTask) : +internal class EventRelationsAggregationUpdater @Inject constructor( + @SessionDatabase realmConfiguration: RealmConfiguration, + @UserId private val userId: String, + private val task: EventRelationsAggregationTask) : RealmLiveEntityObserver(realmConfiguration) { override val query = Monarchy.Query { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index 6896788de9..622ffbe2f0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -18,13 +18,13 @@ package im.vector.matrix.android.internal.session.room import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol import im.vector.matrix.android.internal.network.NetworkConstants +import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody import im.vector.matrix.android.internal.session.room.relation.RelationsResponse @@ -32,6 +32,7 @@ import im.vector.matrix.android.internal.session.room.reporting.ReportContentBod import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse +import im.vector.matrix.android.internal.session.room.typing.TypingBody import retrofit2.Call import retrofit2.http.* @@ -268,4 +269,12 @@ internal interface RoomAPI { */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call + + /** + * Inform that the user is starting to type or has stopped typing + */ + @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/typing/{userId}") + fun sendTypingState(@Path("roomId") roomId: String, + @Path("userId") userId: String, + @Body body: TypingBody): Call } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index a21a3b4a8d..b24bb73d56 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.session.room.reporting.DefaultReporting import im.vector.matrix.android.internal.session.room.send.DefaultSendService import im.vector.matrix.android.internal.session.room.state.DefaultStateService import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService +import im.vector.matrix.android.internal.session.room.typing.DefaultTypingService import javax.inject.Inject internal interface RoomFactory { @@ -46,6 +47,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona private val stateServiceFactory: DefaultStateService.Factory, private val reportingServiceFactory: DefaultReportingService.Factory, private val readServiceFactory: DefaultReadService.Factory, + private val typingServiceFactory: DefaultTypingService.Factory, private val relationServiceFactory: DefaultRelationService.Factory, private val membershipServiceFactory: DefaultMembershipService.Factory, private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory) : @@ -62,6 +64,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona stateServiceFactory.create(roomId), reportingServiceFactory.create(roomId), readServiceFactory.create(roomId), + typingServiceFactory.create(roomId), cryptoService, relationServiceFactory.create(roomId), membershipServiceFactory.create(roomId), diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index cc786a7493..5551930bd1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -52,6 +52,8 @@ import im.vector.matrix.android.internal.session.room.reporting.ReportContentTas import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask import im.vector.matrix.android.internal.session.room.state.SendStateTask import im.vector.matrix.android.internal.session.room.timeline.* +import im.vector.matrix.android.internal.session.room.typing.DefaultSendTypingTask +import im.vector.matrix.android.internal.session.room.typing.SendTypingTask import retrofit2.Retrofit @Module @@ -68,74 +70,77 @@ internal abstract class RoomModule { } @Binds - abstract fun bindRoomFactory(roomFactory: DefaultRoomFactory): RoomFactory + abstract fun bindRoomFactory(factory: DefaultRoomFactory): RoomFactory @Binds - abstract fun bindRoomService(roomService: DefaultRoomService): RoomService + abstract fun bindRoomService(service: DefaultRoomService): RoomService @Binds - abstract fun bindRoomDirectoryService(roomDirectoryService: DefaultRoomDirectoryService): RoomDirectoryService + abstract fun bindRoomDirectoryService(service: DefaultRoomDirectoryService): RoomDirectoryService @Binds - abstract fun bindEventRelationsAggregationTask(eventRelationsAggregationTask: DefaultEventRelationsAggregationTask): EventRelationsAggregationTask + abstract fun bindFileService(service: DefaultFileService): FileService @Binds - abstract fun bindCreateRoomTask(createRoomTask: DefaultCreateRoomTask): CreateRoomTask + abstract fun bindEventRelationsAggregationTask(task: DefaultEventRelationsAggregationTask): EventRelationsAggregationTask @Binds - abstract fun bindGetPublicRoomTask(getPublicRoomTask: DefaultGetPublicRoomTask): GetPublicRoomTask + abstract fun bindCreateRoomTask(task: DefaultCreateRoomTask): CreateRoomTask @Binds - abstract fun bindGetThirdPartyProtocolsTask(getThirdPartyProtocolsTask: DefaultGetThirdPartyProtocolsTask): GetThirdPartyProtocolsTask + abstract fun bindGetPublicRoomTask(task: DefaultGetPublicRoomTask): GetPublicRoomTask @Binds - abstract fun bindInviteTask(inviteTask: DefaultInviteTask): InviteTask + abstract fun bindGetThirdPartyProtocolsTask(task: DefaultGetThirdPartyProtocolsTask): GetThirdPartyProtocolsTask @Binds - abstract fun bindJoinRoomTask(joinRoomTask: DefaultJoinRoomTask): JoinRoomTask + abstract fun bindInviteTask(task: DefaultInviteTask): InviteTask @Binds - abstract fun bindLeaveRoomTask(leaveRoomTask: DefaultLeaveRoomTask): LeaveRoomTask + abstract fun bindJoinRoomTask(task: DefaultJoinRoomTask): JoinRoomTask @Binds - abstract fun bindLoadRoomMembersTask(loadRoomMembersTask: DefaultLoadRoomMembersTask): LoadRoomMembersTask + abstract fun bindLeaveRoomTask(task: DefaultLeaveRoomTask): LeaveRoomTask @Binds - abstract fun bindPruneEventTask(pruneEventTask: DefaultPruneEventTask): PruneEventTask + abstract fun bindLoadRoomMembersTask(task: DefaultLoadRoomMembersTask): LoadRoomMembersTask @Binds - abstract fun bindSetReadMarkersTask(setReadMarkersTask: DefaultSetReadMarkersTask): SetReadMarkersTask + abstract fun bindPruneEventTask(task: DefaultPruneEventTask): PruneEventTask @Binds - abstract fun bindMarkAllRoomsReadTask(markAllRoomsReadTask: DefaultMarkAllRoomsReadTask): MarkAllRoomsReadTask + abstract fun bindSetReadMarkersTask(task: DefaultSetReadMarkersTask): SetReadMarkersTask @Binds - abstract fun bindFindReactionEventForUndoTask(findReactionEventForUndoTask: DefaultFindReactionEventForUndoTask): FindReactionEventForUndoTask + abstract fun bindMarkAllRoomsReadTask(task: DefaultMarkAllRoomsReadTask): MarkAllRoomsReadTask @Binds - abstract fun bindUpdateQuickReactionTask(updateQuickReactionTask: DefaultUpdateQuickReactionTask): UpdateQuickReactionTask + abstract fun bindFindReactionEventForUndoTask(task: DefaultFindReactionEventForUndoTask): FindReactionEventForUndoTask @Binds - abstract fun bindSendStateTask(sendStateTask: DefaultSendStateTask): SendStateTask + abstract fun bindUpdateQuickReactionTask(task: DefaultUpdateQuickReactionTask): UpdateQuickReactionTask @Binds - abstract fun bindReportContentTask(reportContentTask: DefaultReportContentTask): ReportContentTask + abstract fun bindSendStateTask(task: DefaultSendStateTask): SendStateTask @Binds - abstract fun bindGetContextOfEventTask(getContextOfEventTask: DefaultGetContextOfEventTask): GetContextOfEventTask + abstract fun bindReportContentTask(task: DefaultReportContentTask): ReportContentTask @Binds - abstract fun bindClearUnlinkedEventsTask(clearUnlinkedEventsTask: DefaultClearUnlinkedEventsTask): ClearUnlinkedEventsTask + abstract fun bindGetContextOfEventTask(task: DefaultGetContextOfEventTask): GetContextOfEventTask @Binds - abstract fun bindPaginationTask(paginationTask: DefaultPaginationTask): PaginationTask + abstract fun bindClearUnlinkedEventsTask(task: DefaultClearUnlinkedEventsTask): ClearUnlinkedEventsTask @Binds - abstract fun bindFileService(fileService: DefaultFileService): FileService + abstract fun bindPaginationTask(task: DefaultPaginationTask): PaginationTask @Binds - abstract fun bindFetchEditHistoryTask(fetchEditHistoryTask: DefaultFetchEditHistoryTask): FetchEditHistoryTask + abstract fun bindFetchEditHistoryTask(task: DefaultFetchEditHistoryTask): FetchEditHistoryTask @Binds - abstract fun bindGetRoomIdByAliasTask(getRoomIdByAliasTask: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask + abstract fun bindGetRoomIdByAliasTask(task: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask + + @Binds + abstract fun bindSendTypingTask(task: DefaultSendTypingTask): SendTypingTask } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index 009ca682ed..bbb5feba15 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -29,22 +29,20 @@ import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.* -import im.vector.matrix.android.internal.database.query.isEventRead -import im.vector.matrix.android.internal.database.query.latestEvent -import im.vector.matrix.android.internal.database.query.prev -import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper +import im.vector.matrix.android.internal.session.sync.RoomSyncHandler import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications import io.realm.Realm import javax.inject.Inject -internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId: String, - private val roomDisplayNameResolver: RoomDisplayNameResolver, - private val roomAvatarResolver: RoomAvatarResolver, - private val monarchy: Monarchy) { +internal class RoomSummaryUpdater @Inject constructor( + @UserId private val userId: String, + private val roomDisplayNameResolver: RoomDisplayNameResolver, + private val roomAvatarResolver: RoomAvatarResolver, + private val monarchy: Monarchy) { // TODO: maybe allow user of SDK to give that list private val PREVIEWABLE_TYPES = listOf( @@ -57,7 +55,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId EventType.CALL_HANGUP, EventType.CALL_ANSWER, EventType.ENCRYPTED, - EventType.ENCRYPTION, + EventType.STATE_ROOM_ENCRYPTION, EventType.STATE_ROOM_THIRD_PARTY_INVITE, EventType.STICKER, EventType.STATE_ROOM_CREATE @@ -68,7 +66,8 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId membership: Membership? = null, roomSummary: RoomSyncSummary? = null, unreadNotifications: RoomSyncUnreadNotifications? = null, - updateMembers: Boolean = false) { + updateMembers: Boolean = false, + ephemeralResult: RoomSyncHandler.EphemeralResult? = null) { val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) if (roomSummary != null) { if (roomSummary.heroes.isNotEmpty()) { @@ -93,11 +92,11 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev() val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev() val lastAliasesEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev() - val encryptionEvent = EventEntity.where(realm, roomId, EventType.ENCRYPTION).prev() + val encryptionEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ENCRYPTION).prev() roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 - // avoid this call if we are sure there are unread events - || !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId) + // avoid this call if we are sure there are unread events + || !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId) roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString() roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId) @@ -107,11 +106,13 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId ?.canonicalAlias val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel()?.aliases - ?: emptyList() + ?: emptyList() roomSummaryEntity.aliases.clear() roomSummaryEntity.aliases.addAll(roomAliases) roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|") roomSummaryEntity.isEncrypted = encryptionEvent != null + roomSummaryEntity.typingUserIds.clear() + roomSummaryEntity.typingUserIds.addAll(ephemeralResult?.typingUserIds.orEmpty()) if (updateMembers) { val otherRoomMembers = RoomMemberHelper(realm, roomId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/alias/GetRoomIdByAliasTask.kt index 1a726c3fe5..6aee8c170b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/alias/GetRoomIdByAliasTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/alias/GetRoomIdByAliasTask.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task import io.realm.Realm +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetRoomIdByAliasTask : Task> { @@ -33,8 +34,11 @@ internal interface GetRoomIdByAliasTask : Task { var roomId = Realm.getInstance(monarchy.realmConfiguration).use { @@ -45,7 +49,7 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(private val monar } else if (!params.searchOnServer) { Optional.from(null) } else { - roomId = executeRequest { + roomId = executeRequest(eventBus) { apiCall = roomAPI.getRoomIdByAlias(params.roomAlias) }.roomId Optional.from(roomId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt index 970a1fed7e..6567b7ad97 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt @@ -35,21 +35,25 @@ import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.RealmConfiguration import kotlinx.coroutines.TimeoutCancellationException +import org.greenrobot.eventbus.EventBus import java.util.concurrent.TimeUnit import javax.inject.Inject internal interface CreateRoomTask : Task -internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: RoomAPI, - private val monarchy: Monarchy, - private val directChatsHelper: DirectChatsHelper, - private val updateUserAccountDataTask: UpdateUserAccountDataTask, - private val readMarkersTask: SetReadMarkersTask, - @SessionDatabase - private val realmConfiguration: RealmConfiguration) : CreateRoomTask { +internal class DefaultCreateRoomTask @Inject constructor( + private val roomAPI: RoomAPI, + private val monarchy: Monarchy, + private val directChatsHelper: DirectChatsHelper, + private val updateUserAccountDataTask: UpdateUserAccountDataTask, + private val readMarkersTask: SetReadMarkersTask, + @SessionDatabase + private val realmConfiguration: RealmConfiguration, + private val eventBus: EventBus +) : CreateRoomTask { override suspend fun execute(params: CreateRoomParams): String { - val createRoomResponse = executeRequest { + val createRoomResponse = executeRequest(eventBus) { apiCall = roomAPI.createRoom(params) } val roomId = createRoomResponse.roomId!! diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/directory/GetPublicRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/directory/GetPublicRoomTask.kt index a24765e0bb..2b7d2e7dd9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/directory/GetPublicRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/directory/GetPublicRoomTask.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRooms import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetPublicRoomTask : Task { @@ -30,10 +31,13 @@ internal interface GetPublicRoomTask : Task> -internal class DefaultGetThirdPartyProtocolsTask @Inject constructor(private val roomAPI: RoomAPI) : GetThirdPartyProtocolsTask { +internal class DefaultGetThirdPartyProtocolsTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus +) : GetThirdPartyProtocolsTask { override suspend fun execute(params: Unit): Map { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomAPI.thirdPartyProtocols() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt index 01646e4c17..0195497798 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt @@ -29,6 +29,7 @@ import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields +import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.query.process import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask @@ -39,13 +40,16 @@ import im.vector.matrix.android.internal.util.fetchCopied import io.realm.Realm import io.realm.RealmQuery -internal class DefaultMembershipService @AssistedInject constructor(@Assisted private val roomId: String, - private val monarchy: Monarchy, - private val taskExecutor: TaskExecutor, - private val loadRoomMembersTask: LoadRoomMembersTask, - private val inviteTask: InviteTask, - private val joinTask: JoinRoomTask, - private val leaveRoomTask: LeaveRoomTask +internal class DefaultMembershipService @AssistedInject constructor( + @Assisted private val roomId: String, + private val monarchy: Monarchy, + private val taskExecutor: TaskExecutor, + private val loadRoomMembersTask: LoadRoomMembersTask, + private val inviteTask: InviteTask, + private val joinTask: JoinRoomTask, + private val leaveRoomTask: LeaveRoomTask, + @UserId + private val userId: String ) : MembershipService { @AssistedInject.Factory @@ -91,11 +95,17 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr ) } + private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery { return RoomMemberHelper(realm, roomId).queryRoomMembersEvent() .process(RoomMemberSummaryEntityFields.USER_ID, queryParams.userId) .process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) .process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) + .apply { + if (queryParams.excludeSelf) { + notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId) + } + } } override fun getNumberOfJoinedMembers(): Int { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt index dd91875f98..3610511dbf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm import io.realm.kotlin.createObject +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface LoadRoomMembersTask : Task { @@ -40,12 +41,14 @@ internal interface LoadRoomMembersTask : Task ) } -internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAPI: RoomAPI, - private val monarchy: Monarchy, - private val syncTokenStore: SyncTokenStore, - private val roomSummaryUpdater: RoomSummaryUpdater, - private val roomMemberEventHandler: RoomMemberEventHandler, - private val timelineEventSenderVisitor: TimelineEventSenderVisitor +internal class DefaultLoadRoomMembersTask @Inject constructor( + private val roomAPI: RoomAPI, + private val monarchy: Monarchy, + private val syncTokenStore: SyncTokenStore, + private val roomSummaryUpdater: RoomSummaryUpdater, + private val roomMemberEventHandler: RoomMemberEventHandler, + private val timelineEventSenderVisitor: TimelineEventSenderVisitor, + private val eventBus: EventBus ) : LoadRoomMembersTask { override suspend fun execute(params: LoadRoomMembersTask.Params) { @@ -53,7 +56,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP return } val lastToken = syncTokenStore.getLastToken() - val response = executeRequest { + val response = executeRequest(eventBus) { apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value) } insertInDb(response, params.roomId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt index 6bc453a0f3..93b3889455 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.membership.joining import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface InviteTask : Task { @@ -29,10 +30,13 @@ internal interface InviteTask : Task { ) } -internal class DefaultInviteTask @Inject constructor(private val roomAPI: RoomAPI) : InviteTask { +internal class DefaultInviteTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus +) : InviteTask { override suspend fun execute(params: InviteTask.Params) { - return executeRequest { + return executeRequest(eventBus) { val body = InviteBody(params.userId, params.reason) apiCall = roomAPI.invite(params.roomId, body) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt index fbede72520..d4341951eb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt @@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask import im.vector.matrix.android.internal.task.Task import io.realm.RealmConfiguration import kotlinx.coroutines.TimeoutCancellationException +import org.greenrobot.eventbus.EventBus import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -38,13 +39,16 @@ internal interface JoinRoomTask : Task { ) } -internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: RoomAPI, - private val readMarkersTask: SetReadMarkersTask, - @SessionDatabase - private val realmConfiguration: RealmConfiguration) : JoinRoomTask { +internal class DefaultJoinRoomTask @Inject constructor( + private val roomAPI: RoomAPI, + private val readMarkersTask: SetReadMarkersTask, + @SessionDatabase + private val realmConfiguration: RealmConfiguration, + private val eventBus: EventBus +) : JoinRoomTask { override suspend fun execute(params: JoinRoomTask.Params) { - executeRequest { + executeRequest(eventBus) { apiCall = roomAPI.join(params.roomId, params.viaServers, mapOf("reason" to params.reason)) } // Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt index 01198c47de..08eb71fc89 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.membership.leaving import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface LeaveRoomTask : Task { @@ -28,10 +29,13 @@ internal interface LeaveRoomTask : Task { ) } -internal class DefaultLeaveRoomTask @Inject constructor(private val roomAPI: RoomAPI) : LeaveRoomTask { +internal class DefaultLeaveRoomTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus +) : LeaveRoomTask { override suspend fun execute(params: LeaveRoomTask.Params) { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomAPI.leave(params.roomId, mapOf("reason" to params.reason)) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt index b8b9fe82ef..a9a0f60083 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt @@ -36,12 +36,13 @@ import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith -internal class DefaultReadService @AssistedInject constructor(@Assisted private val roomId: String, - private val monarchy: Monarchy, - private val taskExecutor: TaskExecutor, - private val setReadMarkersTask: SetReadMarkersTask, - private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, - @UserId private val userId: String +internal class DefaultReadService @AssistedInject constructor( + @Assisted private val roomId: String, + private val monarchy: Monarchy, + private val taskExecutor: TaskExecutor, + private val setReadMarkersTask: SetReadMarkersTask, + private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, + @UserId private val userId: String ) : ReadService { @AssistedInject.Factory diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt index b9dca748cb..6a0d04193d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt @@ -20,7 +20,8 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.events.model.LocalEcho import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity -import im.vector.matrix.android.internal.database.query.* +import im.vector.matrix.android.internal.database.query.isEventRead +import im.vector.matrix.android.internal.database.query.isReadMarkerMoreRecent import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.UserId @@ -31,8 +32,11 @@ import im.vector.matrix.android.internal.session.sync.RoomFullyReadHandler import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm +import org.greenrobot.eventbus.EventBus import timber.log.Timber import javax.inject.Inject +import kotlin.collections.HashMap +import kotlin.collections.set internal interface SetReadMarkersTask : Task { @@ -47,12 +51,14 @@ internal interface SetReadMarkersTask : Task { private const val READ_MARKER = "m.fully_read" private const val READ_RECEIPT = "m.read" -internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI: RoomAPI, - private val monarchy: Monarchy, - private val roomFullyReadHandler: RoomFullyReadHandler, - private val readReceiptHandler: ReadReceiptHandler, - @UserId private val userId: String) - : SetReadMarkersTask { +internal class DefaultSetReadMarkersTask @Inject constructor( + private val roomAPI: RoomAPI, + private val monarchy: Monarchy, + private val roomFullyReadHandler: RoomFullyReadHandler, + private val readReceiptHandler: ReadReceiptHandler, + @UserId private val userId: String, + private val eventBus: EventBus +) : SetReadMarkersTask { override suspend fun execute(params: SetReadMarkersTask.Params) { val markers = HashMap() @@ -76,7 +82,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI } if (readReceiptEventId != null - && !isEventRead(monarchy, userId, params.roomId, readReceiptEventId)) { + && !isEventRead(monarchy, userId, params.roomId, readReceiptEventId)) { if (LocalEcho.isLocalEchoId(readReceiptEventId)) { Timber.w("Can't set read receipt for local event $readReceiptEventId") } else { @@ -87,7 +93,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI return } updateDatabase(params.roomId, markers) - executeRequest { + executeRequest(eventBus) { apiCall = roomAPI.sendReadMarker(params.roomId, markers) } } @@ -105,7 +111,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId == readReceiptId if (isLatestReceived) { val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() - ?: return@awaitTransaction + ?: return@awaitTransaction roomSummary.notificationCount = 0 roomSummary.highlightCount = 0 roomSummary.hasUnreadMessages = false diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt index 180776ba8d..1b2b27a3eb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt @@ -38,7 +38,7 @@ import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.session.room.send.EncryptEventWorker import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.room.send.RedactEventWorker @@ -51,16 +51,17 @@ import im.vector.matrix.android.internal.util.fetchCopyMap import im.vector.matrix.android.internal.worker.WorkerParamsFactory import timber.log.Timber -internal class DefaultRelationService @AssistedInject constructor(@Assisted private val roomId: String, - private val context: Context, - @UserId private val userId: String, - private val eventFactory: LocalEchoEventFactory, - private val cryptoService: CryptoService, - private val findReactionEventForUndoTask: FindReactionEventForUndoTask, - private val fetchEditHistoryTask: FetchEditHistoryTask, - private val timelineEventMapper: TimelineEventMapper, - private val monarchy: Monarchy, - private val taskExecutor: TaskExecutor) +internal class DefaultRelationService @AssistedInject constructor( + @Assisted private val roomId: String, + private val context: Context, + @SessionId private val sessionId: String, + private val eventFactory: LocalEchoEventFactory, + private val cryptoService: CryptoService, + private val findReactionEventForUndoTask: FindReactionEventForUndoTask, + private val fetchEditHistoryTask: FetchEditHistoryTask, + private val timelineEventMapper: TimelineEventMapper, + private val monarchy: Monarchy, + private val taskExecutor: TaskExecutor) : RelationService { @AssistedInject.Factory @@ -125,7 +126,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv // TODO duplicate with send service? private fun createRedactEventWork(localEvent: Event, eventId: String, reason: String?): OneTimeWorkRequest { val sendContentWorkerParams = RedactEventWorker.Params( - userId, + sessionId, localEvent.eventId!!, roomId, eventId, @@ -204,13 +205,13 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv private fun createEncryptEventWork(event: Event, keepKeys: List?): OneTimeWorkRequest { // Same parameter - val params = EncryptEventWorker.Params(userId, roomId, event, keepKeys) + val params = EncryptEventWorker.Params(sessionId, roomId, event, keepKeys) val sendWorkData = WorkerParamsFactory.toData(params) return TimelineSendEventWorkCommon.createWork(sendWorkData, true) } private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { - val sendContentWorkerParams = SendEventWorker.Params(userId, roomId, event) + val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) return TimelineSendEventWorkCommon.createWork(sendWorkData, startChain) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt index 5e5db58bdb..a4ab06f767 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface FetchEditHistoryTask : Task> { @@ -33,11 +34,12 @@ internal interface FetchEditHistoryTask : Task { - val response = executeRequest { + val response = executeRequest(eventBus) { apiCall = roomAPI.getRelations(params.roomId, params.eventId, RelationType.REPLACE, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FindReactionEventForUndoTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FindReactionEventForUndoTask.kt index baa01e4042..bdf4fab35e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FindReactionEventForUndoTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FindReactionEventForUndoTask.kt @@ -38,8 +38,9 @@ internal interface FindReactionEventForUndoTask : Task @@ -55,7 +56,7 @@ internal class DefaultFindReactionEventForUndoTask @Inject constructor(private v .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reaction) .findFirst() ?: return null - // want to find the event orignated by me! + // want to find the event originated by me! return rase.sourceEvents .asSequence() .mapNotNull { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt index eafe1e7419..5857eaa89b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt @@ -30,20 +30,23 @@ import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent +import org.greenrobot.eventbus.EventBus import javax.inject.Inject +// TODO This is not used. Delete? internal class SendRelationWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { @JsonClass(generateAdapter = true) internal data class Params( - override val userId: String, + override val sessionId: String, val roomId: String, val event: Event, val relationType: String? = null, - override val lastFailureMessage: String? + override val lastFailureMessage: String? = null ) : SessionWorkerParams @Inject lateinit var roomAPI: RoomAPI + @Inject lateinit var eventBus: EventBus override suspend fun doWork(): Result { val params = WorkerParamsFactory.fromData(inputData) @@ -54,7 +57,7 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) : return Result.success(inputData) } - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val localEvent = params.event @@ -82,7 +85,7 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) : } private suspend fun sendRelation(roomId: String, relationType: String, relatedEventId: String, localEvent: Event) { - executeRequest { + executeRequest(eventBus) { apiCall = roomAPI.sendRelation( roomId = roomId, parent_id = relatedEventId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentTask.kt index 60c031158a..74012348df 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.reporting import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface ReportContentTask : Task { @@ -30,9 +31,13 @@ internal interface ReportContentTask : Task { ) } -internal class DefaultReportContentTask @Inject constructor(private val roomAPI: RoomAPI) : ReportContentTask { +internal class DefaultReportContentTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus +) : ReportContentTask { + override suspend fun execute(params: ReportContentTask.Params) { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomAPI.reportContent(params.roomId, params.eventId, ReportContentBody(params.score, params.reason)) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index 8fad03b588..507c8dd247 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -37,7 +37,7 @@ import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.session.content.UploadContentWorker import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon import im.vector.matrix.android.internal.util.CancelableWork @@ -53,12 +53,13 @@ import java.util.concurrent.TimeUnit private const val UPLOAD_WORK = "UPLOAD_WORK" private const val BACKOFF_DELAY = 10_000L -internal class DefaultSendService @AssistedInject constructor(@Assisted private val roomId: String, - private val context: Context, - @UserId private val userId: String, - private val localEchoEventFactory: LocalEchoEventFactory, - private val cryptoService: CryptoService, - private val monarchy: Monarchy +internal class DefaultSendService @AssistedInject constructor( + @Assisted private val roomId: String, + private val context: Context, + @SessionId private val sessionId: String, + private val localEchoEventFactory: LocalEchoEventFactory, + private val cryptoService: CryptoService, + private val monarchy: Monarchy ) : SendService { @AssistedInject.Factory @@ -285,7 +286,7 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { // Same parameter - val params = EncryptEventWorker.Params(userId, roomId, event) + val params = EncryptEventWorker.Params(sessionId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(params) return matrixOneTimeWorkRequestBuilder() @@ -297,7 +298,7 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private } private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { - val sendContentWorkerParams = SendEventWorker.Params(userId, roomId, event) + val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) return TimelineSendEventWorkCommon.createWork(sendWorkData, startChain) @@ -307,7 +308,7 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private val redactEvent = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason).also { saveLocalEcho(it) } - val sendContentWorkerParams = RedactEventWorker.Params(userId, redactEvent.eventId!!, roomId, event.eventId, reason) + val sendContentWorkerParams = RedactEventWorker.Params(sessionId, redactEvent.eventId!!, roomId, event.eventId, reason) val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) return TimelineSendEventWorkCommon.createWork(redactWorkData, true) } @@ -316,7 +317,7 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private attachment: ContentAttachmentData, isRoomEncrypted: Boolean, startChain: Boolean): OneTimeWorkRequest { - val uploadMediaWorkerParams = UploadContentWorker.Params(userId, roomId, event, attachment, isRoomEncrypted) + val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, roomId, event, attachment, isRoomEncrypted) val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams) return matrixOneTimeWorkRequestBuilder() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt index a269529dd2..6f1593bc08 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt @@ -37,7 +37,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) @JsonClass(generateAdapter = true) internal data class Params( - override val userId: String, + override val sessionId: String, val roomId: String, val event: Event, /**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/ @@ -61,7 +61,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) return Result.success(inputData) } - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val localEvent = params.event @@ -97,7 +97,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) type = safeResult.eventType, content = safeResult.eventContent ) - val nextWorkerParams = SendEventWorker.Params(params.userId, params.roomId, encryptedEvent) + val nextWorkerParams = SendEventWorker.Params(params.sessionId, params.roomId, encryptedEvent) return Result.success(WorkerParamsFactory.toData(nextWorkerParams)) } else { val sendState = when (error) { @@ -106,7 +106,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) } localEchoUpdater.updateSendState(localEvent.eventId, sendState) // always return success, or the chain will be stuck for ever! - val nextWorkerParams = SendEventWorker.Params(params.userId, params.roomId, localEvent, error?.localizedMessage + val nextWorkerParams = SendEventWorker.Params(params.sessionId, params.roomId, localEvent, error?.localizedMessage ?: "Error") return Result.success(WorkerParamsFactory.toData(nextWorkerParams)) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt index ec311458cd..3ff318aa8a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt @@ -25,13 +25,14 @@ import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal class RedactEventWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { @JsonClass(generateAdapter = true) internal data class Params( - override val userId: String, + override val sessionId: String, val txID: String, val roomId: String, val eventId: String, @@ -40,6 +41,7 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C ) : SessionWorkerParams @Inject lateinit var roomAPI: RoomAPI + @Inject lateinit var eventBus: EventBus override suspend fun doWork(): Result { val params = WorkerParamsFactory.fromData(inputData) @@ -50,12 +52,12 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C return Result.success(inputData) } - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val eventId = params.eventId return runCatching { - executeRequest { + executeRequest(eventBus) { apiCall = roomAPI.redactEvent( params.txID, params.roomId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt index 6527113054..3215662ba2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal class SendEventWorker constructor(context: Context, params: WorkerParameters) @@ -37,7 +38,7 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam @JsonClass(generateAdapter = true) internal data class Params( - override val userId: String, + override val sessionId: String, val roomId: String, val event: Event, override val lastFailureMessage: String? = null @@ -45,12 +46,13 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam @Inject lateinit var localEchoUpdater: LocalEchoUpdater @Inject lateinit var roomAPI: RoomAPI + @Inject lateinit var eventBus: EventBus override suspend fun doWork(): Result { val params = WorkerParamsFactory.fromData(inputData) ?: return Result.success() - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val event = params.event @@ -84,7 +86,7 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam private suspend fun sendEvent(eventId: String, eventType: String, content: Content?, roomId: String) { localEchoUpdater.updateSendState(eventId, SendState.SENDING) - executeRequest { + executeRequest(eventBus) { apiCall = roomAPI.send( eventId, roomId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt index d52e5272cf..9c6f1f4ae1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt @@ -27,20 +27,16 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.state.StateService import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.toOptional +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.RoomSummaryEntity -import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.query.descending import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith -import im.vector.matrix.android.internal.util.fetchCopied -import im.vector.matrix.android.internal.util.fetchCopyMap import io.realm.Realm -import io.realm.RealmConfiguration +import java.security.InvalidParameterException internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String, private val monarchy: Monarchy, @@ -71,10 +67,10 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private override fun updateTopic(topic: String, callback: MatrixCallback) { val params = SendStateTask.Params(roomId, - EventType.STATE_ROOM_TOPIC, - mapOf( - "topic" to topic - )) + EventType.STATE_ROOM_TOPIC, + mapOf( + "topic" to topic + )) sendStateTask .configureWith(params) { @@ -82,4 +78,22 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private } .executeBy(taskExecutor) } + + override fun enableEncryption(algorithm: String, callback: MatrixCallback) { + if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM) { + callback.onFailure(InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported")) + } else { + val params = SendStateTask.Params(roomId, + EventType.STATE_ROOM_ENCRYPTION, + mapOf( + "algorithm" to algorithm + )) + + sendStateTask + .configureWith(params) { + this.callback = callback + } + .executeBy(taskExecutor) + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/SendStateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/SendStateTask.kt index 39d606f5df..b0d583c6d1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/SendStateTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/SendStateTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.state import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface SendStateTask : Task { @@ -29,9 +30,13 @@ internal interface SendStateTask : Task { ) } -internal class DefaultSendStateTask @Inject constructor(private val roomAPI: RoomAPI) : SendStateTask { +internal class DefaultSendStateTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus +) : SendStateTask { + override suspend fun execute(params: SendStateTask.Params) { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomAPI.sendStateEvent(params.roomId, params.eventType, params.body) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt index 08d34d3056..966bdcc1fd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.filter.FilterRepository import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetContextOfEventTask : Task { @@ -31,14 +32,16 @@ internal interface GetContextOfEventTask : Task { + val response = executeRequest(eventBus) { apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, params.limit, filter) } return tokenChunkEventPersistor.insertInDb(response, params.roomId, PaginationDirection.BACKWARDS) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt index d817b22996..aa7b4321dc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.filter.FilterRepository import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface PaginationTask : Task { @@ -32,14 +33,16 @@ internal interface PaginationTask : Task { + val chunk = executeRequest(eventBus) { apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) } return tokenChunkEventPersistor.insertInDb(chunk, params.roomId, params.direction) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventTask.kt index 8e097c50d9..10c0f5003b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventTask.kt @@ -20,9 +20,14 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject -internal class GetEventTask @Inject constructor(private val roomAPI: RoomAPI +// TODO Add parent task + +internal class GetEventTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus ) : Task { internal data class Params( @@ -31,7 +36,7 @@ internal class GetEventTask @Inject constructor(private val roomAPI: RoomAPI ) override suspend fun execute(params: Params): Event { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomAPI.getEvent(params.roomId, params.eventId) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/typing/DefaultTypingService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/typing/DefaultTypingService.kt new file mode 100644 index 0000000000..4989d8a235 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/typing/DefaultTypingService.kt @@ -0,0 +1,118 @@ +/* + * 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.session.room.typing + +import android.os.SystemClock +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.room.typing.TypingService +import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith +import timber.log.Timber + +/** + * Rules: + * - user is typing: notify the homeserver (true), at least once every 10s + * - user stop typing: after 10s delay: notify the homeserver (false) + * - user empty the text composer or quit the timeline screen: notify the homeserver (false) + */ +internal class DefaultTypingService @AssistedInject constructor( + @Assisted private val roomId: String, + private val taskExecutor: TaskExecutor, + private val sendTypingTask: SendTypingTask +) : TypingService { + + @AssistedInject.Factory + interface Factory { + fun create(roomId: String): TypingService + } + + private var currentTask: Cancelable? = null + private var currentAutoStopTask: Cancelable? = null + + // What the homeserver knows + private var userIsTyping = false + // Last time the user is typing event has been sent + private var lastRequestTimestamp: Long = 0 + + override fun userIsTyping() { + scheduleAutoStop() + + val now = SystemClock.elapsedRealtime() + + if (userIsTyping && now < lastRequestTimestamp + MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS) { + Timber.d("Typing: Skip start request") + return + } + + Timber.d("Typing: Send start request") + userIsTyping = true + lastRequestTimestamp = now + + currentTask?.cancel() + + val params = SendTypingTask.Params(roomId, true) + currentTask = sendTypingTask + .configureWith(params) + .executeBy(taskExecutor) + } + + override fun userStopsTyping() { + if (!userIsTyping) { + Timber.d("Typing: Skip stop request") + return + } + + Timber.d("Typing: Send stop request") + userIsTyping = false + lastRequestTimestamp = 0 + + currentAutoStopTask?.cancel() + currentTask?.cancel() + + val params = SendTypingTask.Params(roomId, false) + currentTask = sendTypingTask + .configureWith(params) + .executeBy(taskExecutor) + } + + private fun scheduleAutoStop() { + Timber.d("Typing: Schedule auto stop") + currentAutoStopTask?.cancel() + + val params = SendTypingTask.Params( + roomId, + false, + delay = MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS) + currentAutoStopTask = sendTypingTask + .configureWith(params) { + callback = object : MatrixCallback { + override fun onSuccess(data: Unit) { + userIsTyping = false + } + } + } + .executeBy(taskExecutor) + } + + companion object { + private const val MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS = 10_000L + private const val MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS = 10_000L + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/typing/SendTypingTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/typing/SendTypingTask.kt new file mode 100644 index 0000000000..6460933621 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/typing/SendTypingTask.kt @@ -0,0 +1,55 @@ +/* + * Copyright 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.session.room.typing + +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.task.Task +import kotlinx.coroutines.delay +import org.greenrobot.eventbus.EventBus +import javax.inject.Inject + +internal interface SendTypingTask : Task { + + data class Params( + val roomId: String, + val isTyping: Boolean, + val typingTimeoutMillis: Int? = 30_000, + // Optional delay before sending the request to the homeserver + val delay: Long? = null + ) +} + +internal class DefaultSendTypingTask @Inject constructor( + private val roomAPI: RoomAPI, + @UserId private val userId: String, + private val eventBus: EventBus +) : SendTypingTask { + + override suspend fun execute(params: SendTypingTask.Params) { + delay(params.delay ?: -1) + + executeRequest(eventBus) { + apiCall = roomAPI.sendTypingState( + params.roomId, + userId, + TypingBody(params.isTyping, params.typingTimeoutMillis?.takeIf { params.isTyping }) + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/typing/TypingBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/typing/TypingBody.kt new file mode 100644 index 0000000000..26099599d7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/typing/TypingBody.kt @@ -0,0 +1,30 @@ +/* + * Copyright 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.session.room.typing + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class TypingBody( + // Required. Whether the user is typing or not. If false, the timeout key can be omitted. + @Json(name = "typing") + val typing: Boolean, + // The length of time in milliseconds to mark this user as typing. + @Json(name = "timeout") + val timeout: Int? +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionId.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/typing/TypingEventContent.kt similarity index 67% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionId.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/typing/TypingEventContent.kt index 2f39806aa5..969db3f235 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionId.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/typing/TypingEventContent.kt @@ -14,10 +14,13 @@ * limitations under the License. */ -package im.vector.matrix.android.internal.auth +package im.vector.matrix.android.internal.session.room.typing -import im.vector.matrix.android.internal.util.md5 +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass -internal fun createSessionId(userId: String, deviceId: String?): String { - return (if (deviceId.isNullOrBlank()) userId else "$userId|$deviceId").md5() -} +@JsonClass(generateAdapter = true) +data class TypingEventContent( + @Json(name = "user_ids") + val typingUserIds: List = emptyList() +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt index 17c91011e7..68df456831 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt @@ -50,10 +50,10 @@ internal class DefaultSignOutService @Inject constructor(private val signOutTask } } - override fun signOut(sigOutFromHomeserver: Boolean, + override fun signOut(signOutFromHomeserver: Boolean, callback: MatrixCallback): Cancelable { return signOutTask - .configureWith(SignOutTask.Params(sigOutFromHomeserver)) { + .configureWith(SignOutTask.Params(signOutFromHomeserver)) { this.callback = callback } .executeBy(taskExecutor) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignInAgainTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignInAgainTask.kt index 666852c988..0b8902e71b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignInAgainTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignInAgainTask.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.data.PasswordLoginParams import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface SignInAgainTask : Task { @@ -33,10 +34,12 @@ internal interface SignInAgainTask : Task { internal class DefaultSignInAgainTask @Inject constructor( private val signOutAPI: SignOutAPI, private val sessionParams: SessionParams, - private val sessionParamsStore: SessionParamsStore) : SignInAgainTask { + private val sessionParamsStore: SessionParamsStore, + private val eventBus: EventBus +) : SignInAgainTask { override suspend fun execute(params: SignInAgainTask.Params) { - val newCredentials = executeRequest { + val newCredentials = executeRequest(eventBus) { apiCall = signOutAPI.loginAgain( PasswordLoginParams.userIdentifier( // Reuse the same userId diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt index b43bfa603c..9c31ce567b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt @@ -32,6 +32,7 @@ import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.worker.WorkManagerUtil import io.realm.Realm import io.realm.RealmConfiguration +import org.greenrobot.eventbus.EventBus import timber.log.Timber import java.io.File import java.net.HttpURLConnection @@ -43,25 +44,28 @@ internal interface SignOutTask : Task { ) } -internal class DefaultSignOutTask @Inject constructor(private val context: Context, - @UserId private val userId: String, - private val signOutAPI: SignOutAPI, - private val sessionManager: SessionManager, - private val sessionParamsStore: SessionParamsStore, - @SessionDatabase private val clearSessionDataTask: ClearCacheTask, - @CryptoDatabase private val clearCryptoDataTask: ClearCacheTask, - @UserCacheDirectory private val userFile: File, - private val realmKeysUtils: RealmKeysUtils, - @SessionDatabase private val realmSessionConfiguration: RealmConfiguration, - @CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration, - @UserMd5 private val userMd5: String) : SignOutTask { +internal class DefaultSignOutTask @Inject constructor( + private val context: Context, + @SessionId private val sessionId: String, + private val signOutAPI: SignOutAPI, + private val sessionManager: SessionManager, + private val sessionParamsStore: SessionParamsStore, + @SessionDatabase private val clearSessionDataTask: ClearCacheTask, + @CryptoDatabase private val clearCryptoDataTask: ClearCacheTask, + @UserCacheDirectory private val userFile: File, + private val realmKeysUtils: RealmKeysUtils, + @SessionDatabase private val realmSessionConfiguration: RealmConfiguration, + @CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration, + @UserMd5 private val userMd5: String, + private val eventBus: EventBus +) : SignOutTask { override suspend fun execute(params: SignOutTask.Params) { // It should be done even after a soft logout, to be sure the deviceId is deleted on the if (params.sigOutFromHomeserver) { Timber.d("SignOut: send request...") try { - executeRequest { + executeRequest(eventBus) { apiCall = signOutAPI.signOut() } } catch (throwable: Throwable) { @@ -79,13 +83,13 @@ internal class DefaultSignOutTask @Inject constructor(private val context: Conte } Timber.d("SignOut: release session...") - sessionManager.releaseSession(userId) + sessionManager.releaseSession(sessionId) Timber.d("SignOut: cancel pending works...") WorkManagerUtil.cancelAllWorks(context) Timber.d("SignOut: delete session params...") - sessionParamsStore.delete(userId) + sessionParamsStore.delete(sessionId) Timber.d("SignOut: clear session data...") clearSessionDataTask.execute(Unit) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index 488b9ce83d..a1699d9f55 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -37,6 +37,7 @@ import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.membership.RoomMemberEventHandler import im.vector.matrix.android.internal.session.room.read.FullyReadContent import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection +import im.vector.matrix.android.internal.session.room.typing.TypingEventContent import im.vector.matrix.android.internal.session.sync.model.* import io.realm.Realm import io.realm.kotlin.createObject @@ -97,11 +98,12 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle isInitialSync: Boolean): RoomEntity { Timber.v("Handle join sync for room $roomId") - if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) { - handleEphemeral(realm, roomId, roomSync.ephemeral, isInitialSync) + var ephemeralResult: EphemeralResult? = null + if (roomSync.ephemeral?.events?.isNotEmpty() == true) { + ephemeralResult = handleEphemeral(realm, roomId, roomSync.ephemeral, isInitialSync) } - if (roomSync.accountData != null && roomSync.accountData.events.isNullOrEmpty().not()) { + if (roomSync.accountData?.events?.isNotEmpty() == true) { handleRoomAccountDataEvents(realm, roomId, roomSync.accountData) } @@ -114,7 +116,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle // State event - if (roomSync.state != null && roomSync.state.events.isNotEmpty()) { + if (roomSync.state?.events?.isNotEmpty() == true) { val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt() ?: Int.MIN_VALUE val untimelinedStateIndex = minStateIndex + 1 @@ -125,7 +127,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle roomMemberEventHandler.handle(realm, roomId, event) } } - if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) { + if (roomSync.timeline?.events?.isNotEmpty() == true) { val chunkEntity = handleTimelineEvents( realm, roomEntity, @@ -141,7 +143,14 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle it.type == EventType.STATE_ROOM_MEMBER } != null - roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications, updateMembers = hasRoomMember) + roomSummaryUpdater.update( + realm, + roomId, + Membership.JOIN, + roomSync.summary, + roomSync.unreadNotifications, + updateMembers = hasRoomMember, + ephemeralResult = ephemeralResult) return roomEntity } @@ -215,16 +224,33 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle return chunkEntity } - @Suppress("UNCHECKED_CAST") + data class EphemeralResult( + val typingUserIds: List = emptyList() + ) + private fun handleEphemeral(realm: Realm, roomId: String, ephemeral: RoomSyncEphemeral, - isInitialSync: Boolean) { + isInitialSync: Boolean): EphemeralResult { + var result = EphemeralResult() for (event in ephemeral.events) { - if (event.type != EventType.RECEIPT) continue - val readReceiptContent = event.content as? ReadReceiptContent ?: continue - readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitialSync) + when (event.type) { + EventType.RECEIPT -> { + @Suppress("UNCHECKED_CAST") + (event.content as? ReadReceiptContent)?.let { readReceiptContent -> + readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitialSync) + } + } + EventType.TYPING -> { + event.content.toModel()?.let { typingEventContent -> + result = result.copy(typingUserIds = typingEventContent.typingUserIds) + } + } + else -> Timber.w("Ephemeral event type '${event.type}' not yet supported") + } } + + return result } private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt index d99b9df4df..b56594bd16 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.session.homeserver.GetHomeServerCapabil import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.user.UserStore import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import timber.log.Timber import javax.inject.Inject @@ -33,15 +34,17 @@ internal interface SyncTask : Task { data class Params(var timeout: Long = 30_000L) } -internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, - @UserId private val userId: String, - private val filterRepository: FilterRepository, - private val syncResponseHandler: SyncResponseHandler, - private val initialSyncProgressService: DefaultInitialSyncProgressService, - private val syncTokenStore: SyncTokenStore, - private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, - private val userStore: UserStore, - private val syncTaskSequencer: SyncTaskSequencer +internal class DefaultSyncTask @Inject constructor( + private val syncAPI: SyncAPI, + @UserId private val userId: String, + private val filterRepository: FilterRepository, + private val syncResponseHandler: SyncResponseHandler, + private val initialSyncProgressService: DefaultInitialSyncProgressService, + private val syncTokenStore: SyncTokenStore, + private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, + private val userStore: UserStore, + private val syncTaskSequencer: SyncTaskSequencer, + private val eventBus: EventBus ) : SyncTask { override suspend fun execute(params: SyncTask.Params) = syncTaskSequencer.post { @@ -70,7 +73,7 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, initialSyncProgressService.endAll() initialSyncProgressService.startTask(R.string.initial_sync_start_importing_account, 100) } - val syncResponse = executeRequest { + val syncResponse = executeRequest(eventBus) { apiCall = syncAPI.sync(requestParams) } syncResponseHandler.handleResponse(syncResponse, token) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt index d7d19802c5..07e8664102 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt @@ -38,10 +38,11 @@ import io.realm.RealmList import timber.log.Timber import javax.inject.Inject -internal class UserAccountDataSyncHandler @Inject constructor(private val monarchy: Monarchy, - @UserId private val userId: String, - private val directChatsHelper: DirectChatsHelper, - private val updateUserAccountDataTask: UpdateUserAccountDataTask) { +internal class UserAccountDataSyncHandler @Inject constructor( + private val monarchy: Monarchy, + @UserId private val userId: String, + private val directChatsHelper: DirectChatsHelper, + private val updateUserAccountDataTask: UpdateUserAccountDataTask) { fun handle(realm: Realm, accountData: UserAccountDataSync?) { accountData?.list?.forEach { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt index 9fe3e38d36..724b0e1360 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt @@ -39,7 +39,7 @@ import java.util.concurrent.atomic.AtomicBoolean */ abstract class SyncService : Service() { - private var userId: String? = null + private var sessionId: String? = null private var mIsSelfDestroyed: Boolean = false private var isInitialSync: Boolean = false @@ -58,18 +58,17 @@ abstract class SyncService : Service() { Timber.i("onStartCommand $intent") intent?.let { val matrix = Matrix.getInstance(applicationContext) - val safeUserId = it.getStringExtra(EXTRA_USER_ID) ?: return@let - val sessionComponent = matrix.sessionManager.getSessionComponent(safeUserId) + val safeSessionId = it.getStringExtra(EXTRA_SESSION_ID) ?: return@let + val sessionComponent = matrix.sessionManager.getSessionComponent(safeSessionId) ?: return@let session = sessionComponent.session() - userId = safeUserId + sessionId = safeSessionId syncTask = sessionComponent.syncTask() isInitialSync = !session.hasAlreadySynced() networkConnectivityChecker = sessionComponent.networkConnectivityChecker() taskExecutor = sessionComponent.taskExecutor() coroutineDispatchers = sessionComponent.coroutineDispatchers() backgroundDetectionObserver = matrix.backgroundDetectionObserver - onStart(isInitialSync) if (isRunning.get()) { Timber.i("Received a start while was already syncing... ignore") } else { @@ -79,6 +78,7 @@ abstract class SyncService : Service() { } } } + onStart(isInitialSync) // No intent just start the service, an alarm will should call with intent return START_STICKY } @@ -101,7 +101,7 @@ abstract class SyncService : Service() { private suspend fun doSync() { if (!networkConnectivityChecker.hasInternetAccess()) { Timber.v("No network reschedule to avoid wasting resources") - userId?.also { + sessionId?.also { onRescheduleAsked(it, isInitialSync, delay = 10_000L) } stopMe() @@ -131,14 +131,14 @@ abstract class SyncService : Service() { abstract fun onStart(isInitialSync: Boolean) - abstract fun onRescheduleAsked(userId: String, isInitialSync: Boolean, delay: Long) + abstract fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, delay: Long) override fun onBind(intent: Intent?): IBinder? { return null } companion object { - const val EXTRA_USER_ID = "EXTRA_USER_ID" + const val EXTRA_SESSION_ID = "EXTRA_SESSION_ID" private const val TIME_OUT = 0L private const val DELAY_FAILURE = 5_000L } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt index eb4f2ff7c2..3637cc624f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.api.failure.isTokenError import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.session.sync.SyncTask import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkerParamsFactory @@ -38,10 +39,11 @@ internal class SyncWorker(context: Context, @JsonClass(generateAdapter = true) internal data class Params( - val userId: String, + override val sessionId: String, val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT, - val automaticallyRetry: Boolean = false - ) + val automaticallyRetry: Boolean = false, + override val lastFailureMessage: String? = null + ) : SessionWorkerParams @Inject lateinit var syncTask: SyncTask @Inject lateinit var taskExecutor: TaskExecutor @@ -50,7 +52,7 @@ internal class SyncWorker(context: Context, override suspend fun doWork(): Result { Timber.i("Sync work starting") val params = WorkerParamsFactory.fromData(inputData) ?: return Result.success() - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) return runCatching { doSync(params.timeout) @@ -75,8 +77,8 @@ internal class SyncWorker(context: Context, const val BG_SYNC_WORK_NAME = "BG_SYNCP" - fun requireBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0) { - val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, false)) + fun requireBackgroundSync(context: Context, sessionId: String, serverTimeout: Long = 0) { + val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, false)) val workRequest = matrixOneTimeWorkRequestBuilder() .setConstraints(WorkManagerUtil.workConstraints) .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) @@ -85,8 +87,8 @@ internal class SyncWorker(context: Context, WorkManager.getInstance(context).enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest) } - fun automaticallyBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0, delay: Long = 30_000) { - val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, true)) + fun automaticallyBackgroundSync(context: Context, sessionId: String, serverTimeout: Long = 0, delay: Long = 30_000) { + val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, true)) val workRequest = matrixOneTimeWorkRequestBuilder() .setConstraints(WorkManagerUtil.workConstraints) .setInputData(data) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt index 075eeb23d1..55b26f203f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.sync.model.accountdata.IgnoredUsersContent import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface UpdateIgnoredUserIdsTask : Task { @@ -33,10 +34,13 @@ internal interface UpdateIgnoredUserIdsTask : Task { + executeRequest(eventBus) { apiCall = accountDataApi.setAccountData(userId, UserAccountData.TYPE_IGNORED_USER_LIST, body) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt index 4c4f40add5..068ce4777a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface UpdateUserAccountDataTask : Task { @@ -50,11 +51,14 @@ internal interface UpdateUserAccountDataTask : Task> { @@ -31,10 +32,13 @@ internal interface SearchUserTask : Task> { ) } -internal class DefaultSearchUserTask @Inject constructor(private val searchUserAPI: SearchUserAPI) : SearchUserTask { +internal class DefaultSearchUserTask @Inject constructor( + private val searchUserAPI: SearchUserAPI, + private val eventBus: EventBus +) : SearchUserTask { override suspend fun execute(params: SearchUserTask.Params): List { - val response = executeRequest { + val response = executeRequest(eventBus) { apiCall = searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit)) } return response.users.map { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt index 0c53a3ef0b..c05367cf10 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt @@ -17,7 +17,7 @@ package im.vector.matrix.android.internal.worker interface SessionWorkerParams { - val userId: String + val sessionId: String // Null is no error occurs. When chaining Workers, first step is to check that there is no lastFailureMessage from the previous workers val lastFailureMessage: String? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/Worker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/Worker.kt index 58abd10e81..88680786ad 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/Worker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/Worker.kt @@ -20,6 +20,6 @@ import androidx.work.ListenableWorker import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.internal.session.SessionComponent -internal fun ListenableWorker.getSessionComponent(userId: String): SessionComponent? { - return Matrix.getInstance(applicationContext).sessionManager.getSessionComponent(userId) +internal fun ListenableWorker.getSessionComponent(sessionId: String): SessionComponent? { + return Matrix.getInstance(applicationContext).sessionManager.getSessionComponent(sessionId) } diff --git a/matrix-sdk-android/src/main/res/values-az/strings.xml b/matrix-sdk-android/src/main/res/values-az/strings.xml new file mode 100644 index 0000000000..c4347619d0 --- /dev/null +++ b/matrix-sdk-android/src/main/res/values-az/strings.xml @@ -0,0 +1,189 @@ + + + %1$s: %2$s + %1$s şəkil göndərdi. + %1$s stiker göndərdi. + + %s-nin dəvəti + %1$s dəvət etdi %2$s + %1$s sizi dəvət etdi + %1$s qoşuldu + %1$s qalıb + %1$s dəvəti rədd etdi + %1$s %2$s-i xaric etdi + %1$s %2$s-i blokdan açdı + %1$s %2$s-i blokladı + %1$s %2$s-in dəvətini geri götürdü + %1$s avatarı dəyişdi + %1$s ekran adını %2$s olaraq təyin etdi + %1$s ekran adını %2$s-dan %3$s-ya dəyişdi + %1$s onların göstərilən adlarını sildi (%2$s) + %1$s mövzunu dəyişdi: %2$s + %1$s otaq adını dəyişdirdi: %2$s + %s video zəng etdi. + %s səsli zəng etdi. + %s zəngə cavab verdi. + %s zəng başa çatdı. + "%1$s gələcək otaq tarixçəsini %2$s-ə görünən etdi" + bütün otaq üzvləri, dəvət olunduğu andan. + bütün otaq üzvləri, qoşulduğu andan. + bütün otaq üzvləri. + hər kəs. + naməlum (%s). + %1$s sondan-sona şifrələmə açdı (%2$s) + %s bu otağı təkmilləşdirdi. + + %1$s VoIP konfrans istədi + VoIP konfransı başladı + VoIP konfransı başa çatdı + + (avatar da dəyişdirilib) + %1$s otaq adını sildi + %1$s otaq mövzusunu sildi + Mesaj silindi + Mesaj %1$s tərəfindən silindi + Mesaj silindi [səbəb: %1$s] + Mesaj %1$s tərəfindən qaldırıldı [səbəb: %2$s] + %1$s profilini %2$s yenilədi + %1$s otağa qoşulmaq üçün %2$s dəvətnamə göndərdi + %1$s otağa qoşulmaq üçün %2$s dəvətini ləğv etdi + %1$s %2$s üçün dəvəti qəbul etdi + + ** Şifrəni aça bilmir: %s ** + Göndərənin cihazı bu mesaj üçün açarları bizə göndərməyib. + + Cavab olaraq + + Redaktə etmək olmur + Mesaj göndərmək olmur + + Şəkil yükləmək olmur + + Şəbəkə xətası + Matris xətası + + Boş bir otağa yenidən qoşulmaq hazırda mümkün deyil. + + Şifrəli mesaj + + Elektron poçt ünvanı + Telefon nömrəsi + + şəkil göndərdi. + video göndərdi. + səs faylı göndərdi. + fayl göndərdi. + + %s-dən dəvət + Otağa dəvət + + %1$s və %2$s + + + %1$s və 1 digər + %1$s və %2$d digərləri + + + Boş otaq + + + It + Pişik + Aslan + At + Kərgədan + Donuz + Fil + Dovşan + Panda + Xoruz + Pinqvin + Tısbağa + Balıq + Ahtapot + Kəpənək + Çiçək + Ağac + Kaktus + Göbələk + Qlobus + Ay + Bulud + Atəş + Banan + Alma + Çiyələk + Qarğıdalı + Pizza + Tort + Ürək + Təbəssüm + Robot + Papaq + Eynəklər + Açar + Santa + Baş barmaqlar yuxarı + Çətir + Qum saatı + Saat + Hədiyyə + Lampa + Kitab + Qələm + Kağız sancağı + Qayçı + Qıfıl + Açar + Çəkic + Telefon + Bayraq + Qatar + Velosiped + Təyyarə + Raket + Kubok + Top + Gitara + Saz + Zəng + Anker + Qulaqlıqlar + Qovluq + Sancaq + + İlkin sinxronizasiya: +\nHesab idxal olunur… + İlkin sinxronizasiya: +\nKriptografiyanın idxalı + İlkin sinxronizasiya: +\nOtaqlar idxalı + İlkin sinxronizasiya: +\nOtaqlara daxil olmaq + İlkin sinxronizasiya: +\nDəvət olunmuş otaqların idxalı + İlkin sinxronizasiya: +\nTərk olunmuş otaqların idxalı + İlkin sinxronizasiya: +\nİcmaların idxalı + İlkin sinxronizasiya: +\nHesab məlumatlarının idxalı + + Mesaj göndərilir… + Göndərmə növbəsini təmizləyin + + %1$s-nin dəvəti. Səbəb: %2$s + %1$s dəvət olunmuş %2$s. Səbəb: %3$s + %1$s sizi dəvət etdi. Səbəb: %2$s + %1$s qoşuldu. Səbəb: %2$s + %1$s qalıb. Səbəb: %2$s + %1$s dəvəti rədd etdi. Səbəb: %2$s + %1$s %2$s-i xaric etdi. Səbəb: %3$s + %1$s blokdan açdı %2$s. Səbəb: %3$s + %1$s blokladı %2$s. Səbəb: %3$s + %1$s otağa qoşulmaq üçün %2$s dəvətnamə göndərdi. Səbəb: %3$s + %1$s otağa qoşulmaq üçün %2$s dəvətini ləğv etdi. Səbəb: %3$s + %1$s %2$s üçün dəvəti qəbul etdi. Səbəb: %3$s + %1$s %2$s dəvətini geri götürdü. Səbəb: %3$s + + diff --git a/matrix-sdk-android/src/main/res/values-bg/strings.xml b/matrix-sdk-android/src/main/res/values-bg/strings.xml index 2566ee780c..1b296074c6 100644 --- a/matrix-sdk-android/src/main/res/values-bg/strings.xml +++ b/matrix-sdk-android/src/main/res/values-bg/strings.xml @@ -173,4 +173,18 @@ Изчисти опашката за изпращане %1$s оттегли поканата за присъединяване на %2$s към стаята + поканата на %1$s. Причина: %2$s + %1$s покани %2$s. Причина: %3$s + %1$s ви покани. Причина: %2$s + %1$s се присъедини. Причина: %2$s + %1$s напусна. Причина: %2$s + %1$s отхвърли поканата. Причина: %2$s + %1$s изгони %2$s. Причина: %3$s + %1$s блокира %2$s. Причина: %3$s + %1$s блокира %2$s. Причина: %3$s + %1$s изпрати покана до %2$s да се присъедини в стаята. Причина: %3$s + %1$s премахна поканата за присъединяване на %2$s в стаята. Причина: %3$s + %1$s прие поканата за %2$s. Причина: %3$s + %1$s оттегли поканата на %2$s. Причина: %3$s + diff --git a/matrix-sdk-android/src/main/res/values-de/strings.xml b/matrix-sdk-android/src/main/res/values-de/strings.xml index 59b5ee4212..fa66f96aba 100644 --- a/matrix-sdk-android/src/main/res/values-de/strings.xml +++ b/matrix-sdk-android/src/main/res/values-de/strings.xml @@ -176,4 +176,18 @@ Erste Synchronisation: Importiere Benutzerdaten %1$s hat die Einladung an %2$s, den Raum zu betreten, zurückgezogen + %1$s\'s Einladung. Grund: %2$s + %1$s hat %2$s eingeladen. Grund: %3$s + %1$s hat dich eingeladen. Grund: %2$s + %1$s beigetreten. Grund: %2$s + %1$s ging. Grund: %2$s + %1$s hat die Einladung abgelehnt. Grund: %2$s + %1$s hat %2$s gekickt. Grund: %3$s + %1$s hat Verbannung für %2$s aufgehoben. Grund: %3$s + %1$s hat %2$s verbannt. Grund: %3$s + %1$s hat eine Einladung an %2$s gesandt um diesem Raum beizutreten. Grund: %3$s + %1$s hat Einladung an %2$s zu Betreten dieses Raumes zurückgezogen. Grund: %3$s + %1$s hat die Einladung für %2$s angenommen. Grund: %3$s + %1$s hat Einladung für %2$s verworfen. Grund: %3$s + diff --git a/matrix-sdk-android/src/main/res/values-eu/strings.xml b/matrix-sdk-android/src/main/res/values-eu/strings.xml index 5b36858253..acbfcd3a97 100644 --- a/matrix-sdk-android/src/main/res/values-eu/strings.xml +++ b/matrix-sdk-android/src/main/res/values-eu/strings.xml @@ -173,4 +173,18 @@ Garbitu bidalketa-ilara %1$s erabiltzaileak %2$s gelara elkartzeko gonbidapena indargabetu du + %1$s erabiltzailearen gonbidapena. Arrazoia: %2$s + %1$s erabiltzaileak %2$s gonbidatu du. Arrazoia: %3$s + %1$s erabiltzaileak gonbidatu zaitu. Arrazoia: %2$s + %1$s elkartu da. Arrazoia: %2$s + %1$s atera da. Arrazoia: %2$s + %1$s erabiltzaileak gonbidapena baztertu du. Arrazoia: %2$s + %1$s erabiltzaileak %2$s kanporatu du. Arrazoia: %3$s + %1$s erabiltzaileak debekua kendu dio %2$s erabiltzaileari. Arrazoia: %3$s + %1$s erabiltzaileak %2$s debekatu du. Arrazoia: %3$s + "%1$s erabiltzaileak gelara elkartzeko gonbidapen bat bidali dio %2$s erabiltzaileari. Arrazoia: %3$s" + "%1$s erabiltzaileak %2$s gelara elkartzeko gonbidapena indargabetu du. Arrazoia: %3$s" + "%1$s erabiltzaileak %2$s gelarako gonbidapena onartu du. Arrazoia: %3$s" + "%1$s erabiltzaileak %2$s erabiltzailearen gonbidapena indargabetu du. Arrazoia: %3$s" + diff --git a/matrix-sdk-android/src/main/res/values-fi/strings.xml b/matrix-sdk-android/src/main/res/values-fi/strings.xml index 3c02e5c2ce..b101b91736 100644 --- a/matrix-sdk-android/src/main/res/values-fi/strings.xml +++ b/matrix-sdk-android/src/main/res/values-fi/strings.xml @@ -174,4 +174,18 @@ Tyhjennä lähetysjono %1$s veti takaisin käyttäjän %2$s liittymiskutsun huoneeseen + Henkilön %1$s kutsu. Syy: %2$s + %1$s kutsui henkilön %2$s. Syy: %3$s + %1$s kutsui sinut. Syy: %2$s + %1$s liittyi. Syy: %2$s + %1$s poistui. Syy: %2$s + %1$s hylkäsi kutsun. Syy: %2$s + %1$s potkaisi käyttäjän %2$s pois. Syy: %3$s + %1$s poisti eston käyttäjältä %2$s. Syy: %3$s + %1$s esti käyttäjän %2$s. Syy: %3$s + %1$s lähetti kutsun käyttäjälle %2$s huoneeseen liittymiseksi. Syy: %3$s + %1$s kumosi kutsun käyttäjälle %2$s huoneeseen liittymiseksi. Syy: %3$s + %1$s hyväksyi kutsun liityäkseen huoneeseen %2$s. Syy: %3$s + %1$s poisti käyttäjän %2$s kutsun. Syy: %3$s + diff --git a/matrix-sdk-android/src/main/res/values-fr/strings.xml b/matrix-sdk-android/src/main/res/values-fr/strings.xml index 98a98a3e7a..01f0cfae0f 100644 --- a/matrix-sdk-android/src/main/res/values-fr/strings.xml +++ b/matrix-sdk-android/src/main/res/values-fr/strings.xml @@ -173,4 +173,18 @@ Vider la file d’envoi %1$s a révoqué l’invitation pour %2$s à rejoindre le salon + Invitation de %1$s. Raison : %2$s + %1$s a invité %2$s. Raison : %3$s + %1$s vous a invité. Raison : %2$s + %1$s a rejoint le salon. Raison : %2$s + %1$s est parti. Raison : %2$s + %1$s a refusé l’invitation. Raison : %2$s + %1$s a expulsé %2$s. Raison : %3$s + %1$s a révoqué le bannissement de %2$s. Raison : %3$s + %1$s a banni %2$s. Raison : %3$s + %1$s a envoyé une invitation à %2$s pour rejoindre le salon. Raison : %3$s + %1$s a révoqué l’invitation de %2$s à rejoindre le salon. Raison : %3$s + %1$s a accepté l’invitation pour %2$s. Raison : %3$s + %1$s a annulé l’invitation de %2$s. Raison : %3$s + diff --git a/matrix-sdk-android/src/main/res/values-hu/strings.xml b/matrix-sdk-android/src/main/res/values-hu/strings.xml index da6b8f5687..2294661621 100644 --- a/matrix-sdk-android/src/main/res/values-hu/strings.xml +++ b/matrix-sdk-android/src/main/res/values-hu/strings.xml @@ -172,4 +172,18 @@ Küldő sor ürítése %1$s visszavonta a meghívót a belépéshez ebbe a szobába: %2$s + %1$s meghívója. Ok: %2$s + %1$s meghívta őt: %2$s. Ok: %3$s + %1$s meghívott. Ok: %2$s + %1$s csatlakozott. Ok: %2$s + %1$s kilépett. Ok: %2$s + %1$s visszautasította a meghívót. Ok: %2$s + %1$s kirúgta őt: %2$s. Ok: %3$s + %1$s visszaengedte őt: %2$s. Ok: %3$s + %1$s kitiltotta őt: %2$s. Ok: %3$s + %1$s meghívót küldött neki: %2$s, hogy lépjen be a szobába. Ok: %3$s + %1$s visszavonta %2$s meghívóját a szobába való belépéshez. Ok: %3$s + %1$s elfogadta a meghívót ide: %2$s. Ok: %3$s + %1$s visszavonta %2$s meghívóját. Ok: %3$s + diff --git a/matrix-sdk-android/src/main/res/values-it/strings.xml b/matrix-sdk-android/src/main/res/values-it/strings.xml index a8d844ddde..585ec47703 100644 --- a/matrix-sdk-android/src/main/res/values-it/strings.xml +++ b/matrix-sdk-android/src/main/res/values-it/strings.xml @@ -173,4 +173,18 @@ Cancella la coda di invio %1$s ha revocato l\'invito a %2$s di unirsi alla stanza + Invito di %1$s. Motivo: %2$s + %1$s ha invitato %2$s. Motivo: %3$s + %1$s ti ha invitato. Motivo: %2$s + %1$s è entrato. Motivo: %2$s + %1$s è uscito. Motivo: %2$s + %1$s ha rifiutato l\'invito. Motivo: %2$s + %1$s ha buttato fuori %2$s. Motivo: %3$s + %1$s ha riammesso %2$s. Motivo: %3$s + %1$s ha bandito %2$s. Motivo: %3$s + %1$s ha inviato un invito a %2$s di unirsi alla stanza. Motivo: %3$s + %1$s ha revocato l\'invito a %2$s di unirsi alla stanza. Motivo: %3$s + %1$s ha accettato l\'invito per %2$s. Motivo: %3$s + %1$s ha rifiutato l\'invito di %2$s. Motivo: %3$s + diff --git a/matrix-sdk-android/src/main/res/values-ja/strings.xml b/matrix-sdk-android/src/main/res/values-ja/strings.xml index d2a051caeb..b72d1a13ca 100644 --- a/matrix-sdk-android/src/main/res/values-ja/strings.xml +++ b/matrix-sdk-android/src/main/res/values-ja/strings.xml @@ -40,7 +40,7 @@ 部屋のメンバー全員。 誰でも。 不明 (%s)。 - %1$sはエンドツーエンドの暗号化を有効にしました (%2$s) + %1$s がエンドツーエンド暗号化を有効にしました (%2$s) %1$s がVoIP会議をリクエストしました VoIP会議が開始されました diff --git a/matrix-sdk-android/src/main/res/values-ru/strings.xml b/matrix-sdk-android/src/main/res/values-ru/strings.xml index 07411c097f..b0f2a60ac0 100644 --- a/matrix-sdk-android/src/main/res/values-ru/strings.xml +++ b/matrix-sdk-android/src/main/res/values-ru/strings.xml @@ -132,7 +132,7 @@ Робот Шляпа Очки - гаечный ключ + Гаечный ключ Санта Большой палец вверх Зонтик @@ -186,4 +186,18 @@ Очистить очередь отправки %1$s отозвал приглашение %2$s присоединиться к комнате + Приглашение %1$s. Причина: %2$s + %1$s приглашен %2$s. Причина: %3$s + %1$s пригласил вас. Причина: %2$s + %1$s присоединился. Причина: %2$s + Осталось %1$s. Причина: %2$s + %1$s отклонил приглашение. Причина: %2$s + %1$s выгнали %2$s. Причина: %3$s + %1$s разблокировано %2$s. Причина: %3$s + %1$s забанен %2$s. Причина: %3$s + %1$s отправил приглашение %2$s в комнату. Причина: %3$s + %1$s отозвал приглашение %2$s присоединиться к комнате. Причина: %3$s + %1$s принял приглашение для %2$s. Причина: %3$s + %1$s отозвал приглашение %2$s. Причина: %3$s + diff --git a/matrix-sdk-android/src/main/res/values-sq/strings.xml b/matrix-sdk-android/src/main/res/values-sq/strings.xml index cffd55a7f7..bd6993fab1 100644 --- a/matrix-sdk-android/src/main/res/values-sq/strings.xml +++ b/matrix-sdk-android/src/main/res/values-sq/strings.xml @@ -169,4 +169,18 @@ Spastro radhë pritjeje %1$s shfuqizoi ftesën për %2$s për pjesëmarrje te dhoma + Ftesë e %1$s. Arsye: %2$s + %1$s ftoi %2$s. Arsye: %3$s + %1$s ju ftoi. Arsye: %2$s + %1$s erdhi. Arsye: %2$s + %1$s iku. Arsye: %2$s + %1$s hodhi poshtë ftesën. Arsye: %2$s + %1$s përzuri %2$s. Arsye: %3$s + %1$s hoqi dëbimin për %2$s. Arsye: %3$s + %1$s dëboi %2$s. Arsye: %3$s + %1$s dërgoi një ftesë për %2$s për të ardhur në dhomë. Arsye: %3$s + %1$s shfuqizoi ftesën për %2$s për të ardhur në dhomë. Arsye: %3$s + %1$s pranoi ftesën për %2$s. Arsye: %3$s + %1$s tërhoqi mbrapsht ftesën për %2$s. Arsye: %3$s + diff --git a/matrix-sdk-android/src/main/res/values-uk/strings.xml b/matrix-sdk-android/src/main/res/values-uk/strings.xml index 9f8e9078a4..bf83e39d72 100644 --- a/matrix-sdk-android/src/main/res/values-uk/strings.xml +++ b/matrix-sdk-android/src/main/res/values-uk/strings.xml @@ -32,7 +32,7 @@ %1$s змінив(ла) назву кімнати на: %2$s %s розпочав(ла) відеодзвінок. %s розпочав(ла) голосовий дзвінок. - %s віпдовів(ла) на дзвінок. + %s відповів(ла) на дзвінок. %s завершив(ла) дзвінок. %1$s зробив(ла) майбутню історію кімнати видимою для %2$s усіх співрозмовників, з моменту їх запрошення. @@ -80,7 +80,12 @@ %1$s та 1 інший %1$s та %2$d інші %1$s та %2$d інших - + + %s вдосконалили цю кімнату. + + Повідомлення видалено + %1$s видалили повідомлення + Повідомлення видалено [причина: %1$s] diff --git a/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml b/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml index 5b5ae3beb0..e60bfd564d 100644 --- a/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml +++ b/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml @@ -171,4 +171,18 @@ 清除傳送佇列 %1$s 撤銷了 %2$s 加入聊天室的邀請 + %1$s 的邀請。理由:%2$s + %1$s 邀請了 %2$s。理由:%3$s + %1$s 邀請了您。理由:%2$s + %1$s 已加入。理由:%2$s + %1$s 已離開。理由:%2$s + %1$s 已回絕邀請。理由:%2$s + %1$s 踢走了 %2$s。理由:%3$s + %1$s 取消封鎖了 %2$s。理由:%3$s + %1$s 封鎖了 %2$s。理由:%3$s + %1$s 已傳送邀請給 %2$s 來加入聊天室。理由:%3$s + %1$s 撤銷了 %2$s 加入聊天室的邀請。理由:%3$s + %1$s 接受 %2$s 的邀請。理由:%3$s + %1$s 撤回了對 %2$s 的邀請。理由:%3$s + diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index e611ae25b0..d5cd7a117b 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -257,4 +257,22 @@ %1$s accepted the invitation for %2$s. Reason: %3$s %1$s withdrew %2$s\'s invitation. Reason: %3$s + + %1$s added %2$s as an address for this room. + %1$s added %2$s as addresses for this room. + + + + %1$s removed %2$s as an address for this room. + %1$s removed %3$s as addresses for this room. + + + %1$s added %2$s and removed %3$s as addresses for this room. + + "%1$s set the main address for this room to %2$s." + "%1$s removed the main address for this room." + + "%1$s has allowed guests to join the room." + "%1$s has prevented guests from joining the room." + diff --git a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml index 84aa9d9f5c..d781ec5f1e 100644 --- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml +++ b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml @@ -1,20 +1,4 @@ - - - %1$s added %2$s as an address for this room. - %1$s added %2$s as addresses for this room. - - - - %1$s removed %2$s as an address for this room. - %1$s removed %3$s as addresses for this room. - - - %1$s added %2$s and removed %3$s as addresses for this room. - - "%1$s set the main address for this room to %2$s." - "%1$s removed the main address for this room." - diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt index 1d1bbe1406..d67f14842b 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt @@ -25,7 +25,8 @@ import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import io.mockk.every import io.mockk.mockk -import org.junit.Assert +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Test class PushrulesConditionTest { @@ -147,9 +148,9 @@ class PushrulesConditionTest { content = MessageTextContent("m.text", "A").toContent(), originServerTs = 0, roomId = room2JoinedId).also { - Assert.assertFalse("This room does not have 3 members", conditionEqual3.isSatisfied(it, sessionStub)) - Assert.assertFalse("This room does not have 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub)) - Assert.assertTrue("This room has less than 3 members", conditionLessThan3.isSatisfied(it, sessionStub)) + assertFalse("This room does not have 3 members", conditionEqual3.isSatisfied(it, sessionStub)) + assertFalse("This room does not have 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub)) + assertTrue("This room has less than 3 members", conditionLessThan3.isSatisfied(it, sessionStub)) } Event( @@ -158,9 +159,9 @@ class PushrulesConditionTest { content = MessageTextContent("m.text", "A").toContent(), originServerTs = 0, roomId = room3JoinedId).also { - Assert.assertTrue("This room has 3 members", conditionEqual3.isSatisfied(it, sessionStub)) - Assert.assertTrue("This room has 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub)) - Assert.assertFalse("This room has more than 3 members", conditionLessThan3.isSatisfied(it, sessionStub)) + assertTrue("This room has 3 members", conditionEqual3.isSatisfied(it, sessionStub)) + assertTrue("This room has 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub)) + assertFalse("This room has more than 3 members", conditionLessThan3.isSatisfied(it, sessionStub)) } } @@ -174,7 +175,7 @@ class PushrulesConditionTest { content = MessageTextContent("m.notice", "A").toContent(), originServerTs = 0, roomId = "2joined").also { - Assert.assertTrue("Notice", conditionEqual.isSatisfied(it)) + assertTrue("Notice", conditionEqual.isSatisfied(it)) } } } diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/keysbackup/util/Base58Test.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/keysbackup/util/Base58Test.kt new file mode 100644 index 0000000000..42295dada0 --- /dev/null +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/keysbackup/util/Base58Test.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2018 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.crypto.keysbackup.util + +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runners.MethodSorters + +@FixMethodOrder(MethodSorters.JVM) +class Base58Test { + + @Test + fun encode() { + // Example comes from https://github.com/keis/base58 + assertEquals("StV1DL6CwTryKyV", base58encode("hello world".toByteArray())) + } + + @Test + fun decode() { + // Example comes from https://github.com/keis/base58 + assertArrayEquals("hello world".toByteArray(), base58decode("StV1DL6CwTryKyV")) + } + + @Test + fun encode_curve25519() { + // Encode a 32 bytes key + assertEquals("4F85ZySpwyY6FuH7mQYyyr5b8nV9zFRBLj92AJa37sMr", + base58encode(("0123456789" + "0123456789" + "0123456789" + "01").toByteArray())) + } + + @Test + fun decode_curve25519() { + assertArrayEquals(("0123456789" + "0123456789" + "0123456789" + "01").toByteArray(), + base58decode("4F85ZySpwyY6FuH7mQYyyr5b8nV9zFRBLj92AJa37sMr")) + } +} diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/keysbackup/util/RecoveryKeyTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/keysbackup/util/RecoveryKeyTest.kt new file mode 100644 index 0000000000..47a2aa08df --- /dev/null +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/keysbackup/util/RecoveryKeyTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2018 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.crypto.keysbackup.util + +import org.junit.Assert.* +import org.junit.Test + +class RecoveryKeyTest { + private val curve25519Key = byteArrayOf( + 0x77.toByte(), 0x07.toByte(), 0x6D.toByte(), 0x0A.toByte(), 0x73.toByte(), 0x18.toByte(), 0xA5.toByte(), 0x7D.toByte(), + 0x3C.toByte(), 0x16.toByte(), 0xC1.toByte(), 0x72.toByte(), 0x51.toByte(), 0xB2.toByte(), 0x66.toByte(), 0x45.toByte(), + 0xDF.toByte(), 0x4C.toByte(), 0x2F.toByte(), 0x87.toByte(), 0xEB.toByte(), 0xC0.toByte(), 0x99.toByte(), 0x2A.toByte(), + 0xB1.toByte(), 0x77.toByte(), 0xFB.toByte(), 0xA5.toByte(), 0x1D.toByte(), 0xB9.toByte(), 0x2C.toByte(), 0x2A.toByte()) + + @Test + fun isValidRecoveryKey_valid_true() { + assertTrue(isValidRecoveryKey("EsTcLW2KPGiFwKEA3As5g5c4BXwkqeeJZJV8Q9fugUMNUE4d")) + + // Space should be ignored + assertTrue(isValidRecoveryKey("EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d")) + + // All whitespace should be ignored + assertTrue(isValidRecoveryKey("EsTc LW2K PGiF wKEA 3As5 g5c4\r\nBXwk qeeJ ZJV8 Q9fu gUMN UE4d")) + } + + @Test + fun isValidRecoveryKey_null_false() { + assertFalse(isValidRecoveryKey(null)) + } + + @Test + fun isValidRecoveryKey_empty_false() { + assertFalse(isValidRecoveryKey("")) + } + + @Test + fun isValidRecoveryKey_wrong_size_false() { + assertFalse(isValidRecoveryKey("abc")) + } + + @Test + fun isValidRecoveryKey_bad_first_byte_false() { + assertFalse(isValidRecoveryKey("FsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d")) + } + + @Test + fun isValidRecoveryKey_bad_second_byte_false() { + assertFalse(isValidRecoveryKey("EqTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d")) + } + + @Test + fun isValidRecoveryKey_bad_parity_false() { + assertFalse(isValidRecoveryKey("EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4e")) + } + + @Test + fun computeRecoveryKey_ok() { + assertEquals("EsTcLW2KPGiFwKEA3As5g5c4BXwkqeeJZJV8Q9fugUMNUE4d", computeRecoveryKey(curve25519Key)) + } + + @Test + fun extractCurveKeyFromRecoveryKey_ok() { + assertArrayEquals(curve25519Key, extractCurveKeyFromRecoveryKey("EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d")) + } +} diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/store/db/HelperTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/store/db/HelperTest.kt new file mode 100644 index 0000000000..d4740bdc4f --- /dev/null +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/store/db/HelperTest.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2018 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.crypto.store.db + +import im.vector.matrix.android.internal.util.md5 +import org.junit.Assert.assertEquals +import org.junit.Test + +class HelperTest { + + @Test + fun testHash_ok() { + assertEquals("e9ee13b1ba2afc0825f4e556114785dd", "alice_15428931567802abf5ba7-d685-4333-af47-d38417ab3724:localhost:8480".md5()) + } + + @Test + fun testHash_size_ok() { + // Any String will have a 32 char hash + for (i in 1..100) { + assertEquals(32, "a".repeat(i).md5().length) + } + } +} diff --git a/tools/import_from_riot.sh b/tools/import_from_riot.sh index 271a872a48..23f9167a5d 100755 --- a/tools/import_from_riot.sh +++ b/tools/import_from_riot.sh @@ -24,6 +24,7 @@ echo "Copy strings to SDK" cp ../matrix-android-sdk/matrix-sdk/src/main/res/values/strings.xml ./matrix-sdk-android/src/main/res/values/strings.xml cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-ar/strings.xml ./matrix-sdk-android/src/main/res/values-ar/strings.xml +cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-az/strings.xml ./matrix-sdk-android/src/main/res/values-az/strings.xml cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-bg/strings.xml ./matrix-sdk-android/src/main/res/values-bg/strings.xml cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-bs/strings.xml ./matrix-sdk-android/src/main/res/values-bs/strings.xml cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-ca/strings.xml ./matrix-sdk-android/src/main/res/values-ca/strings.xml @@ -67,6 +68,7 @@ echo "Copy strings to RiotX" cp ../riot-android/vector/src/main/res/values/strings.xml ./vector/src/main/res/values/strings.xml cp ../riot-android/vector/src/main/res/values-ar/strings.xml ./vector/src/main/res/values-ar/strings.xml +cp ../riot-android/vector/src/main/res/values-az/strings.xml ./vector/src/main/res/values-az/strings.xml cp ../riot-android/vector/src/main/res/values-b+sr+Latn/strings.xml ./vector/src/main/res/values-b+sr+Latn/strings.xml cp ../riot-android/vector/src/main/res/values-bg/strings.xml ./vector/src/main/res/values-bg/strings.xml cp ../riot-android/vector/src/main/res/values-bn-rIN/strings.xml ./vector/src/main/res/values-bn-rIN/strings.xml diff --git a/vector/build.gradle b/vector/build.gradle index 9bab8596b1..4960c6b796 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -15,7 +15,7 @@ androidExtensions { } ext.versionMajor = 0 -ext.versionMinor = 12 +ext.versionMinor = 13 ext.versionPatch = 0 static def getGitTimestamp() { @@ -24,12 +24,16 @@ static def getGitTimestamp() { } static def generateVersionCodeFromTimestamp() { - // It's unix timestamp divided by 10: It's incremented by one every 10 seconds. - return (getGitTimestamp() / 10).toInteger() + // It's unix timestamp, minus timestamp of October 3rd 2018 (first commit date) divided by 100: It's incremented by one every 100 seconds. + // plus 20_000_000 for compatibility reason with the previous way the Version Code was computed + // Note that the result will be multiplied by 10 when adding the digit for the arch + return ((getGitTimestamp() - 1_538_524_800 ) / 100).toInteger() + 20_000_000 } def generateVersionCodeFromVersionName() { - return versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch + // plus 4_000_000 for compatibility reason with the previous way the Version Code was computed + // Note that the result will be multiplied by 10 when adding the digit for the arch + return (versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch) + 4_000_000 } def getVersionCode() { @@ -77,8 +81,8 @@ project.android.buildTypes.all { buildType -> ] } -// map for the version codes -// x86 must have greater values than arm, see https://software.intel.com/en-us/android/articles/google-play-supports-cpu-architecture-filtering-for-multiple-apk +// map for the version codes last digit +// x86 must have greater values than arm // 64 bits have greater value than 32 bits ext.abiVersionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2, "x86": 3, "x86_64": 4].withDefault { 0 } @@ -144,7 +148,7 @@ android { variant.outputs.each { output -> def baseAbiVersionCode = project.ext.abiVersionCodes.get(output.getFilter(OutputFile.ABI)) // Known limitation: it does not modify the value in the BuildConfig.java generated file - output.versionCodeOverride = baseAbiVersionCode * 10_000_000 + variant.versionCode + output.versionCodeOverride = variant.versionCode * 10 + baseAbiVersionCode } } } diff --git a/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt b/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt index 951fcaa14d..84895f9f43 100644 --- a/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt +++ b/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt @@ -31,17 +31,17 @@ import timber.log.Timber class AlarmSyncBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - // Aquire a lock to give enough time for the sync :/ + // Acquire a lock to give enough time for the sync :/ (context.getSystemService(Context.POWER_SERVICE) as PowerManager).run { newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "riotx:fdroidSynclock").apply { acquire((10_000).toLong()) } } - val userId = intent.getStringExtra(SyncService.EXTRA_USER_ID) + val sessionId = intent.getStringExtra(SyncService.EXTRA_SESSION_ID) // This method is called when the BroadcastReceiver is receiving an Intent broadcast. Timber.d("RestartBroadcastReceiver received intent") - VectorSyncService.newIntent(context, userId).also { + VectorSyncService.newIntent(context, sessionId).let { try { ContextCompat.startForegroundService(context, it) } catch (ex: Throwable) { @@ -50,7 +50,7 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { } } - scheduleAlarm(context, userId, 30_000L) + scheduleAlarm(context, sessionId, 30_000L) Timber.i("Alarm scheduled to restart service") } @@ -58,10 +58,10 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { companion object { private const val REQUEST_CODE = 0 - fun scheduleAlarm(context: Context, userId: String, delay: Long) { + fun scheduleAlarm(context: Context, sessionId: String, delay: Long) { // Reschedule val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java).apply { - putExtra(SyncService.EXTRA_USER_ID, userId) + putExtra(SyncService.EXTRA_SESSION_ID, sessionId) } val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT) val firstMillis = System.currentTimeMillis() + delay diff --git a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt index f2daa6fc9a..88b6ef5463 100644 --- a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt @@ -67,6 +67,9 @@ class DefaultErrorFormatter @Inject constructor( throwable.error.code == MatrixError.M_NOT_JSON -> { stringProvider.getString(R.string.login_error_not_json) } + throwable.error.code == MatrixError.M_THREEPID_DENIED -> { + stringProvider.getString(R.string.login_error_threepid_denied) + } throwable.error.code == MatrixError.M_LIMIT_EXCEEDED -> { limitExceededError(throwable.error) } diff --git a/vector/src/main/java/im/vector/riotx/core/error/fatal.kt b/vector/src/main/java/im/vector/riotx/core/error/fatal.kt index 800e1ea7ad..ad6a99928a 100644 --- a/vector/src/main/java/im/vector/riotx/core/error/fatal.kt +++ b/vector/src/main/java/im/vector/riotx/core/error/fatal.kt @@ -16,14 +16,13 @@ package im.vector.riotx.core.error -import im.vector.riotx.BuildConfig import timber.log.Timber /** * throw in debug, only log in production. As this method does not always throw, next statement should be a return */ -fun fatalError(message: String) { - if (BuildConfig.DEBUG) { +fun fatalError(message: String, failFast: Boolean) { + if (failFast) { error(message) } else { Timber.e(message) diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt index 620e32f51f..0a8345c650 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt @@ -47,7 +47,7 @@ fun Session.configureAndStart(context: Context, fun Session.startSyncing(context: Context) { val applicationContext = context.applicationContext if (!hasAlreadySynced()) { - VectorSyncService.newIntent(applicationContext, myUserId).also { + VectorSyncService.newIntent(applicationContext, sessionId).also { try { ContextCompat.startForegroundService(applicationContext, it) } catch (ex: Throwable) { diff --git a/vector/src/main/java/im/vector/riotx/core/rx/RxConfig.kt b/vector/src/main/java/im/vector/riotx/core/rx/RxConfig.kt index d8828eb1b8..bd87251a58 100644 --- a/vector/src/main/java/im/vector/riotx/core/rx/RxConfig.kt +++ b/vector/src/main/java/im/vector/riotx/core/rx/RxConfig.kt @@ -16,7 +16,6 @@ package im.vector.riotx.core.rx -import im.vector.riotx.BuildConfig import im.vector.riotx.features.settings.VectorPreferences import io.reactivex.plugins.RxJavaPlugins import timber.log.Timber @@ -33,8 +32,8 @@ class RxConfig @Inject constructor( RxJavaPlugins.setErrorHandler { throwable -> Timber.e(throwable, "RxError") - // Avoid crash in production - if (BuildConfig.DEBUG || vectorPreferences.failFast()) { + // Avoid crash in production, except if user wants it + if (vectorPreferences.failFast()) { throw throwable } } diff --git a/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt index b6b8fbf06a..314e12db05 100644 --- a/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt +++ b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt @@ -30,9 +30,9 @@ class VectorSyncService : SyncService() { companion object { - fun newIntent(context: Context, userId: String): Intent { + fun newIntent(context: Context, sessionId: String): Intent { return Intent(context, VectorSyncService::class.java).also { - it.putExtra(EXTRA_USER_ID, userId) + it.putExtra(EXTRA_SESSION_ID, sessionId) } } } @@ -54,25 +54,25 @@ class VectorSyncService : SyncService() { startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification) } - override fun onRescheduleAsked(userId: String, isInitialSync: Boolean, delay: Long) { - reschedule(userId, delay) + override fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, delay: Long) { + reschedule(sessionId, delay) } override fun onDestroy() { - removeForegroundNotif() + removeForegroundNotification() super.onDestroy() } - private fun removeForegroundNotif() { + private fun removeForegroundNotification() { val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.cancel(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE) } - private fun reschedule(userId: String, delay: Long) { + private fun reschedule(sessionId: String, delay: Long) { val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - PendingIntent.getForegroundService(this, 0, newIntent(this, userId), 0) + PendingIntent.getForegroundService(this, 0, newIntent(this, sessionId), 0) } else { - PendingIntent.getService(this, 0, newIntent(this, userId), 0) + PendingIntent.getService(this, 0, newIntent(this, sessionId), 0) } val firstMillis = System.currentTimeMillis() + delay val alarmMgr = getSystemService(Context.ALARM_SERVICE) as AlarmManager diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt index 970df6c403..4be19f2e73 100644 --- a/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/member/AutocompleteMemberPresenter.kt @@ -63,6 +63,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context, QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE) } memberships = listOf(Membership.JOIN) + excludeSelf = true } val members = room.getRoomMembers(queryParams) .asSequence() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt index 3b77835917..2761be88f1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt @@ -22,9 +22,11 @@ import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.core.epoxy.zeroItem import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.home.room.typing.TypingHelper import javax.inject.Inject class BreadcrumbsController @Inject constructor( + private val typingHelper: TypingHelper, private val avatarRenderer: AvatarRenderer ) : EpoxyController() { @@ -62,6 +64,7 @@ class BreadcrumbsController @Inject constructor( unreadNotificationCount(it.notificationCount) showHighlighted(it.highlightCount > 0) hasUnreadMessage(it.hasUnreadMessages) + hasTypingUsers(typingHelper.excludeCurrentUser(it.typingRoomMemberIds).isNotEmpty()) hasDraft(it.userDrafts.isNotEmpty()) itemClickListener( DebouncedClickListener(View.OnClickListener { _ -> diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt index 6d18a85b75..8b36b307a9 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt @@ -37,6 +37,7 @@ abstract class BreadcrumbsItem : VectorEpoxyModel() { @EpoxyAttribute var unreadNotificationCount: Int = 0 @EpoxyAttribute var showHighlighted: Boolean = false @EpoxyAttribute var hasUnreadMessage: Boolean = false + @EpoxyAttribute var hasTypingUsers: Boolean = false @EpoxyAttribute var hasDraft: Boolean = false @EpoxyAttribute var itemClickListener: View.OnClickListener? = null @@ -44,6 +45,7 @@ abstract class BreadcrumbsItem : VectorEpoxyModel() { super.bind(holder) holder.rootView.setOnClickListener(itemClickListener) holder.unreadIndentIndicator.isVisible = hasUnreadMessage + holder.typingIndicator.isVisible = hasTypingUsers avatarRenderer.render(matrixItem, holder.avatarImageView) holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted)) holder.draftIndentIndicator.isVisible = hasDraft @@ -53,6 +55,7 @@ abstract class BreadcrumbsItem : VectorEpoxyModel() { val unreadCounterBadgeView by bind(R.id.breadcrumbsUnreadCounterBadgeView) val unreadIndentIndicator by bind(R.id.breadcrumbsUnreadIndicator) val draftIndentIndicator by bind(R.id.breadcrumbsDraftBadge) + val typingIndicator by bind(R.id.breadcrumbsTypingView) val avatarImageView by bind(R.id.breadcrumbsImageView) val rootView by bind(R.id.breadcrumbsRoot) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt index c1743ae3fc..8024e5b547 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotx.core.platform.VectorViewModelAction sealed class RoomDetailAction : VectorViewModelAction { + data class UserIsTyping(val isTyping: Boolean) : RoomDetailAction() data class SaveDraft(val draft: String) : RoomDetailAction() data class SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : RoomDetailAction() data class SendMedia(val attachments: List) : RoomDetailAction() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index e1832dd7d8..77a4abc1bd 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -20,6 +20,7 @@ import android.annotation.SuppressLint import android.app.Activity.RESULT_OK import android.content.DialogInterface import android.content.Intent +import android.graphics.Typeface import android.net.Uri import android.os.Build import android.os.Bundle @@ -61,6 +62,7 @@ import com.github.piasy.biv.BigImageViewer import com.github.piasy.biv.loader.ImageLoader import com.google.android.material.snackbar.Snackbar import com.google.android.material.textfield.TextInputEditText +import com.jakewharton.rxbinding3.widget.textChanges import im.vector.matrix.android.api.permalinks.PermalinkFactory import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.content.ContentAttachmentData @@ -139,6 +141,7 @@ import im.vector.riotx.features.permalink.PermalinkHandler import im.vector.riotx.features.reactions.EmojiReactionPickerActivity import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.share.SharedData +import im.vector.riotx.features.themes.ThemeUtils import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import kotlinx.android.parcel.Parcelize @@ -148,6 +151,7 @@ import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import org.commonmark.parser.Parser import timber.log.Timber import java.io.File +import java.util.concurrent.TimeUnit import javax.inject.Inject @Parcelize @@ -582,6 +586,9 @@ class RoomDetailFragment @Inject constructor( private fun setupComposer() { autoCompleter.setup(composerLayout.composerEditText) + + observerUserTyping() + composerLayout.callback = object : TextComposerView.Callback { override fun onAddAttachment() { if (!::attachmentTypeSelector.isInitialized) { @@ -618,6 +625,18 @@ class RoomDetailFragment @Inject constructor( } } + private fun observerUserTyping() { + composerLayout.composerEditText.textChanges() + .skipInitialValue() + .debounce(300, TimeUnit.MILLISECONDS) + .map { it.isNotEmpty() } + .subscribe { + Timber.d("Typing: User is typing: $it") + roomDetailViewModel.handle(RoomDetailAction.UserIsTyping(it)) + } + .disposeOnDestroyView() + } + private fun sendUri(uri: Uri): Boolean { val shareIntent = Intent(Intent.ACTION_SEND, uri) val isHandled = attachmentsHelper.handleShareIntent(shareIntent) @@ -670,13 +689,29 @@ class RoomDetailFragment @Inject constructor( } else { roomToolbarTitleView.text = it.displayName avatarRenderer.render(it.toMatrixItem(), roomToolbarAvatarImageView) - roomToolbarSubtitleView.setTextOrHide(it.topic) + + renderSubTitle(state.typingMessage, it.topic) } jumpToBottomView.count = it.notificationCount jumpToBottomView.drawBadge = it.hasUnreadMessages } } + private fun renderSubTitle(typingMessage: String?, topic: String) { + // TODO Temporary place to put typing data + roomToolbarSubtitleView.let { + it.setTextOrHide(typingMessage ?: topic) + + if (typingMessage == null) { + it.setTextColor(ThemeUtils.getColor(requireContext(), R.attr.vctr_toolbar_secondary_text_color)) + it.setTypeface(null, Typeface.NORMAL) + } else { + it.setTextColor(ContextCompat.getColor(requireContext(), R.color.riotx_accent)) + it.setTypeface(null, Typeface.BOLD) + } + } + } + private fun renderTombstoneEventHandling(async: Async) { when (async) { is Loading -> { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index e780d36411..41c90504f5 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -20,12 +20,7 @@ import android.net.Uri import androidx.annotation.IdRes import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.ViewModelContext +import com.airbnb.mvrx.* import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.PublishRelay import com.squareup.inject.assisted.Assisted @@ -55,7 +50,6 @@ import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap -import im.vector.riotx.BuildConfig import im.vector.riotx.R import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.platform.VectorViewModel @@ -68,6 +62,7 @@ import im.vector.riotx.core.utils.subscribeLogError import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.ParsedCommand import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents +import im.vector.riotx.features.home.room.typing.TypingHelper import im.vector.riotx.features.settings.VectorPreferences import io.reactivex.Observable import io.reactivex.functions.BiFunction @@ -84,6 +79,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro userPreferencesProvider: UserPreferencesProvider, private val vectorPreferences: VectorPreferences, private val stringProvider: StringProvider, + private val typingHelper: TypingHelper, private val session: Session ) : VectorViewModel(initialState), Timeline.Listener { @@ -93,16 +89,16 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private val visibleEventsObservable = BehaviorRelay.create() private val timelineSettings = if (userPreferencesProvider.shouldShowHiddenEvents()) { TimelineSettings(30, - filterEdits = false, - filterTypes = true, - allowedTypes = TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES, - buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts()) + filterEdits = false, + filterTypes = true, + allowedTypes = TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES, + buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts()) } else { TimelineSettings(30, - filterEdits = true, - filterTypes = true, - allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES, - buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts()) + filterEdits = true, + filterTypes = true, + allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES, + buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts()) } private var timelineEvents = PublishRelay.create>() @@ -160,6 +156,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro override fun handle(action: RoomDetailAction) { when (action) { + is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action) is RoomDetailAction.SaveDraft -> handleSaveDraft(action) is RoomDetailAction.SendMessage -> handleSendMessage(action) is RoomDetailAction.SendMedia -> handleSendMedia(action) @@ -237,32 +234,41 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro copy( // Create a sendMode from a draft and retrieve the TimelineEvent sendMode = when (draft) { - is UserDraft.REGULAR -> SendMode.REGULAR(draft.text) - is UserDraft.QUOTE -> { - room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> - SendMode.QUOTE(timelineEvent, draft.text) - } - } - is UserDraft.REPLY -> { - room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> - SendMode.REPLY(timelineEvent, draft.text) - } - } - is UserDraft.EDIT -> { - room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> - SendMode.EDIT(timelineEvent, draft.text) - } - } - } ?: SendMode.REGULAR("") + is UserDraft.REGULAR -> SendMode.REGULAR(draft.text) + is UserDraft.QUOTE -> { + room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> + SendMode.QUOTE(timelineEvent, draft.text) + } + } + is UserDraft.REPLY -> { + room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> + SendMode.REPLY(timelineEvent, draft.text) + } + } + is UserDraft.EDIT -> { + room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> + SendMode.EDIT(timelineEvent, draft.text) + } + } + } ?: SendMode.REGULAR("") ) } } .disposeOnClear() } + private fun handleUserIsTyping(action: RoomDetailAction.UserIsTyping) { + if (vectorPreferences.sendTypingNotifs()) { + if (action.isTyping) { + room.userIsTyping() + } else { + room.userStopsTyping() + } + } + } + private fun handleTombstoneEvent(action: RoomDetailAction.HandleTombstoneEvent) { - val tombstoneContent = action.event.getClearContent().toModel() - ?: return + val tombstoneContent = action.event.getClearContent().toModel() ?: return val roomId = tombstoneContent.replacementRoom ?: "" val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN @@ -308,7 +314,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro fun isMenuItemVisible(@IdRes itemId: Int) = when (itemId) { R.id.clear_message_queue -> /* For now always disable on production, worker cancellation is not working properly */ - timeline.pendingEventCount() > 0 && BuildConfig.DEBUG + timeline.pendingEventCount() > 0 && vectorPreferences.developerMode() R.id.resend_all -> timeline.failedToDeliverEventCount() > 0 R.id.clear_all -> timeline.failedToDeliverEventCount() > 0 else -> false @@ -400,7 +406,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro is SendMode.EDIT -> { // is original event a reply? val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel()?.relatesTo?.inReplyTo?.eventId - ?: state.sendMode.timelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId + ?: state.sendMode.timelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId if (inReplyTo != null) { // TODO check if same content? room.getTimeLineEvent(inReplyTo)?.let { @@ -409,13 +415,13 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } else { val messageContent: MessageContent? = state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() - ?: state.sendMode.timelineEvent.root.getClearContent().toModel() + ?: state.sendMode.timelineEvent.root.getClearContent().toModel() val existingBody = messageContent?.body ?: "" if (existingBody != action.text) { room.editTextMessage(state.sendMode.timelineEvent.root.eventId ?: "", - messageContent?.type ?: MessageType.MSGTYPE_TEXT, - action.text, - action.autoMarkdown) + messageContent?.type ?: MessageType.MSGTYPE_TEXT, + action.text, + action.autoMarkdown) } else { Timber.w("Same message content, do not send edition") } @@ -426,7 +432,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro is SendMode.QUOTE -> { val messageContent: MessageContent? = state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() - ?: state.sendMode.timelineEvent.root.getClearContent().toModel() + ?: state.sendMode.timelineEvent.root.getClearContent().toModel() val textMsg = messageContent?.body val finalText = legacyRiotQuoteText(textMsg, action.text.toString()) @@ -542,7 +548,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) { null -> room.sendMedias(attachments) else -> _fileTooBigEvent.postValue(LiveEvent(FileTooBigError(tooBigFile.name - ?: tooBigFile.path, tooBigFile.size, maxUploadFileSize))) + ?: tooBigFile.path, tooBigFile.size, maxUploadFileSize))) } } } @@ -731,8 +737,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro .buffer(1, TimeUnit.SECONDS) .filter { it.isNotEmpty() } .subscribeBy(onNext = { actions -> - val bufferedMostRecentDisplayedEvent = actions.maxBy { it.event.displayIndex }?.event - ?: return@subscribeBy + val bufferedMostRecentDisplayedEvent = actions.maxBy { it.event.displayIndex }?.event ?: return@subscribeBy val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent if (trackUnreadMessages.get()) { if (globalMostRecentDisplayedEvent == null) { @@ -795,7 +800,14 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro room.rx().liveRoomSummary() .unwrap() .execute { async -> - copy(asyncRoomSummary = async) + val typingRoomMembers = + typingHelper.toTypingRoomMembers(async.invoke()?.typingRoomMemberIds.orEmpty(), room) + + copy( + asyncRoomSummary = async, + typingRoomMembers = typingRoomMembers, + typingMessage = typingHelper.toTypingMessage(typingRoomMembers) + ) } } @@ -882,6 +894,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro override fun onCleared() { timeline.dispose() timeline.removeAllListeners() + if (vectorPreferences.sendTypingNotifs()) { + room.userStopsTyping() + } super.onCleared() } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt index 588d398e7f..23b0dc16ca 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.user.model.User +import im.vector.matrix.android.api.util.MatrixItem /** * Describes the current send mode: @@ -44,7 +45,7 @@ sealed class SendMode(open val text: String) { sealed class UnreadState { object Unknown : UnreadState() object HasNoUnread : UnreadState() - data class ReadMarkerNotLoaded(val readMarkerId: String): UnreadState() + data class ReadMarkerNotLoaded(val readMarkerId: String) : UnreadState() data class HasUnread(val firstUnreadEventId: String) : UnreadState() } @@ -53,6 +54,8 @@ data class RoomDetailViewState( val eventId: String?, val asyncInviter: Async = Uninitialized, val asyncRoomSummary: Async = Uninitialized, + val typingRoomMembers: List? = null, + val typingMessage: String? = null, val sendMode: SendMode = SendMode.REGULAR(""), val tombstoneEvent: Event? = null, val tombstoneEventHandling: Async = Uninitialized, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt index 94d7812512..d9bed98b1f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt @@ -47,8 +47,8 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava fun create(event: TimelineEvent, highlight: Boolean, callback: TimelineEventController.Callback?, - exception: Exception? = null): DefaultItem { - val text = if (exception == null) { + throwable: Throwable? = null): DefaultItem { + val text = if (throwable == null) { "${event.root.getClearType()} events are not yet handled" } else { "an exception occurred when rendering the event ${event.root.eventId}" diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt deleted file mode 100644 index 3f234fcd3e..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.riotx.features.home.room.detail.timeline.factory - -import android.view.View -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.toModel -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent -import im.vector.riotx.R -import im.vector.riotx.core.resources.StringProvider -import im.vector.riotx.features.home.AvatarRenderer -import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController -import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider -import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData -import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem -import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_ -import javax.inject.Inject - -class EncryptionItemFactory @Inject constructor(private val stringProvider: StringProvider, - private val avatarRenderer: AvatarRenderer, - private val avatarSizeProvider: AvatarSizeProvider) { - - fun create(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback?): NoticeItem? { - val text = buildNoticeText(event.root, event.getDisambiguatedDisplayName()) ?: return null - val informationData = MessageInformationData( - eventId = event.root.eventId ?: "?", - senderId = event.root.senderId ?: "", - sendState = event.root.sendState, - avatarUrl = event.senderAvatar, - memberName = event.getDisambiguatedDisplayName(), - showInformation = false - ) - val attributes = NoticeItem.Attributes( - avatarRenderer = avatarRenderer, - informationData = informationData, - noticeText = text, - itemLongClickListener = View.OnLongClickListener { view -> - callback?.onEventLongClicked(informationData, null, view) ?: false - }, - readReceiptsCallback = callback - ) - return NoticeItem_() - .leftGuideline(avatarSizeProvider.leftGuideline) - .highlighted(highlight) - .attributes(attributes) - } - - private fun buildNoticeText(event: Event, senderName: String?): CharSequence? { - return when { - EventType.ENCRYPTION == event.getClearType() -> { - val content = event.content.toModel() ?: return null - stringProvider.getString(R.string.notice_end_to_end, senderName, content.algorithm) - } - else -> null - } - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 4a7a1e2a86..6d27cf7211 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -24,11 +24,12 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle import timber.log.Timber import javax.inject.Inject -class TimelineItemFactory @Inject constructor(private val messageItemFactory: MessageItemFactory, - private val encryptedItemFactory: EncryptedItemFactory, - private val noticeItemFactory: NoticeItemFactory, - private val defaultItemFactory: DefaultItemFactory, - private val roomCreateItemFactory: RoomCreateItemFactory) { +class TimelineItemFactory @Inject constructor( + private val messageItemFactory: MessageItemFactory, + private val encryptedItemFactory: EncryptedItemFactory, + private val noticeItemFactory: NoticeItemFactory, + private val defaultItemFactory: DefaultItemFactory, + private val roomCreateItemFactory: RoomCreateItemFactory) { fun create(event: TimelineEvent, nextEvent: TimelineEvent?, @@ -49,12 +50,13 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.STATE_ROOM_CANONICAL_ALIAS, EventType.STATE_ROOM_JOIN_RULES, EventType.STATE_ROOM_HISTORY_VISIBILITY, + EventType.STATE_ROOM_GUEST_ACCESS, EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_ANSWER, EventType.REACTION, EventType.REDACTION, - EventType.ENCRYPTION -> noticeItemFactory.create(event, highlight, callback) + EventType.STATE_ROOM_ENCRYPTION -> noticeItemFactory.create(event, highlight, callback) // State room create EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback) // Crypto @@ -74,9 +76,9 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me null } } - } catch (e: Exception) { - Timber.e(e, "failed to create message item") - defaultItemFactory.create(event, highlight, callback, e) + } catch (throwable: Throwable) { + Timber.e(throwable, "failed to create message item") + defaultItemFactory.create(event, highlight, callback, throwable) } return (computedModel ?: EmptyItem_()) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index a201890912..563a970cfb 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.* import im.vector.matrix.android.api.session.room.model.call.CallInviteContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.resources.StringProvider @@ -40,6 +41,8 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.getDisambiguatedDisplayName()) EventType.CALL_INVITE, EventType.CALL_HANGUP, @@ -166,6 +169,20 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active ?: sp.getString(R.string.notice_room_canonical_alias_unset, senderName) } + private fun formatRoomGuestAccessEvent(event: Event, senderName: String?): String? { + val eventContent: RoomGuestAccessContent? = event.getClearContent().toModel() + return when (eventContent?.guestAccess) { + GuestAccess.CanJoin -> sp.getString(R.string.notice_room_guest_access_can_join, senderName) + GuestAccess.Forbidden -> sp.getString(R.string.notice_room_guest_access_forbidden, senderName) + else -> null + } + } + + private fun formatRoomEncryptionEvent(event: Event, senderName: String?): CharSequence? { + val content = event.content.toModel() ?: return null + return sp.getString(R.string.notice_end_to_end, senderName, content.algorithm) + } + private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMemberContent?, prevEventContent: RoomMemberContent?): String { val displayText = StringBuilder() // Check display name has been changed diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index b0f3e617a6..1c9ff6dac5 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -34,7 +34,8 @@ object TimelineDisplayableEvents { EventType.CALL_HANGUP, EventType.CALL_ANSWER, EventType.ENCRYPTED, - EventType.ENCRYPTION, + EventType.STATE_ROOM_ENCRYPTION, + EventType.STATE_ROOM_GUEST_ACCESS, EventType.STATE_ROOM_THIRD_PARTY_INVITE, EventType.STICKER, EventType.STATE_ROOM_CREATE, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt index 23a0fd60a2..15e7a63b7b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt @@ -20,6 +20,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView +import androidx.core.view.isInvisible import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass @@ -27,6 +28,7 @@ import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyModel +import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.features.home.AvatarRenderer @EpoxyModelClass(layout = R.layout.item_room) @@ -36,6 +38,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var matrixItem: MatrixItem @EpoxyAttribute lateinit var lastFormattedEvent: CharSequence @EpoxyAttribute lateinit var lastEventTime: CharSequence + @EpoxyAttribute var typingString: CharSequence? = null @EpoxyAttribute var unreadNotificationCount: Int = 0 @EpoxyAttribute var hasUnreadMessage: Boolean = false @EpoxyAttribute var hasDraft: Boolean = false @@ -50,6 +53,8 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { holder.titleView.text = matrixItem.getBestName() holder.lastEventTimeView.text = lastEventTime holder.lastEventView.text = lastFormattedEvent + holder.typingView.setTextOrHide(typingString) + holder.lastEventView.isInvisible = holder.typingView.isVisible holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted)) holder.unreadIndentIndicator.isVisible = hasUnreadMessage holder.draftView.isVisible = hasDraft @@ -61,6 +66,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { val unreadCounterBadgeView by bind(R.id.roomUnreadCounterBadgeView) val unreadIndentIndicator by bind(R.id.roomUnreadIndicator) val lastEventView by bind(R.id.roomLastEventView) + val typingView by bind(R.id.roomTypingView) val draftView by bind(R.id.roomDraftBadge) val lastEventTimeView by bind(R.id.roomLastEventTimeView) val avatarImageView by bind(R.id.roomAvatarImageView) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt index 84a5f942e8..b5c0a77c38 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.home.room.list import android.view.View +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.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomSummary @@ -32,6 +33,7 @@ import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter +import im.vector.riotx.features.home.room.typing.TypingHelper import me.gujun.android.span.span import javax.inject.Inject @@ -39,6 +41,8 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte private val dateFormatter: VectorDateFormatter, private val colorProvider: ColorProvider, private val stringProvider: StringProvider, + private val typingHelper: TypingHelper, + private val session: Session, private val avatarRenderer: AvatarRenderer) { fun create(roomSummary: RoomSummary, @@ -121,11 +125,22 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte dateFormatter.formatMessageDay(date) } } + + val typingString = typingHelper.excludeCurrentUser(roomSummary.typingRoomMemberIds) + .takeIf { it.isNotEmpty() } + ?.let { typingMembers -> + // It's not ideal to get a Room and to fetch data from DB here, but let's keep it like this for the moment + val room = session.getRoom(roomSummary.roomId) + val typingRoomMembers = typingHelper.toTypingRoomMembers(typingMembers, room) + typingHelper.toTypingMessage(typingRoomMembers) + } + return RoomSummaryItem_() .id(roomSummary.roomId) .avatarRenderer(avatarRenderer) .matrixItem(roomSummary.toMatrixItem()) .lastEventTime(latestEventTime) + .typingString(typingString) .lastFormattedEvent(latestFormattedEvent) .showHighlighted(showHighlighted) .unreadNotificationCount(unreadCount) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/typing/TypingHelper.kt b/vector/src/main/java/im/vector/riotx/features/home/room/typing/TypingHelper.kt new file mode 100644 index 0000000000..e3dc3dc7ee --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/typing/TypingHelper.kt @@ -0,0 +1,69 @@ +/* + * Copyright 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.riotx.features.home.room.typing + +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.room.members.MembershipService +import im.vector.matrix.android.api.util.MatrixItem +import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.riotx.R +import im.vector.riotx.core.resources.StringProvider +import javax.inject.Inject + +class TypingHelper @Inject constructor( + private val session: Session, + private val stringProvider: StringProvider +) { + /** + * Exclude current user from the list of typing users + */ + fun excludeCurrentUser( + typingUserIds: List + ): List { + return typingUserIds + .filter { it != session.myUserId } + } + + /** + * Convert a list of userId to a list of maximum 3 UserItems + */ + fun toTypingRoomMembers( + typingUserIds: List, + membershipService: MembershipService? + ): List { + return excludeCurrentUser(typingUserIds) + .take(3) + .mapNotNull { membershipService?.getRoomMember(it) } + .map { it.toMatrixItem() } + } + + /** + * Convert a list of typing UserItems to a human readable String + */ + fun toTypingMessage(typingUserItems: List): String? { + return when { + typingUserItems.isEmpty() -> + null + typingUserItems.size == 1 -> + stringProvider.getString(R.string.room_one_user_is_typing, typingUserItems[0].getBestName()) + typingUserItems.size == 2 -> + stringProvider.getString(R.string.room_two_users_are_typing, typingUserItems[0].getBestName(), typingUserItems[1].getBestName()) + else -> + stringProvider.getString(R.string.room_many_users_are_typing, typingUserItems[0].getBestName(), typingUserItems[1].getBestName()) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index 018f8a0702..9268cc8390 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -26,19 +26,20 @@ import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.error.fatalError import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.utils.toast +import im.vector.riotx.features.createdirect.CreateDirectRoomActivity import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupActivity import im.vector.riotx.features.debug.DebugMenuActivity -import im.vector.riotx.features.createdirect.CreateDirectRoomActivity import im.vector.riotx.features.home.room.detail.RoomDetailActivity import im.vector.riotx.features.home.room.detail.RoomDetailArgs import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity -import im.vector.riotx.features.roommemberprofile.RoomMemberProfileActivity -import im.vector.riotx.features.roommemberprofile.RoomMemberProfileArgs import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewActivity +import im.vector.riotx.features.roommemberprofile.RoomMemberProfileActivity +import im.vector.riotx.features.roommemberprofile.RoomMemberProfileArgs import im.vector.riotx.features.roomprofile.RoomProfileActivity +import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorSettingsActivity import im.vector.riotx.features.share.SharedData import javax.inject.Inject @@ -46,12 +47,13 @@ import javax.inject.Singleton @Singleton class DefaultNavigator @Inject constructor( - private val sessionHolder: ActiveSessionHolder + private val sessionHolder: ActiveSessionHolder, + private val vectorPreferences: VectorPreferences ) : Navigator { override fun openRoom(context: Context, roomId: String, eventId: String?, buildTask: Boolean) { if (sessionHolder.getSafeActiveSession()?.getRoom(roomId) == null) { - fatalError("Trying to open an unknown room $roomId") + fatalError("Trying to open an unknown room $roomId", vectorPreferences.failFast()) return } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryListCreator.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryListCreator.kt index 4073929a4f..0b02101ecb 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryListCreator.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryListCreator.kt @@ -26,13 +26,11 @@ import javax.inject.Inject class RoomDirectoryListCreator @Inject constructor(private val stringArrayProvider: StringArrayProvider, private val session: Session) { - private val credentials = session.sessionParams.credentials - fun computeDirectories(thirdPartyProtocolData: Map): List { val result = ArrayList() // Add user homeserver name - val userHsName = credentials.userId.substring(credentials.userId.indexOf(":") + 1) + val userHsName = session.myUserId.substringAfter(":") result.add(RoomDirectoryData( displayName = userHsName, diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index 72f8cf01dd..c734558c0e 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -24,6 +24,7 @@ import android.provider.MediaStore import androidx.core.content.edit import androidx.preference.PreferenceManager import com.squareup.seismic.ShakeDetector +import im.vector.riotx.BuildConfig import im.vector.riotx.R import im.vector.riotx.features.homeserver.ServerUrlsRepository import im.vector.riotx.features.themes.ThemeUtils @@ -268,7 +269,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } fun failFast(): Boolean { - return developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false) + return BuildConfig.DEBUG || (developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false)) } /** diff --git a/vector/src/main/res/drawable/bg_breadcrumbs_typing.xml b/vector/src/main/res/drawable/bg_breadcrumbs_typing.xml new file mode 100644 index 0000000000..47a095c8ba --- /dev/null +++ b/vector/src/main/res/drawable/bg_breadcrumbs_typing.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_breadcrumbs.xml b/vector/src/main/res/layout/item_breadcrumbs.xml index a7a312f16b..6596f71b40 100644 --- a/vector/src/main/res/layout/item_breadcrumbs.xml +++ b/vector/src/main/res/layout/item_breadcrumbs.xml @@ -53,6 +53,23 @@ tools:text="24" tools:visibility="visible" /> + + diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml index 741bd47069..3c9e40fae5 100644 --- a/vector/src/main/res/layout/item_room.xml +++ b/vector/src/main/res/layout/item_room.xml @@ -15,11 +15,11 @@ android:layout_width="4dp" android:layout_height="0dp" android:background="?attr/colorAccent" + android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - android:visibility="gone" - tools:visibility="visible"/> + tools:visibility="visible" /> + + + \ No newline at end of file diff --git a/vector/src/main/res/values-bg/strings.xml b/vector/src/main/res/values-bg/strings.xml index e98bcd1a5f..eee8e5d153 100644 --- a/vector/src/main/res/values-bg/strings.xml +++ b/vector/src/main/res/values-bg/strings.xml @@ -1558,7 +1558,7 @@ \n \nRiotX поддържа: • Вход в съществуващ акаунт • Създаване на стая и влизане в публични стаи • Приемане и отхвърляне на покани • Показване на списък със стаите • Преглеждане на информация за стая • Изпращане на текстови съобщения • Изпращане на прикачени файлове • Четене и писане на съобщения в шифровани стаи • Шифроване: резервни копия на E2E ключове, потвърждение на устройства, заявяване и отговаряне на заявки за споделяне на ключове • Уведомления • Светла, Тъмна и Черна тема \n -\nЗасега не всички функции на Riot са налични в RiotX. Основни липсващи (и скоро пристигащи!) функции са: • Създаване на нов профил • Настройки на стаи (показване на членове и т.н.) • Обаждания • Приспособления • … +\nЗасега не всички функции на Riot са налични в RiotX. Основни липсващи (и скоро пристигащи!) функции са: • Настройки на стаи (показване на членове и т.н.) • Обаждания • Приспособления • … Директни съобщения @@ -1759,4 +1759,11 @@ В момента няма връзка с мрежата + Това не е валиден адрес на Matrix сървър + Потвърдете паролата + Не може да направите това от мобилно приложение на Riot + Нужна е автентикация + + + Интеграции diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 55fa5d1488..a3dee0eb0b 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -998,8 +998,8 @@ Beachte: Diese Aktion wird die App neu starten und einige Zeit brauchen.%dh - 1d - %dd + 1T + %dT Jetzt %1$s @@ -1654,7 +1654,7 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Ablehnen - app_id: + App-ID: Überprüfung Keine Widerrufen @@ -1687,8 +1687,8 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Sie versuchen anscheinend, eine Verbindung zu einem anderen Homeserver herzustellen. Möchten Sie sich abmelden\? - push_key: - app_display_name: + Push-Key: + App-Anzeigename: Url: Nutzungsbedingungen Nutzungsbedingungen überprüfen @@ -1710,4 +1710,84 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Konnte keine Verbindung zum Heimserver herstellen Latn + Bitte frage den Administrator deines Home-Servers (%1$s) um einen TURN server einzurichten, damit Anrufe zuverlässig funktionieren. +\n +\nAlternativ kannst du einen öffentlichen Server auf %2$s nutzen doch wird das nicht zu zuverlässig sein und es wird deine IP-Adresse mit dem Server teilen. Du kannst dies auch in den Einstellungen konfigurieren. + Dies ist keine Adresse eines Matrixservers + Kann Home-Server nicht bei dieser URL erreichen. Bitte überprüfen + Wir nutzen %s als Assistenten wenn dein Home-Server keinen anbietet (Deine IP-Adresse wird während des Anrufs geteilt) + Führe einen Identitätsserver in deinen Einstellungen hinzu um diese Aktion auszuführen. + Passwort bestätigen + Du kannst dies nicht auf einem mobilen Riot tun + Authentifizierung benötigt + + + Hintergrundsynchronisierungsmodus (experimentell) + Riot wird sich im Hintergrund auf eine Art synchronisieren die die Ressourcen des Geräts schont (Akku). +\nAbhängig von dem Ressourcen-Statuses deines Geräts kann dein System die Synchronisierung verschieben. + Riot wird sich im Hintergrund periodisch zu einem bestimmten Zeitpunkt synchronisieren (konfigurierbar). +\nDies wird Funk- und Akkunutzung beeinflussen. Es wird wird eine permanente Benachrichtigung geben, die sagt, dass Riot auf Ereignisse lauscht. + %s +\nDie Synchronisierung kann aufgrund deiner Ressourcen (Akku) oder Gerätezustands (schlafend) verschoben werden. + Integrationen + Benutze einen Integrations-Manager um Bots, Brpcken, Widgets und Sticker-Pakete zu verwalten. +\nIntegrations-Manager erhalten Konfigurationsdaten und können Widgets verändern, Raum-Einladungen senden und in deinem Namen Berechtigungslevel setzen. + Erlaube Integrationen + Widget + Widget laden + Dieses Widget wurde hinzugefügt von: + Konnte Widget nicht laden. +\n%s + Widget erneut laden + Im Browser öffnen + Dein Anzeigename + Deine Profilbild-Adresse + Deine Benutzer-ID + Dein Design + Widget-ID + Raum-ID + + + Dieses Widget möchte folgende Ressourcen benutzen: + Erlauben + Alle blockieren + Kamera benutzen + Mikrofon benutzen + Lese DRM-geschützte Medien + + Frühere Versionen von Riot hatten einen Sicherheitsproblem, welches dem Identitätsserver (%1$s) Zugriff auf deinen Account geben konnte. Wenn du %2$s vertraust, kannst du dies ignorieren – ansonsten logge dich bitte aus und wieder ein. +\n +\nWeitere Details gibt es hier (Englisch): +\nhttps://medium.com/@RiotChat/36b4792ea0d6 + + Du wirst nicht über eingehende Nachrichten benachrichtigt, wenn die App im Hintergrund ist. + Verwalte deine Erkennungseinstellungen. + Zugriff für mich zurückziehen + + Gerätename: + Format: + + RiotX ist ein neuer Client für das Matrix-Protokoll (matrix.org): Ein offenes Netzwerk für sichere, dezentrale Kommunikation. +\nRiotX ist der Riot-Android-Client der auf dem matrix-android-sdk basiert - aber beides komplett neu geschrieben. +\n +\nHinweis: Dies ist eine Beta-Version. RiotX wird aktuell aktiv entwickelt und enthält Einschränkungen und (wir hoffen nicht zu viele) Fehler. Jede Rückmeldung ist willkommen! +\n +\nRiotX unterstützt: • Anmelden an ein existierendes Konto • Erstelle Räume und trete öffentlichen Räumen bei • Akzeptiere und lehne Einladungen ab • Zeige Raum-Details • Sende Textnachrichten • Sende Anhänge • Lese und Schreibe Nachrichten in verschlüsselten Räumen • Verschlüsselung: Backup der Ende-zu-Ende-Schlüssel, erweiterte Geräteverifizierung, Schlüsseltauschanfragen und -antworten • Push-Benachrichtigungen • Helles, dunkles und schwarzes Thema +\n +\nNicht alle Features in Riot sind bisher in RiotX implementiert. Hauptfunktionen, die noch fehlen (und bald kommen!): • Raum-Einstellungen (Raum-Mitglieder auflisten, etc.) • Anrufe • Widgets • … + + Du nutzt aktuell %1$s um zu entdecken und von dir bekannten Kontakten entdeckt zu werden. + Du benutzt aktuell keinen Identitätsserver. Um zu entdecken und um von dir bekannten Kontakten entdeckt zu werden, richte unten einen ein. + Entdeckbare Telefonnummern + Bitte gebe Adresse des Identitätsserver ein + Identitätsserver hat keine Nutzungsbedingungen + Der Identitätsserver den du ausgewählt hast, hat keine Nutzungsbedingungen. Fahre nur fort, wenn du dem Besitzer des Dienstes vertraust + Eine Textnachricht wurde an %s gesendet. Bitte gebe den Verifizierungscode ein, den sie enthält. + + Aktiviere gesprächige Logs. + Gesprächige Logs wird den Entwicklern helfen indem sie mehr Informationen enthalten, wenn du einen Fehlerbericht sendest. Auch wenn dies aktiviert ist, werden keine Nachrichteninhalte oder andere privaten Daten aufgezeichnet. + + + Bitte erneut versuchen, nachdem du die Nutzungsbedingungendeines Home-Servers akzeptiert hast. + diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index 046e5557f5..8915d0e471 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -1564,7 +1564,7 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. \n \nRiotX bezeroak honakoa ahalbidetzen du: • Badagoen kontu batean saioa hasi • Gelak sortu eta gela publikoetara elkartu • Gonbidapenak onartu edo ukatu • Erabiltzailearen gelak zerrendatu • Gelaren xehetasunak ikusi • Testuzko mezuak bidali • Eranskinak bidali • Zifratutako geletan mezuak irakurri eta idatzi • Zifratzea: E2Egakoen babeskopia, gailuaren egiaztaketa aurreratua, gakoa partekatzeko eskaria eta erantzuna • Push jakinarazpena • Gai argia, iluna eta beltza \n -\nEz dira oraindik Riot bezeroaren ezaugarri guztiak ezarri RiotX bezeroan. Falta diren (eta laster etorriko direnen) artean nabarmenak dira: • Kontua sortzea • Gelaren ezarpenak (gelako kideak zerrendatzea, eta abar.) • Deiak • Trepetak • … +\nEz dira oraindik Riot bezeroaren ezaugarri guztiak ezarri RiotX bezeroan. Falta diren (eta laster etorriko direnen) artean nabarmenak dira: • Gelaren ezarpenak (gelako kideak zerrendatzea, eta abar.) • Deiak • Trepetak • … app_display_name: Mezu zuzenak @@ -1741,7 +1741,7 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. Spama da Desegokia da - Salaketa pertsonalizatua + Salaketa pertsonalizatua… Salatu eduki hau Eduki hau salatzeko arrazoia SALATU @@ -1801,4 +1801,180 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. Mikrofonoa erabili DRM bidez babestutako multimedia irakurri + Hau ez da baliozko Matrix zerbitzari helbide bat + Blokeatu erabiltzailea + + Mezu guztiak (ozen) + Mezu guztiak + Aipamenak besterik ez + Mututu + Ezarpenak + Atera gelatik + %1$s erabiltzaileak ez du aldaketarik egin + Emandako mezua izorraki gisa bidaltzen du + Izorrakia + Idatzi hitz gakoak erreakzio bat aurkitzeko. + + Ez duzu erabiltzailerik ezikusten + + Sakatu luze gela batean aukera gehiago ikusteko + + + %1$s erabiltzaileak gela publikoa bihurtu du esteka dakien edonorentzat. + %1$s erabiltzaileak gela soilik gonbidatuentzat bihurtu du. + Irakurri gabeko mezuak + + Askatu zure komunikazioa + Txateatu beste taldeetako jendearekin + Mantendu elkarrizketak pribatu zifratzearekin + Hedatu eta pertsonalizatu zure esperientzia + Hasi + + Hautatu zerbitzari bat + E-mailarekin bezala, kontuek etxe bat dute, baina edonorekin hitz egin dezakezu + Elkartu milioiekin aske zerbitzari publiko handienean + Ordainpeko ostatatzea elkarteentzat + Ikasi gehiago + Beste bat + Ezarpen pertsonalizatu eta aurreratuak + + Jarraitu + Konektatu %1$s zerbitzarira + Konektatu Modular-era + Konektatu zerbitzari pertsonalizatu batera + Hasi saioa %1$s zerbitzarian + Erregistratu + Hasi saioa + Jarraitu SSO-rekin + + Modular helbidea + Helbidea + Ordainpeko ostatatzea elkarteentzat + Sartu erabili nahi duzun Modular Riot edo zerbitzariaren helbidea + Sartu konektatu nahi duzun zerbitzari edo Riot-aren helbidea + + Errore bat gertatu da orria kargatzean: %1$s (%2$d) + Aplikazioak ezin du hasiera-zerbitzari honetan saioa hasi. Hasiera-zerbitzariak honako saio mota onartzen du: %1$s. +\n +\nWeb bezero batekin hasi nahi duzu saioa\? + Sentitzen dugu, zerbitzari honek ez ditu kontu berriak onartzen. + Aplikazioak ezin du kontu berri bat sortu hasiera-zerbitzari honetan. +\n +\nWeb bezero bat erabiliz erregistratu nahi duzu\? + + E-mail hau ez dago kontu batera lotuta. + + Berrezarri %1$s zerbitzariko pasahitza + Egiaztaketa e-mail bat bidaliko zaizu zure pasahitz berriaren ezarpena baieztatzeko. + Hurrengoa + E-mail + Pasahitz berria + + Abisua! + Zure pasahitza aldatzeak zure gailu guztietako muturretik-muturrerako zifratzerako gakoak berrezarriko ditu, eta aurretik zifratutako mezuen historiala ezin izango da irakurri. Ezarri gakoen babes-kopia edo esportatu zure geletako gakoak beste gailu batetik pasahitza aldatu aurretik. + Jarraitu + + E-mail hau ez dago kontu batera lotuta + + Egiaztatu zure sarrera ontzia + Egiaztaketa e-mail bat bidali da %1$s helbidera. + Sakatu estekan zure pasahitz berria baieztatzeko. Behin dakarren esteka jarraitu duzula, sakatu hemen azpian. + Nire e-mail helbidea baieztatu dut + + Ongi! + Zure pasahitza berrezarri da. + Gailu guztietan saioa amaitu duzu eta ez duzu push jakinarazpenik jasoko. Jakinarazpenak berriro aktibatzeko, hasi saioa gailuetan. + Itzuli saio hasierara + + Abisua + Zure pasahitza ez da oraindik aldatu. +\n +\nUtzi pasahitza aldatzeko prozesua\? + + Ezarri e-mail helbidea + Ezarri e-mail bat zure kontua berreskuratzeko. Geroago, nahiez gero jendeak zure e-mail helbidearen bidez zu aurkitzea ahalbidetu dezakezu. + E-mail + E-mail (aukerakoa) + Hurrengoa + + Ezarri telefono zenbakia + Ezarri aukeran telefono zenbakia, honen bidez jendeak zu aurkitzea ahalbidetzeko. + Erabili formatu internazionala. + Telefono zenbakia + Telefono zenbakia (aukerakoa) + Hurrengoa + + Baieztatu telefono zenbakia + Kode bat bidali dugu %1$s zenbakira. Sartu hemen azpian zu zarela baieztatzeko. + Sartu kodea + Bidali berriro + Hurrengoa + + Telefono zenbaki internazionalak \'+\' batekin hasten dira + Telefono zenbakia baliogabea dirudi. Egiaztatu ezazu + + Erregistratu %1$s zerbitzarian + Erabiltzaile-izena edo e-maila + Pasahitza + Hurrengoa + Erabiltzaile-izen hori hartuta dago + Abisua + Zure kontua ez da oraindik sortu. +\n +\nUtzi erregistratze prozesua\? + + Hautatu matrix.org + Hautatu modular + Hautatu hasiera-zerbitzari pertsonalizatu bat + Bete captcha erronka + Onartu baldintzak jarraitzeko + + Egiaztatu zure e-maila + E-mail bat bidali dizugu %1$s helbidera. +\nSakatu dakarren esteka kontuaren sorrerarekin jarraitzeko. + Sartutako kodea ez da zuzena. Egiaztatu ezazu. + Zaharkitutako hasiera-zerbitzaria + Hasiera-zerbitzari honek konektatzeko zaharregia den bertsio bat darabil. Eskatu administratzaileari eguneratu dezala. + + + Eskaera gehiegi bidali dira. Segundo 1$d barru saiatu zaitezke berriro… + Eskaera gehiegi bidali dira. 1$d segundo barru saiatu zaitezke berriro… + + + Hauek ikusia + + Saioa amaitu duzu + Hainbat arrazoiengatik izan daiteke: +\n +\n• Pasahitza aldatu duzu beste gailu batean. +\n +\n• Gailu hau ezabatu duzu beste gailu batetik. +\n +\n• Zure zerbitzariko administratzaileak zure sarbidea baliogabetu du segurtasun arrazoiengatik. + Hasi saioa berriro + + Saioa amaitu duzu + Hasi saioa + Zure hasiera zerbitzariaren administratzaileak (%1$s) zure %2$s kontuaren saioa amaitu du (%3$s). + Hasi saioa gailu honetan besterik gorde ez diren zifratze gakoak berreskuratzeko. Zure mezu seguruak beste gailuetan irakurri ahal izateko behar dituzu. + Hasi saioa + Pasahitza + Garbitu datu pertsonalak + Abisua: Zure datu pertsonalak (zure zifratze gakoak barne) gailu honetan daude oraindik. +\n +\nGarbitu ezazu gailu hau erabiltzen bukatu duzunean, edo beste kontu batekin saioa hasi nahi duzunean. + Garbitu datu guztiak + + Garbitu datuak + Gailu honetan gordetako datu guztiak ezabatu\? +\n +\nHasi saioa berriro zure kontuaren datuak eta mezuak atzitzeko. + Zure mezu zifratuetara sarbidea galduko duzu ez baduzu saioa hasten zifratze gakoak berreskuratzeko. + Garbitu datuak + Oraingo saioa %1$s erabiltzailearena da eta %2$s erabiltzailearen kredentzialak eman dituzu. RiotX-k ez du hau onartzen. +\nAurretik garbitu datuak, gero hasi saioa berriro beste kontu batekin. + + Zure matrix.to esteka gaizki osatua dago + Deskripzio hau laburregia da + diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index ea8a6abc5a..309e1a0756 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -1801,7 +1801,7 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös \n \nRiotX tukee: • Kirjaudu olemassaolevalle tunnukselle • Luo huoneita ja liity julkisiin huoneisiin • Hyväksy ja hylkää kutsuja • Listaa käyttäjän huoneet • Katso huoneen tietoja • Lähetä tekstiviestejä • Lähetä liitteitä • Lue ja kirjoita viestejä salatuissa huoneissa • Salaus: osapuolten välisen salauksen avaimien varmuuskopiointi, edistynyt laitteiden varmennus, avainten jakopyynnöt ja vastaus • Viesti-ilmoitukset • Vaalea, tumma ja musta teema \n -\nKaikkia Riotin ominaisuuksia ei ole vielä toteutettu RiotX:ssä. Tärkeimmät puuttuvat (ja pian saapuvat!) ominaisuudet: • Tunnusten luonti • Huoneen asetukset (listaa huoneen jäsenet jne.) • Puhelut • Sovelmat • … +\nKaikkia Riotin ominaisuuksia ei ole vielä toteutettu RiotX:ssä. Tärkeimmät puuttuvat (ja pian saapuvat!) ominaisuudet: • Huoneen asetukset (listaa huoneen jäsenet jne.) • Puhelut • Sovelmat • … Salataan pikkukuvaa… Lähetetään pikkukuvaa (%1$s / %2$s) @@ -1828,7 +1828,7 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös Ääni Jakotiedon käsittely epäonnistui - Muokattu ilmianto + Muokattu ilmianto… Ilmianna tämä sisältö Sisällön ilmiannon syy ILMIANNA @@ -1851,4 +1851,179 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös \n \nSalli pääsy tiedostoihin seuraavassa ponnahdusikkunassa, jotta voit viedä avaimesi käsin. + Tämä ei ole kelvollinen Matrix-palvelimen osoite + Estä käyttäjä + + Kaikki viestit (äänekäs) + Kaikki viestit + Vain maininnat + Vaimenna + Asetukset + Poistu huoneesta + %1$s ei tehnyt muutoksia + Lähettää annetun viestin ilonpilaajana + Ilonpilaaja + Syötä avainsanat löytääksesi reaktion. + + Et jätä yhtään käyttäjää huomiotta + + Tee pitkä klikkaus huoneelle nähdäksesi lisää asetuksia + + + %1$s asetti huoneen julkiseksi kelle tahansa, joka tietää huoneen osoitteen. + %1$s muutti huoneeseen liittymisen vaatimaan kutsua. + Lukemattomia viestejä + + Vapauta keskustelusi + Keskustele kaksistaan tai ryhmissä + Pidä keskustelut yksityisinä ja salattuina + Laajenna ja muokkaa kokemustasi + Aloita + + Valitse palvelin + Kuten sähköpostissa, tunnuksilla on yksi koti, mutta voit keskustella koko maailman kanssa + Liity miljoonien joukkoon suurimmalla julkisella palvelimella + Korkealuokkaista isännöintiä organisaatioille + Lue lisää + Muut + Mukautetut ja monimutkaiset asetukset + + Jatka + Yhdistä palvelimeen %1$s + Yhdistä Modulariin + Yhdistä itse määritettyyn palvelimeen + Kirjaudu sisään palvelimeen %1$s + Rekisteröidy + Kirjaudu sisään + Jatka kertakirjautumiseen + + Modularin osoite + Osoite + Korkealuokkaista isännöintiä organisaatioille + Syötä Modular Riotin tai haluamasi palvelimen osoite + Sivun lataamisessa tapahtui virhe: %1$s (%2$d) + Sovellus ei pysty kirjautumaan sisään tälle kotipalvelimelle. Tämä kotipalvelin tukee seuraavia kirjautumistyyppejä: %1$s. +\n +\nHaluatko kirjautua sisään web-klientillä\? + Valitettavasti tämä palvelin ei hyväksy uusia tunnuksia. + Sovellus ei pysty luomaan uusia tunnuksia tälle kotipalvelimelle. +\n +\nHaluatko rekisteröityä web-klientillä\? + + Tämä sähköpostiosoite ei ole liitettynä mihinkään tunnukseen. + + Palauta salasana palvelimella %1$s + Sähköpostiisi lähetetään viesti uuden salananan asettamiseksi. + Seuraava + Sähköposti + Uusi salasana + + Varoitus! + Salasanan vaihtaminen nollaa kaikki osapuolten välisen salauksen avaimet kaikilla laitteillasi, joka estää sinua lukemasta vanhoja viestejä. Ota käyttöön avainten varmuuskopiointi tai vie huoneen avaimet toiselta laitteelta ennen kuin vaihdat salasanasi. + Jatka + + Tämä sähköposti ei ole liitettynä mihinkään tunnukseen + + Tarkista sähköpostisi + Vahvistusviesti lähetettiin osoitteeseen %1$s. + Näpäytä linkkiä vahvistaaksesi uuden salasanasi. Seurattuasi siinä olevaa linkkiä, klikkaa alapuolelta. + Olen vahvistanut sähköpostiosoitteeni + + Valmis! + Salasanasi on vaihdettu. + Olet kirjautunut ulos kaikilta laitteilta, etkä saa enää viesti-ilmoituksia. Ottaaksesi viesti-ilmoitukset uudelleen käyttöön, kirjaudu sisään jokaisella laitteellasi. + Takaisin sisäänkirjautumiseen + + Varoitus + Salasanaasi ei ole vielä vaihdettu. +\n +\nPeru salasananvaihtoprosessi\? + + Aseta sähköpostiosoite + Aseta sähköpostiosoite palauttaaksesi tunnuksesi. Myöhemmin, voit antaa muiden löytää sinut sähköpostillasi. + Sähköposti + Sähköposti (vapaaehtoinen) + Seuraava + + Aseta puhelinnumero + Aseta puhelinnumero antaaksesi muiden löytää sinut puhellinumerosi perusteella. + Käytä maailmanlaajuista puhelinnumeron muotoa. + Puhelinnumero + Puhelinnumero (vapaaehtoinen) + Seuraava + + Vahvista puhelinnumero + Lähetimme sinulle koodin numeroon %1$s. Syötä se alapuolelle vahvistaaksesi numeron. + Syötä koodi + Lähetä uudelleen + Seuraava + + Kansainvälisten puhelinnumeroiden pitää alkaa merkillä ”+” + Puhelinnumero vaikuttaa epäkelvolta. Tarkista numero + + Rekisteröidy palvelimelle %1$s + Käyttäjätunnus tai sähköpostiosoite + Salasana + Seuraava + Käyttäjätunnus on varattu + Varoitus + Tunnustasi ei ole vielä luotu. +\n +\nPeru rekisteröintiprosessi\? + + Valitse matrix.org + Valitse modular + Valitse muu kotipalvelin + Ratkaise seuraava kuvavarmennushaaste + Hyväksy ehdot jatkaaksesi + + Tarkista sähköpostisi + Lähetimme sähköpostin osoitteeseen %1$s. +\nKlikkaa siinä olevaa linkkiä jatkaaksesi tunnuksen luontia. + Syöttämäsi koodi ei ole kelvollinen. Tarkista se. + Vanhentunut kotipalvelin + Tämä kotipalvelin pyörii liian vanhalla versiolla, jotta pystyisimme yhdistämään siihen. Pyydä kotipalvelimesi ylläpitäjää päivittämään palvelimensa. + + + Liian monta pyyntöä lähetettiin. Voit yrittää uudelleen 1 sekunnissa… + Liian monta pyyntöä lähetettiin. Voit yrittää uudelleen %1$d sekunnissa… + + + Nähty toimesta + + Olet kirjautunut ulos + Kirjaudu sisään uudelleen + + Olet kirjautunut ulos + Kirjaudu sisään + Kotipalvelimen (%1$s) ylläpitäjä on kirjannut sinut ulos tunnukseltasi %2$s (%3$s). + Kirjaudu sisään + Salasana + Poista henkilökohtaiset tiedot + Varoitus: henkilökohtaiset tietosi (sisältää salausavaimesi) ovat vielä tallennettuna tälle laitteelle. +\n +\nPoista ne jos olet lopettanut tämän laitteen käytön, tai haluat kirjautua sisään toiselle tunnukselle. + Poista kaikki tiedot + + Poista tiedot + Poista kaikki tälle laitteelle tallennetut tiedot\? +\nKirjaudu sisään päästäksesi käsiksi tunnuksesi tietoihin ja viesteihin. + Menetät pääsyn salattuihin viesteihisi ellet kirjaudu sisään palauttaaksesi salausavaimesi. + Poista tiedot + Nykyinen istunto on käyttäjälle %1$s, ja yritit kirjautuas isään käyttäjälle %2$s. RiotX ei tue tätä. +\nPoista ensin tietosi ja kirjaudu sen jälkeen toisella tunnuksella. Voit vaihtoehtoisesti käyttää Riotin selainversiota. + + matrix.to-linkkisi oli epämuodostunut + Kuvaus on liian lyhyt + + Syötä palvelin tai sen Riotin osoite, mihin haluat yhdistää + + Se voi johtua monesta eri syystä: +\n +\n• olet vaihtanut salasanasi toisella laitteella +\n +\n• olet poistanut tämän laitteen toisella laitteella +\n +\n• palvelimen ylläpitäjä on estänyt pääsysi turvallisuussyistä. + Kirjaudu sisään palauttaaksesi salausavaimesi, jotka ovat tallessa vain tällä laitteella. Tarvitset niitä lukeaksi kaikki salatut viestisi millä tahansa laitteella. diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index cc81f595d2..124f4eabc5 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -1570,7 +1570,7 @@ Si vous n’avez pas configuré de nouvelle méthode de récupération, un attaq \n \nRiotX prend en charge : • Se connecter à un compte existant • Créer de salons et rejoindre des salons publics • Accepter et refuser des invitations • Lister les salons des utilisateurs • Voir les informations des salons • Envoyer des messages texte • Envoyer des pièces jointes • Lire et écrire des messages dans les salons chiffrés • Chiffrement : sauvegarde des clés de chiffrement, vérification avancée des appareils, demande et réponse de partage de clé • Notifications • Thèmes clair, sombre et noir \n -\nToutes les fonctionnalités de Riot ne sont pas encore implémentées dans RiotX. Principales fonctionnalités manquantes (et qui arrivent bientôt !) : • Création de compte • Réglages des salons (lister les membres du salon etc.) • Appels • Widgets • … +\nToutes les fonctionnalités de Riot ne sont pas encore implémentées dans RiotX. Principales fonctionnalités manquantes (et qui arrivent bientôt !) : • Réglages des salons (lister les membres du salon etc.) • Appels • Widgets • … Messages directs @@ -1745,7 +1745,7 @@ Si vous n’avez pas configuré de nouvelle méthode de récupération, un attaq C’est du pourriel C’est inapproprié - Signalement personnalisé + Signalement personnalisé… Signaler ce contenu Motif de signalement de ce contenu SIGNALER @@ -1805,4 +1805,179 @@ Si vous n’avez pas configuré de nouvelle méthode de récupération, un attaq Utiliser le micro Lire des médias protégés par des DRM + Ce n’est pas une adresse de serveur Matrix valide + Bloquer l’utilisateur + + Tous les messages (sonore) + Tous les messages + Seulement les mentions + Sourdine + Paramètres + Quitter le salon + %1$s n’a effectué aucun changement + Envoie le message fourni comme un spoiler + Spoiler + Saisir des mots-clés pour trouver une réaction. + + Vous n’ignorez aucun utilisateur + + Clic long sur un salon pour voir plus d’options + + + %1$s a rendu le salon public à tous ceux qui en connaissent le lien. + %1$s a rendu le salon accessible uniquement par invitation. + Messages non lus + + Libérez votre communication + Discutez directement avec des personnes ou avec des groupes + Gardez vos conversations privées avec le chiffrement + Étendez et personnalisez votre expérience + Démarrer + + Sélectionner un serveur + Comme les e-mails, les comptes ont une maison, même si vous pouvez parler à n’importe qui + Rejoignez des millions de personnes gratuitement sur le plus grand serveur public + Hébergement premium pour les organisations + En savoir plus + Autre + Paramètres personnalisés et avancés + + Continuer + Se connecter à %1$s + Se connecter à Modular + Se connecter à un serveur personnalisé + S’authentifier sur %1$s + S’inscrire + S’authentifier + Continuer avec l’authentification unique + + Adresse Modular + Adresse + Hébergement privé pour les organisations + Saisir l’adresse de Riot ou du serveur de Modular que vous voulez utiliser + Saisir l’adresse d’un serveur ou d’un Riot auquel vous voulez vous connecter + + Une erreur est survenue pendant le chargement de la page : %1$s (%2$d) + L’application ne peut pas s’authentifier sur ce serveur d’accueil. Le serveur d’accueil prend en charge le(s) type(s) d’authentification suivant(s) : %1$s. +\n +\nVoulez-vous vous connecter en utilisant un client web \? + Désolé, ce serveur n’accepte pas de nouveau compte. + L’application ne peut pas créer de compte sur ce serveur d’accueil. +\n +\nVoulez-vous vous inscrire en utilisant un client web \? + + Cet e-mail n’est associé à aucun compte. + + Réinitialiser le mot de passe sur %1$s + Un e-mail de vérification sera envoyé à votre adresse pour confirmer la configuration de votre nouveau mot de passe. + Suivant + E-mail + Nouveau mot de passe + + Attention ! + Le changement de mot de passe réinitialisera toutes les clés de chiffrement sur tous vos appareils, rendant l’historique des discussions chiffrées illisibles. Configurez la sauvegarde de clés ou exportez vos clés de salon depuis un autre appareil avant de réinitialiser votre mot de passe. + Continuer + + Cet e-mail n’est lié à aucun compte + + Vérifiez votre boîte de réception + Un e-mail de vérification a été envoyé à %1$s. + Touchez le lien pour confirmer votre nouveau mot de passe. Après avoir suivi le lien qu’il contient, cliquez ci-dessous. + J’ai vérifié mon adresse e-mail + + Réussi ! + Votre mot de passe a été réinitialisé. + Vous avez été déconnecté de tous les appareils et ne recevrez plus de notification. Pour réactiver les notifications, reconnectez-vous sur chaque appareil. + Retourner à l’authentification + + Attention + Votre mot de passe n’a pas encore été changé. +\n +\nArrêter le processus de changement \? + + Définir l’adresse e-mail + Définir une adresse e-mail pour récupérer votre compte. Plus tard, vous pourrez éventuellement autoriser des personnes à vous retrouver avec votre adresse e-mail. + E-mail + E-mail (facultatif) + Suivant + + Définir le numéro de téléphone + Définir un numéro de téléphone pour autoriser éventuellement des personnes à vous découvrir. + Veuillez utiliser le format international. + Numéro de téléphone + Numéro de téléphone (facultatif) + Suivant + + Confirmer le numéro de téléphone + Nous avons envoyé un code à %1$s. Saisissez-le ci-dessous pour vérifier que c’est bien vous. + Saisir le code + Renvoyer + Suivant + + Les numéros de téléphone internationaux doivent commencer par « + » + Le numéro de téléphone n’a pas l’air d’être valide. Veuillez le vérifier + + S’inscrire sur %1$s + Nom d’utilisateur ou e-mail + Mot de passe + Suivant + Ce nom d’utilisateur est déjà pris + Attention + Votre compte n’est pas encore crée. +\n +\nArrêter le processus de création \? + + Sélectionner matrix.org + Sélectionner Modular + Sélectionner un serveur d’accueil personnalisé + Veuillez compléter le captcha + Acceptez les termes pour continuer + + Vérifiez vos e-mails + Nous avons envoyé un e-mail à %1$s. +\nCliquez sur le lien qu’il contient pour continuer la création du compte. + Le code saisi n’est pas correct. Veuillez vérifier. + Serveur d’accueil trop vieux + Ce serveur d’accueil utilise une version trop ancienne pour s’y connecter. Demandez à l’administrateur de votre serveur d’accueil de le mettre à niveau. + + + Trop de requêtes ont été envoyées. Vous pouvez réessayer dans %1$d seconde… + Trop de requêtes ont été envoyées. Vous pouvez réessayer dans %1$d secondes… + + + Vu par + + Vous êtes déconnecté + Cela peut être dû à plusieurs raisons : +\n +\n• Vous avez changé votre mot de passe sur un autre appareil. +\n +\n• Vous avez supprimé cet appareil depuis un autre appareil. +\n +\n• L’administrateur de votre serveur a invalidé votre accès pour des raisons de sécurité. + Se reconnecter + + Vous êtes déconnecté + Se connecter + L’administrateur de votre serveur d’accueil (%1$s) vous a déconnecté de votre compte %2$s (%3$s). + Connectez-vous pour récupérer les clés de chiffrement stockées uniquement sur cet appareil. Vous en avez besoin pour lire tous vos messages sécurisés sur n’importe quel appareil. + Se connecter + Mot de passe + Effacer les données personnelles + Attention : Vos données personnelles (y compris les clés de chiffrement) sont toujours stockées sur cet appareil. +\n +\nEffacez-les si vous n’utilisez plus cet appareil ou si vous voulez vous connecter à un autre compte. + Effacer toutes les données + + Effacer les données + Effacer toutes les données stockées sur cet appareil \? +\nReconnectez-vous pour accéder aux données et aux messages de votre compte. + Vous perdrez l’accès à vos messages sécurisés sauf si vous vous connectez pour récupérer vos clés de chiffrement. + Effacer les données + La session en cours est celle de l’utilisateur %1$s et vous fournissez des identifiants pour l’utilisateur %2$s. Ce n’est pas pris en charge par RiotX. +\nEffacez d’abord les données, puis reconnectez-vous avec un autre compte. + + Votre lien matrix.to était malformé + La description est trop courte + diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index 1f518fd590..1f25dc1d61 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -1569,7 +1569,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró \n \nRiotX ezeket támogatja: • Bejelentkezés létező fiókba • Szoba készítés és nyilvános szobába való belépés • Meghívók fogadása és elutasítás • Felhasználók szobáinak listázása • Szoba adatainak megtekintése • Szöveges üzenet küldése • Csatolmány küldése • Titkosított szobákban üzenetek olvasása és írása • Titkosítás: Végponttól végpontig titkosító kulcsok mentése, fejlett eszköz ellenőrzés, kulcs megosztás kérése és válasz • „Push” értesítések • Világos, sötét és fekete téma \n -\nNem minden Riot funkció támogatott a RiotX-ben jelenleg. A fő hiányzó (és hamarosan elérhető!) funkciók: • Felhasználói fiók létrehozása • Szoba beállítások (szoba tagság mutatása, stb…) • Hívások • Kisalkalmazások • … +\nNem minden Riot funkció támogatott a RiotX-ben jelenleg. A fő hiányzó (és hamarosan elérhető!) funkciók: • Szoba beállítások (szoba tagság mutatása, stb…) • Hívások • Kisalkalmazások • … Közvetlen beszélgetés @@ -1744,7 +1744,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Ez nemkívánt (spam) Ez nem idevaló - Egyedi jelentés + Egyedi jelentés… Tartalom bejelentése A tartalom bejelentésének oka JELENTÉS @@ -1805,4 +1805,179 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Mikrofon használata DRM-mel védett média olvasása + Ez nem egy érvényes Matrix szerver cím + Felhasználó tiltása + + Minden üzenet (hangos) + Minden üzenet + Csak ha megemlítenek + Elnémít + Beállítások + Szoba elhagyása + %1$snem változtatott semmit + A megadott üzenet szpojlerként küldése + Szpojler + Adj meg kulcsszavakat a reakció megtalálásához. + + Nem hagysz figyelmen kívül senkit + + A szoba további beállításait egy hosszú kattintással érheted el + + + %1$s hozzáférhetővé tette a szobát bárkinek, aki ismeri a linket. + %1$s beállította, hogy a szobába csak meghívóval lehessen belépni. + Olvasatlan üzenetek + + Szabadítsd fel a kommunikációdat + Beszélgess másokkal közvetlenül vagy csoportosan + Legyen a beszélgetés bizalmas a titkosítással + Bővítsd és szabd testre a élményt + Kezd el + + Válassz szervert + Mit ha e-mail lenne, egy felhasználód van de bárkivel tudsz beszélgetni + Milliók csatlakoznak ingyen a legnagyobb nyilvános szerveren + Prémium üzemeltetés szervezetek részére + Tudj meg többet + Egyéb + Egyedi és haladó beállítások + + Folytatás + Csatlakozás ide: %1$s + Csatlakozás Modularhoz + Csatlakozás egyedi matrix szerverhez + Bejelentkezés ide: %1$s + Fiók készítés + Bejelentkezés + SSO-val való folytatás + + Modular Cím + Cím + Prémium üzemeltetés szervezetek részére + Add meg a Modular Riot vagy a Szerver cím amit használni szeretnél + Add meg a szerver vagy Riot címét amihez csatlakozni szeretnél + + Az oldal betöltésekor hiba történt: %1$s (%2$d) + Ne haragudj, ez a szerver nem fogad új fiókokat. + Ez az e-mail cím egyik fiókhoz sincs társítva. + + Jelszó visszaállítása itt: %1$s + Egy ellenőrző e-mail lesz elküldve a címedre, hogy megerősíthesd az új jelszó beállításodat. + Következő + E-mail + Új jelszó + + Figyelem! + A jelszóváltoztatás megváltoztatja minden eszközön az összes végponttól végpontig titkosításhoz használt kulcsodat; így a titkosított csevegések olvashatatlanok lesznek. Készíts biztonsági másolatot vagy mentsd ki a szoba kulcsaidat minden eszközödön mielőtt megváltoztatod a jelszavad. + Folytatás + + Ez az e-mail cím egyik fiókhoz sincs társítva + + Nézd meg a bejövő e-mailjeidet + Az ellenőrző e-mail ide küldtük: %1$s. + Ellenőriztem az e-mail címemet + + Sikerült! + A jelszavad újra beállításra került. + Minden eszközödről ki vagy jelentkeztetve és „push” értesítéseket sem fogsz kapni. Az értesítések újbóli engedélyezéséhez újra be kell jelentkezned minden eszközön. + Vissza a belépéshez + + Figyelmeztetés + A jelszavad még nem módosult. +\n +\nMegszakítod a jelszó módosítást\? + + E-mail cím beállítása + E-mail + E-mail (nem kötelező) + Következő + + Telefonszám beállítása + Telefonszám beállítása, hogy az ismerősök megtalálhassanak. + Kérlek a nemzetközi formátumot használd. + Telefonszám + Telefonszám (nem kötelező) + Következő + + Telefonszám ellenőrzése + Elküldtük a kódot ide: %1$s. Add meg itt alul amivel ellenőrizhetjük, hogy te te vagy. + Kód megadása + Küld újra + Következő + + Az alkalmazás nem tud bejelentkezni a Matrix szerverbe. A Matrix szerver ezeket a bejelentkezési módokat támogatja: %1$s. +\n +\nWeb klienssel szeretnél bejelentkezni\? + Az alkalmazás nem tud fiókot készíteni ezen a Matrix szerveren. +\n +\nWeb klienssel szeretnél bejelentkezni\? + + Koppints a linkre az új jelszó megerősítéséhez. Miután követted a linket, kattints alább. + Állíts be egy e-mail címet a fiókod visszaállításához. Később esetleg engedélyezheted, hogy ismerősök e-mail címmel megtalálhassanak. + Nemzetközi telefonszámnak „+” jellel kell kezdődnie + A telefonszám érvénytelennek látszik. Kérlek ellenőrizd + + Bejelentkezés ide: %1$s + Felhasználónév vagy e-mail + Jelszó + Következő + A felhasználónév már használatban van + Figyelmeztetés + A felhasználói fiókod még nincs kész. +\n +\nMegállítód a regisztrációt\? + + matrix.org kiválasztása + Modular kiválasztása + Egyedi matrix szerver kiválasztása + Kérlek old meg a captcha-t + Folytatáshoz fogadd el a feltételeket + + Kérlek ellenőrizd az e-mailed + E-mailt küldtünk ide: %1$s. +\nKérlek kattints a benne lévő linkre a fiók készítés folytatásához. + A beírt kód helytelen. Kérlek ellenőrizd. + Elavult matrix szerver + Ez a matrix szerver túl elavult a csatlakozáshoz. Kérd meg a matrix szerver adminisztrátorát a frissítésre. + + + Túl sok kérés lett elküldve. %1$d másodperc múlva újra próbálhatod… + Túl sok kérés lett elküldve. %1$d másodperc múlva újra próbálhatod… + + + Látták: + + Kijelentkeztél + A következő okok miatt lehet: +\n +\n• Másik eszközön megváltoztattad a jelszavadat. +\n +\n• Törölted ezt az eszközt egy másik eszközről. +\n +\n• A matrix szerver adminisztrátora biztonsági okokból érvénytelenítette a hozzáférésed. + Lépj be újra + + Kijelentkeztél + Bejelentkezés + A matrix szerver (%1$s) adminisztrátora kiléptetett a felhasználói fiókodból %2$s (%3$s). + A csak ezen az eszközön meglévő titkosítási kulcsokhoz való hozzáféréshez be kell jelentkezned. Ahhoz hogy bármelyik eszközön elolvashasd a titkosított üzeneteidet szükséged lesz rájuk. + Bejelentkezés + Jelszó + Személyes adatok törlése + Figyelmeztetés: A személyes adataid (beleértve a titkosító kulcsokat is) továbbra is az eszközön vannak tárolva. +\n +\nHa az eszközt nem használod tovább vagy másik fiókba szeretnél bejelentkezni, töröld őket. + Minden adat törlése + + Adat törlése + Biztos vagy benne, hogy minden az eszközön tárolt adatot törölni szeretnél\? +\nA fiók és az üzeneteid eléréséhez jelentkezz be. + Elveszted a hozzáférésedet a titkosított üzeneteidhez ha nem jelentkezel be a titkosítási kulcsok visszaállításához. + Adat törlése + A jelenlegi munkamenet %1$s felhasználóhoz tartozik és %2$s azonosítási adatait adtad meg. Ez RiotX-ben nem támogatott. +\nElőször töröld az adatokat, majd a másik felhasználói fiókba lépj be. + + A matrix.to linked hibás + A leírás túl rövid + diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index ddc19fe125..f79cca9f70 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -1615,7 +1615,7 @@ \n \nRiotX supporta: • Accesso ad account esistente • Crea stanze ed entra in stanze pubbliche • Accetta e rifiuta inviti • Elenca stanze utenti • Vedi dettagli stanza • Invia messaggi di testo • Invia allegati • Leggi e scrivi messaggi in stanze cifrate • Crypto: backup chiavi E2E, verifica avanzata dispositivi, richiesta e risposta condivisione chiavi • Notifiche push • Tema chiaro, scuro e nero \n -\nNon tutte le funzioni di Riot sono già implementate in RiotX. Principali funzioni mancanti (prossimamente!): • Creazione account • Impostazioni stanza (elenca membri stanza, ecc.) • Chiamate • Widget • … +\nNon tutte le funzioni di Riot sono già implementate in RiotX. Principali funzioni mancanti (prossimamente!): • Impostazioni stanza (elenca membri stanza, ecc.) • Chiamate • Widget • … Non hai nulla di nuovo da vedere! Messaggi diretti @@ -1791,7 +1791,7 @@ È spam È inappropriato - Segnalazione personalizzata + Segnalazione personalizzata… Segnala questo contenuto Motivo della segnalazione SEGNALA @@ -1851,4 +1851,179 @@ Usa il microfono Leggi media protetti da DRM + Questo non è un indirizzo di server Matrix valido + Blocca utente + + Tutti i messaggi (rumoroso) + Tutti i messaggi + Solo citazioni + Silenzioso + Impostazioni + Lascia la stanza + %1$s non ha fatto modifiche + Invia il messaggio come spoiler + Spoiler + Digita parole chiave per trovare una reazione. + + Non stai ignorando alcun utente + + Tieni premuto su una stanza per altre opzioni + + + %1$s ha reso pubblica la stanza a chiunque conosca il collegamento. + %1$s ha reso la stanza solo ad invito. + Messaggi non letti + + Libera le tue comunicazioni + Chatta con persone direttamente o in gruppi + Tieni private le conversazioni con la cifratura + Estendi e personalizza la tua esperienza + Inizia + + Seleziona un server + Proprio come le email, gli account hanno una sola origine, ma puoi parlare con chiunque + Unisciti a milioni gratuitamente sul server pubblico più grande + Hosting premium per organizzazioni + Maggiori info + Altro + Impostazioni personalizzate ed avanzate + + Continua + Connetti a %1$s + Connetti a Modular + Connetti ad un server personalizzato + Accedi a %1$s + Registrati + Accedi + Continua con SSO + + Indirizzo Modular + Indirizzo + Hosting premium per organizzazioni + Inserisci l\'indirizzo del Riot Modular o del server che vuoi usare + Inserisci l\'indirizzo di un server o di un Riot a cui vuoi connetterti + + Si è verificato un errore caricando la pagina: %1$s (%2$d) + L\'applicazione non riesce ad accedere a questo homeserver. L\'homeserver supporta i seguenti tipi di accesso: %1$s. +\n +\nVuoi accedere usando un client web\? + Spiacenti, questo server non accetta nuovi account. + L\'applicazione non riesce a creare un account su questo homeserver. +\n +\nVuoi registrarti usando un client web\? + + Questa email non è associata ad alcun account. + + Reimposta password su %1$s + Verrà inviata un\'email di verifica nella tua posta per confermare l\'impostazione della nuova password. + Avanti + Email + Nuova password + + Attenzione! + Cambiare la password reimposterà qualunque chiave di cifratura end-to-end su tutti i tuoi dispositivi, rendendo illeggibile la cronologia delle chat criptate. Imposta il Backup Chiavi o esporta le tue chiavi della stanza da un altro dispositivo prima di reimpostare la password. + Continua + + Questa email non è collegata ad alcun account + + Controlla la tua posta + Un\'email di verifica è stata inviata a %1$s. + Tocca il collegamento per confermare la tua nuova password. Una volta seguito il collegamento contenuto, clicca sotto. + Ho verificato il mio indirizzo email + + Successo! + La tua password è stata reimpostata. + Sei stato disconnesso da tutti i dispositivi e non riceverai più notifiche push. Per riattivare le notifiche, riaccedi su ogni dispositivo. + Torna all\'accesso + + Attenzione + La tua password non è ancora cambiata. +\n +\nFermare il processo di cambio password\? + + Imposta indirizzo email + Imposta un\'email per recuperare il tuo account. Più tardi potrai permettere facoltativamente alle persone che conosci di trovarti tramite la tua email. + Email + Email (facoltativa) + Avanti + + Imposta numero di telefono + Imposta un numero di telefono per permettere facoltativamente alle persone che conosci di trovarti. + Si prega di usare il formato internazionale. + Numero di telefono + Numero di telefono (facoltativo) + Avanti + + Conferma numero di telefono + Abbiamo inviato un codice a %1$s. Inseriscilo sotto per verificare che sei tu. + Inserisci codice + Invia di nuovo + Avanti + + I numeri di telefono internazionali devono iniziare con \'+\' + Il numero di telefono non sembra valido. Ricontrollalo + + Registrati su %1$s + Nome utente o email + Password + Avanti + Quel nome utente esiste già + Attenzione + Il tuo account non è ancora stato creato. +\n +\nFermare il processo di registrazione\? + + Seleziona matrix.org + Seleziona Modular + Seleziona un server personalizzato + Completa la verifica Captcha + Accetta le condizioni per continuare + + Controlla la tua email + Abbiamo inviato un\'email a %1$s. +\nClicca il collegamento contenuto per continuare la creazione dell\'account. + Il codice inserito non è corretto. Ricontrollalo. + Homeserver obsoleto + Questo homerserver è di una versione troppo vecchia per connettersi. Chiedi all\'amministratore dell\'homeserver di aggiornarlo. + + + Sono state inviate troppe richieste. Puoi riprovare in %1$d secondo… + Sono state inviate troppe richieste. Puoi riprovare in %1$d secondi… + + + Visto da + + Sei disconnesso + Può essere dovuto a vari motivi: +\n +\n• Hai cambiato la password su un altro dispositivo. +\n +\n• Hai eliminato questo dispositivo da un altro dispositivo. +\n +\n• L\'amministratore del server ha bloccato il tuo accesso per motivi di sicurezza. + Accedi di nuovo + + Sei disconnesso + Accedi + L\'amministratore dell\'homeserver (%1$s) ti ha disconnesso dall\'account %2$s (%3$s). + Accedi per recuperare le chiavi di cifratura memorizzate esclusivamente su questo dispositivo. Ti servono per leggere tutti i tuoi messaggi sicuri su qualsiasi dispositivo. + Accedi + Password + Elimina i dati personali + Attenzione: i tuoi dati personali (incluse chiavi di cifratura) sono ancora in questo dispositivo. +\n +\nEliminali se hai finito di usare questo dispositivo, o se vuoi accedere ad un altro account. + Elimina tutti i dati + + Elimina i dati + Eliminare tutti i dati attualmente presenti in questo dispositivo\? +\nRiaccedi per avere accesso ai dati dell\'account e ai messaggi. + Perderai l\'accesso ai messaggi sicuri a meno che non accedi per recuperare le tue chiavi di cifratura. + Elimina i dati + La sessione attuale è per l\'utente %1$s e hai fornito le credenziali per l\'utente %2$s. Ciò non è supportato da RiotX. +\nPrima elimina i dati, poi accedi di nuovo con un altro account. + + Il tuo collegamento matrix.to non è corretto + La descrizione è troppo breve + diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 8b83c85e39..974b03e02e 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -217,7 +217,7 @@ 端末詳細 ID(端末固有番号) - 端末名 + 公開端末名 端末名 最終接続日 %1$s @ %2$s @@ -281,8 +281,8 @@ この部屋のサーバ内識別ID 実験的 これらは予期せぬ不具合が生じるかもしれない実験的機能です. 慎重に使用してください. - End-to-End 暗号 - End-to-End 暗号を使用中 + エンドツーエンド暗号化 + エンドツーエンド暗号化を使用中 暗号を有効にするためにはログアウトする必要があります. 認証された端末のみで暗号化 この部屋では, この端末から認証されていない端末への暗号送信をしません. @@ -485,9 +485,9 @@ 部屋一覧 外観 - End-to-end 暗号についての情報 + エンドツーエンド暗号化についての情報 - 端末名 + 公開端末名 部屋のEnd-to-end暗号鍵を出力 認証 履歴を検索 @@ -585,7 +585,7 @@ Riotアプリがあなたの電話帳へアクセスすることを許可しま 不具合報告 - レセプトリストを読み込む + 開封確認メッセージのリスト ダウンロードファイルに保存しますか? この招待は%sさんに送られましたが、このアカウントには関連づけられていません。 他のアカウントでログインするか、このメールアドレスをあなたののアカウントに追加できます。 @@ -660,7 +660,7 @@ Riotアプリがあなたの電話帳へアクセスすることを許可しま 復号エラー 発信者装置の情報 - 名前 + 公開端末名 デバイスキー Ed25519 フィンガープリント @@ -1016,4 +1016,43 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ \n%1$s または %2$s として参加 音声 ビデオ + 詳細な通知設定 + バックグラウンド同期モード (実験) + バッテリーを考慮して最適化 + リアルタイム性を重視して最適化 + バックグラウンド同期を行わない + 優先同期間隔 + %s +\n同期は、デバイスのリソース (バッテリ残量) または状態 (スリープ) に応じて延期される場合があります。 + 文字入力中通知を送信 + 文字入力中であることを他の参加者に伝えます。 + 開封確認メッセージを表示 + 開封確認メッセージをクリックすると詳細なリストを確認できます。 + エンター入力でメッセージを送信 + ソフトウェアキーボードのエンターボタンを押した際に、改行を追加する代わりにメッセージを送信します。 + + パスワード + パスワードを更新 + パスワードが無効です + メディア + シャッター音を再生 + + 公開端末名 (会話を行うユーザーに表示されます) + 音なし + パスワードを表示 + パスワードを隠す + 新しいパスワード + + パスワード + パスワード + 今ここでサインアウトすると、あなたの暗号化されたメッセージは失われてしまいます + 鍵のバックアップは現在処理中です。処理中にサインアウトすると暗号化されたメッセージにアクセスできなくなります。 + 暗号化されたメッセージにアクセスできなくなることを防ぐため、鍵の安全なバックアップはあなたのデバイス全てで有効化してください。 + 暗号化されたメッセージは不要です + 鍵をバックアップしています… + 鍵のバックアップを使用 + 続行しますか? + バックアップ + サインアウトする前に鍵をバックアップしないと、暗号化されたメッセージにアクセスできなくなります。 + diff --git a/vector/src/main/res/values-ko/strings.xml b/vector/src/main/res/values-ko/strings.xml index 76dafef323..14f7a082af 100644 --- a/vector/src/main/res/values-ko/strings.xml +++ b/vector/src/main/res/values-ko/strings.xml @@ -1495,7 +1495,7 @@ \n \nRiotX 지원: • 존재하는 계정으로 로그인 • 방을 만들고 공공 방에 참가 • 초대를 수락하거나 거절 • 사용자 방 목록 • 방 세부 정보 보기 • 문자 메시지 보내기 • 첨부 파일 보내기 • 암호화된 방에서 메시지 읽고 쓰기 • 암호화: 종단간 암호화 키 백업, 고급 기기 확인, 키 공유 요청과 답장 • 푸시 알림 • 밝은 테마, 어두운 테마 그리고 검정 테마 \n -\n아직 Riot의 모든 기능이 RiotX에 구현되지 않았습니다. 주요 없는 (그리고 곧 나올!) 기능: • 계정 만들기 • 방 설정 (방 구성원 목록 등) • 전화 • 위젯 • … +\n아직 Riot의 모든 기능이 RiotX에 구현되지 않았습니다. 주요 없는 (그리고 곧 나올!) 기능: • 방 설정 (방 구성원 목록 등) • 전화 • 위젯 • … 다이렉트 메시지 diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index e815f01b9f..014f298545 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -1659,7 +1659,7 @@ \n \nПоддержка RiotX: - Войти в существующую учетную запись - Создать комнату и присоединиться к общедоступным комнатам - Принять и отклонить приглашения - Список комнат пользователей - Просмотр сведений о комнате - Отправить текстовые сообщения - Отправить вложение - Читать и писать сообщения в зашифрованных комнатах - Криптография: Резервное копирование клавиш E2E, предварительная проверка устройства, запрос и ответ на общий доступ к ключам - Нажмите уведомление - Светлые и черные темы \n -\nНе все функции Riot пока реализованы в RiotX. Основные отсутствующие (и скоро появятся!) свойства: - Создание учетной записи - Настройки комнат (список членов комнат и т.д.) - Вызовы - Виджеты - .… +\nНе все функции Riot пока реализованы в RiotX. Основные отсутствующие (и скоро появятся!) свойства: - Настройки комнат (список членов комнат и т.д.) - Вызовы - Виджеты - .… Предварительный просмотр открытой комнаты в RiotX пока не поддерживается @@ -1797,4 +1797,17 @@ Откройте навигационный ящик Откройте меню «Создать комнату» + Неверный адрес сервера Matrix + Подтвердите пароль + Требуется аутентификация + + + Интеграции + Разрешить интеграции + Виджет + Загрузить виджет + Перезагрузить виджет + Открыть в браузере + ID виджета + Принять diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index f2ff6691f3..b0e3137900 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -1521,7 +1521,7 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani \n \nRiotX-i mbulon: • Hyrje në një llogari ekzistuese • Krijim dhome dhe pjesëmarrje në dhoma publike • Pranim dhe hedhje poshtë ftesash • Njohje të dhomave të përdoruesve • Parje hollësish dhome • Dërgim mesazhesh tekst • Dërgim bashkëngjitjesh • Lexim dhe shkrim mesazhesh në dhoma të fshehtëzuara • Kriptografi: kopjeruajtje kyçesh E2E, verifikim i thelluar pajisjesh, kërkesa dhe përgjigje për ndarje kyçesh • Njoftime push • Tema të Çelëta, të Errëta dhe të Zeza \n -\nNë RiotX s’janë sendërtuar ende krejt veçoritë e Riot-it. Veçori kryesore që mungojnë (dhe që do të vijnë së shpejti!): • Krijim llogarish • Rregullime dhome (shfaqje anëtarësh dhome, etj.) • Thirrje • Widget-es • … +\nNë RiotX s’janë sendërtuar ende krejt veçoritë e Riot-it. Veçori kryesore që mungojnë (dhe që do të vijnë së shpejti!): • Rregullime dhome (shfaqje anëtarësh dhome, etj.) • Thirrje • Widget-es • … Përgjegjës Integrimesh @@ -1696,7 +1696,7 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Ngjitës Është e padëshiruar Është e papërshtatshme - Raport vetjak + Raport vetjak… Raportojeni këtë lëndë Arsye për raportimin e kësaj lënde RAPORTOJENI @@ -1757,4 +1757,180 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Të lexojë Media të mbrojtur me DRM Mbyll banderolë kopjeruajtjeje kyçesh + Kjo s’është adresë e vlefshme shërbyesi Matrix + Hashi i lëndës s’u përputh me atë që pritej + S’trajtoi dot të dhëna ndarjeje + + Bllokoje përdoruesin + + Krejt mesazhet (e zhurmshme) + Krejt mesazhet + Vetëm përmendje + Pa Zë + Rregullime + Braktiseni dhomën + %1$s s’bëri ndryshime + Shtypni ca fjalëkyçe që të gjendet një reagim. + + S’jeni duke shpërfillur ndonjë përdorues + + Kryeni klikim të gjatë në një dhomë që të shihni më tepër mundësi + + + %1$s e bëri dhomën publike për këdo që di lidhjen. + %1$s e bëri dhomën vetëm me ftesa. + Mesazhe të palexuar + + Çlironi komunikimet tuaj + Fjalosuni me njerëzit drejtpërsëdrejti ose në grupe + Mbajini bisedat private, përmes fshehtëzimit + Zgjeroni & përshtatni punimin tuaj + Si t’ia fillohet + + Përzgjidhni një shërbyes + Ashtu si te email-et, llogaritë kanë një shtëpi të tyren, edhe pse mund të bisedoni me këdo + Bashkojuni milionave, falas, në shërbyesin më të madh publik + Strehim me pagesë për ente + Mësoni më tepër + Tjetër + Rregullime vetjake & të mëtejshme + + Vazhdo + Lidhu me %1$s + Lidhu me një shërbyes modular + Lidhu me një shërbyes vetjak + Hyni te %1$s + Regjistrohuni + Hyni + Vazhdoni me SSO + + Adresë Modulari + Adresë + Strehim me pagesë për ente + Jepni adresën e Modular Riot-it ose Shërbyesit që doni të përdoret + Jepni adresën e një shërbyesi ose një instance Riot me të cilën doni të lidheni + + Ndodhi një gabim kur ngarkohej faqja: %1$s (%2$d) + Aplikacioni s’është në gjendje të bëjë hyrje në llogari në këtë shërbyes Home. Shërbyesi Home mbulon llojet vijuese të hyrjes(ve): %1$s. +\n +\nDoni të hyni duke përdorur një klient web\? + Na ndjeni, ky shërbyes s’pranon llogari të reja. + Aplikacioni s’është në gjendje të krijojë llogari në këtë shërbyes Home. +\n +\nDoni të regjistroheni duke përdorur një klient web\? + + Ky emai s’është përshoqëruar me ndonjë llogari. + + Ricaktoni fjalëkalimin në %1$s + Te mesazhet tuaj do të dërgohet një email verifikimi, për të ripohuar caktimin e fjalëkalimit tuaj të ri. + Pasuesi + Email + Fjalëkalim i ri + + Kujdes! + Ndryshimi i fjalëkalimit tuaj do të sjellë ricaktim të çfarëdo kyçesh fshehtëzimi skaj-më-skaj në krejt pajisjet tuaja, duke e bërë të palexueshëm historikun e bisedave të fshehtëzuara. Përpara se të ricaktoni fjalëkalimin tuaj, ujdisni një Kopjeruajtje Kyçesh ose eksportoni kyçet e dhomës tuaj prej një tjetër pajisjeje. + Vazhdo + + Ky email s’është i lidhur me ndonjë llogari + + Kontrolloni te mesazhet tuaj të marrë + Një email verifikimi u dërgua te %1$s. + Prekni mbi lidhjen që të ripohohet fjalëkalimi juaj i ri. Pasi të keni ndjekur lidhjen që përmban, klikoni më poshtë. + E kam verifikuar adresën time email + + Sukses! + Fjalëkalimi juaj u ricaktua. + Jeni nxjerrë jashtë prej krejt pajisjeve dhe s’do të merrni më njoftime push. Që të riaktivizoni njoftimet, bëni sërish hyrjen në çdo pajisje. + Mbrapsht te Hyrja + + Kujdes + Fjalëkalimi juaj s’ka ndryshuar ende. +\n +\nTë ndalet procesi i ndryshimit të fjalëkalimit\? + + Caktoni adresë email + Caktoni një email për rimarrje të llogarisë tuaj. Më vonë, mundeni të lejoni persona që njihni t’ju zbulojnë përmes email-it tuaj. + Email + Email (në daçi) + Pasuesi + + Caktoni numër telefoni + Caktoni një numër telefoni për t’i lejuar, në daçi, nejrëzit t’ju gjejnë. + Ju lutemi, përdorni formatin ndërkombëtar. + Numër telefoni + Numër telefoni (opsionale) + Pasuesi + + Ripohoni numër telefoni + Sapo dërguam një kod te %1$s. Jepeni më poshtë që të verifikohet se jeni ju. + Jepni kod + Ridërgoje + Pasuesi + + Numrat e telefonave ndëkombëtarë duhet të fillojnë me \'+\' + Numri i telefonit duket se është i vlefshëm. Ju lutemi, kontrollojeni + + Regjistrohuni te %1$s + Emër përdoruesi ose email + Fjalëkalim + Pasuesi + Ai emër përdoruesi është i zënë + Kujdes + Llogaria juaj s’është krijuar ende. +\n +\nTë ndalet procesi i regjistrimit\? + + Përzgjidhni matrix.org + Përzgjidhni modular + Përzgjidhni një shërbyes Home vetjak + Ju lutemi, zgjidhni captcha-n + Që të vazhdohet, pranoni terma + + Ju lutemi, kontrolloni email-in tuaj + Sapo dërguam një email te %1$s. +\nJu lutemi, klikoni mbi lidhjen që përmban, që të vazhdohet krijimi i llogarisë. + Kodi që dhatë s’është i saktë. Ju lutemi, kontrollojeni. + Shërbyes Home i vjetruar + Ky shërbyes Home xhiron një version shumë të vjetër për t’u lidhur me të. Kërkojini përgjegjësit të shërbyesit tuaj Home ta përmirësojë. + + + Janë dërgua shumë kërkesa. Mund të riprovoni pas %1$d sekonde… + Janë dërgua shumë kërkesa. Mund të riprovoni pas %1$d sekondash… + + + Parë nga + + Keni bërë dalje + Mund të vijë për arsye të ndryshme: +\n +\n• Keni ndryshuar fjalëkalimin tuaj në një pajisje tjetër. +\n +\n• E keni fshirë këtë pajisje prej një pajisjeje tjetër. +\n +\n• Përgjegjësi i shërbyesit tuaj e ka bërë të pamundur hyrjen tuaj për arsye sigurie. + Ribëni hyrjen + + Keni bërë dalje + Hyni + Përgjegjësi i shërbyesit tuaj Home (%1$s) ju ka nxjerrë jashtë llogarisë tuaj %2$s (%3$s). + Bëni hyrjen, që të rimerrni kyçe fshehtëzimi të depozituar përjashtimisht në këtë pajisje. Do t’ju duhen për të lexuar krejt mesazhet tuaj të siguruar në çfarëdo pajisje. + Hyni + Fjalëkalim + Spastro të dhëna personale + Kujdes: Të dhënat tuaja personale (përfshi kyçe fshehtëzimi) janë ende të depozituara në këtë pajisje. +\n +\nSpastrojini, nëse keni përfunduar së përdoruri këtë pajisje, ose dëshironi të bëni hyrjen në një tjetër llogari. + Spastro krejt të dhënat + + Spastroni të dhëna + Të spastrohen krejt të dhënat e depozituara aktualisht në këtë pajisje\? +\nQë të mund të hyni te të dhëna të llogarisë tuaj dhe te mesazhe, bëni sërish hyrjen. + Do të humbni hyrje te mesazhe të sigurt, veç në hyfshi për të rimarrë kyçet tuaj të fshehtëzimit. + Spastro të dhënat + Sesioni i tanishëm është për përdoruesin %1$s dhe ju jepni kredenciale për përdoruesin %2$s. Kjo nuk mbulohet nga RiotX. +\nJu lutemi, së pari spastroni të dhëna, mandej hyni sërish në një tjetër llogari. + + Lidhja juaj matrix.to është e keqformuar + Përshkrimi është shumë i shkurtër + diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index c5f2dc7022..4086598171 100755 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -1128,4 +1128,5 @@ %1$d/%2$d ключ(і/ів) успішно імпортовано. + Позначити як прочитане diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 577c1ef955..b0f66466aa 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -1522,7 +1522,7 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意 \n \nRiotX 支援:• 登入到既有的帳號 • 建立聊天室與加入公開聊天室 • 接受與回絕邀請 • 列出使用者聊天室 • 檢視聊天室詳細資訊 • 傳送文字訊息 • 傳送附件 • 讀取與編寫已加密的聊天室 • 加密:E2E 金鑰備份、進階裝置驗證、金鑰分享請求與回應 • 推送通知 • 亮、暗與黑色主題 \n -\n不是所有 Riot 的功能都已在 RiotX 中實作。主要缺少(會在稍後到來!)的功能:• 建立帳號 • 聊天室設定(列出聊天室成員等) • 通話 • 小工具 • … +\n不是所有 Riot 的功能都已在 RiotX 中實作。主要缺少(會在稍後到來!)的功能 • 聊天室設定(列出聊天室成員等) • 通話 • 小工具 • … 直接訊息 @@ -1695,7 +1695,7 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意 垃圾訊息 不合適 - 自訂回報 + 自訂回報…… 回報此內容 回報此內容的理由 回報 @@ -1757,4 +1757,178 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意 使用麥克風 讀取 DRM 保護的媒體 + 這不是有效的 Matrix 伺服器位置 + 封鎖使用者 + + 所有訊息(吵雜) + 所有訊息 + 僅提及 + 靜音 + 設定 + 離開聊天室 + %1$s 未做出任何變更 + 傳送為擾亂者指定的訊息 + 擾亂者 + 輸入關鍵字以尋找反應。 + + 您未忽略任何使用者 + + 長按聊天室以檢視更多選項 + + + %1$s 將聊天室設為公開給所有知道連結的人。 + %1$s 將聊天室設為僅邀請可進入。 + 未讀訊息 + + 讓您的通訊自由 + 直接或在群組中與夥伴們聊天 + 透過加密讓對話保持隱密 + 擴展並自訂您的體驗 + 開始 + + 選取伺服器 + 就像電子郵件,帳號也有一個家,不過您還是可以跟任何人交談 + 在最大的公開伺服器上免費加入數百萬人之中 + 組織另有專業主機可用 + 了解更多 + 其他 + 自訂與進階設定 + + 繼續 + 連線到 %1$s + 連線到 Modular + 連線到自訂伺服器 + 登入到 %1$s + 註冊 + 登入 + 以 SSO 繼續 + + Modular 位置 + 位置 + 組織有專業主機 + 輸入 Modular Riot 或您想要使用的伺服器位置 + 輸入您想要連線的伺服器或 Riot 的位置 + + 載入頁面時發生錯誤:%1$s (%2$d) + 應用程式無法登入此家伺服器。家伺服器支援以下登入類型:%1$s。 +\n +\n您想要使用網路客戶端登入嗎? + 抱歉,此伺服器不接受新帳號。 + 應用程式無法在此家伺服器上建立帳號。 +\n +\n您想要使用網路客戶端註冊嗎? + + 此電子郵件未關聯到任何帳號。 + + 在 %1$s 上重設密碼 + 驗證郵件已傳送到您的收件匣以確認您要設定新密碼。 + 下一步 + 電子郵件 + 新密碼 + + 警告! + 變更您的密碼將會重設在您所有裝置上任何的端到端加密金鑰,讓已加密的聊天歷史無法讀取。請在重設您的密碼前從其他裝置設定金鑰備份或匯出您的聊天室金鑰。 + 繼續 + + 此電子郵件未被連結到任何帳號 + + 檢查您的收件匣 + 驗證電子郵件已傳送至 %1$s。 + 輕點連結以確認您的新密碼。在您使用了其中包含的連結後,請點擊下方。 + 我已經驗證了我的電子郵件地址 + + 成功! + 您的密碼已被重設。 + 您已登出所有裝置,且不會再收到推播通知。要重新啟用通知,請在裝置上再次登入。 + 返回登入 + + 警告 + 您的密碼未變更。 +\n +\n停止密碼變更流程? + + 設定電子郵件地址 + 設定電子郵件以復原您的帳號。之後您也可以選擇性地讓您認識的人透過您的電子郵件找到您。 + 電子郵件 + 電子郵件(選擇性) + 下一個 + + 設定電話號碼 + 設定電話號碼以讓您認識的人找到您。 + 請使用國際格式。 + 電話號碼 + 電話號碼(選擇性) + 下一個 + + 確認電話號碼 + 我們剛傳送了驗證碼給 %1$s。在下方輸入以驗證是您。 + 輸入驗證碼 + 再次傳送 + 下一個 + + 國際電話號碼必須以加號開頭 + 電話號碼似乎無效。請檢查 + + 註冊至 %1$s + 使用者名稱或電子郵件 + 密碼 + 下一個 + 使用者名稱已被使用 + 警告 + 尚未建立您的帳號。 +\n +\n停止註冊程序? + + 選取 matrix.org + 選取 modular + 選取自訂的家伺服器 + 請執行 captcha 挑戰 + 接受條款以繼續 + + 請檢查您的電子郵件 + 我們剛傳送電子郵件給 %1$s。 +\n請點擊其中所包含的連結以繼續建立帳號。 + 輸入的驗證碼不正確。請檢查。 + 未更新的家伺服器 + 此家伺服器所執行的版本太舊,所以無法連線。請要求您的家伺服器管理員升級。 + + + 傳送了太多請求。您可以在 %1$d 秒後重試…… + + + 檢視由 + + 您已登出 + 這可能有多種原因: +\n +\n• 您已在其他裝置上變更您的密碼。 +\n +\n• 您已從其他裝置刪除此裝置。 +\n +\n• 您伺服器的管理員為了安全性讓您的存取無效。 + 再次登入 + + 您已登出 + 登入 + 您的家伺服器 (%1$s) 管理員已將您的帳號 %2$s (%3$s) 登出。 + 登入以復原僅儲存在此裝置上的加密金鑰。您在任何裝置上都需要它們來讀取您所有的安全訊息。 + 登入 + 密碼 + 清除個人資料 + 警告:您的個人資料(包含加密金鑰)仍儲存在此裝置上。 +\n +\n如果您已不想使用此裝置,請將其清除,或是登入到其他帳號。 + 清除所有資料 + + 清除資料 + 清除目前儲存在此裝置上的所有資料嗎? +\n再次登入以存取您的帳號資料與訊息。 + 除非您登入以復原您的加密金鑰,否則您將會失去對安全訊息的存取權。 + 清除資料 + 使用者 %1$s 目前的工作階段與您提供的使用者 %2$s 憑證。RiotX 並不支援。 +\n請先清除您的資料,然後再以其他帳號登入。 + + 您的 matrix.to 連結格式錯誤 + 描述太短了 + diff --git a/vector/src/main/res/values/donottranslate.xml b/vector/src/main/res/values/donottranslate.xml index f5974e18a5..eb5e6d238c 100755 --- a/vector/src/main/res/values/donottranslate.xml +++ b/vector/src/main/res/values/donottranslate.xml @@ -3,6 +3,7 @@ Debug screen + + : diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 8dd57df90a..8b51970652 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1658,7 +1658,6 @@ RiotX supports: • Light, Dark and Black themes Not all features in Riot are implemented in RiotX yet. Main missing (and coming soon!) features: -• Account creation • Room settings (list room members, etc.) • Calls • Widgets @@ -1963,4 +1962,22 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Your matrix.to link was malformed The description is too short + Initial Sync… + + See all my devices + Advanced settings + Developer mode + The developer mode activates hidden features and may also make the application less stable. For developers only! + Rageshake + Detection threshold + Shake your phone to test the detection threshold + Shake detected! + Settings + Current device + Other devices + + Showing only the first results, type more letters… + + Fail-fast + RiotX may crash more often when an unexpected error occurs diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index e8cc1c7360..8c544a3dbf 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -3,25 +3,7 @@ - Initial Sync… - - - See all my devices - Advanced settings - Developer mode - The developer mode activates hidden features and may also make the application less stable. For developers only! - Rageshake - Detection threshold - Shake your phone to test the detection threshold - Shake detected! - Settings - Current device - Other devices - - Showing only the first results, type more letters… - - Fail-fast - RiotX may crash more often when an unexpected error occurs + Your email domain is not authorized to register on this server Messages in this room are not end-to-end encrypted. Messages in this room are end-to-end encrypted. diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml index 7698372053..ead6a30fa6 100644 --- a/vector/src/main/res/xml/vector_settings_preferences.xml +++ b/vector/src/main/res/xml/vector_settings_preferences.xml @@ -37,8 +37,7 @@ android:defaultValue="true" android:key="SETTINGS_SEND_TYPING_NOTIF_KEY" android:summary="@string/settings_send_typing_notifs_summary" - android:title="@string/settings_send_typing_notifs" - app:isPreferenceVisible="@bool/false_not_implemented" /> + android:title="@string/settings_send_typing_notifs" />