diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml
index 1f93d1feee..93ac86f417 100644
--- a/.idea/dictionaries/bmarty.xml
+++ b/.idea/dictionaries/bmarty.xml
@@ -12,12 +12,15 @@
fdroid
gplay
hmac
+ homeserver
ktlint
linkified
linkify
megolm
msisdn
+ msisdns
pbkdf
+ pids
pkcs
riotx
signin
diff --git a/CHANGES.md b/CHANGES.md
index 803b974413..a4767f8be9 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,4 +1,30 @@
-Changes in RiotX 0.20.0 (2020-XX-XX)
+Changes in RiotX 0.21.0 (2020-XX-XX)
+===================================================
+
+Features ✨:
+ - Identity server support (#607)
+ - Switch language support (#41)
+
+Improvements 🙌:
+ - Better connectivity lost indicator when airplane mode is on
+ - Add a setting to hide redacted events (#951)
+
+Bugfix 🐛:
+ - Fix issues with FontScale switch (#69, #645)
+
+Translations 🗣:
+ -
+
+SDK API changes ⚠️:
+ -
+
+Build 🧱:
+ -
+
+Other changes:
+ -
+
+Changes in RiotX 0.20.0 (2020-05-15)
===================================================
Features ✨:
@@ -13,18 +39,10 @@ Bugfix 🐛:
- Fix | Verify Manually by Text crashes if private SSK not known (#1337)
- Sometimes the same device appears twice in the list of devices of a user (#1329)
- Random Crashes while doing sth with cross signing keys (#1364)
-
-Translations 🗣:
- -
+ - Crash | crash while restoring key backup (#1366)
SDK API changes ⚠️:
- - excludedUserIds parameter add to to UserService.getPagedUsersLive() function
-
-Build 🧱:
- -
-
-Other changes:
- -
+ - excludedUserIds parameter added to the UserService.getPagedUsersLive() function
Changes in RiotX 0.19.0 (2020-05-04)
===================================================
diff --git a/docs/identity_server.md b/docs/identity_server.md
new file mode 100644
index 0000000000..04127c9ab0
--- /dev/null
+++ b/docs/identity_server.md
@@ -0,0 +1,92 @@
+# Identity server
+
+Issue: #607
+PR: #1354
+
+## Introduction
+Identity Servers support contact discovery on Matrix by letting people look up Third Party Identifiers to see if the owner has publicly linked them with their Matrix ID.
+
+## Implementation
+
+The current implementation was Inspired by the code from Riot-Android.
+
+Difference though (list not exhaustive):
+- Only API v2 is supported (see https://matrix.org/docs/spec/identity_service/latest)
+- Homeserver has to be up to date to support binding (Versions.isLoginAndRegistrationSupportedBySdk() has to return true)
+- The SDK managed the session and client secret when binding ThreePid. Those data are not exposed to the client.
+- The SDK supports incremental sendAttempt (this is not used by RiotX)
+- The "Continue" button is now under the information, and not as the same place that the checkbox
+- The app can cancel a binding. Current data are erased from DB.
+- The API (IdentityService) is improved.
+- A new DB to store data related to the identity server management.
+
+Missing features (list not exhaustive):
+- Invite by 3Pid (will be in a dedicated PR)
+- Add email or phone to account (not P1, can be done on Riot-Web)
+- List email and phone of the account (could be done in a dedicated PR)
+- Search contact (not P1)
+- Logout from identity server when user sign out or deactivate his account.
+
+## Related MSCs
+The list can be found here: https://matrix.org/blog/2019/09/27/privacy-improvements-in-synapse-1-4-and-riot-1-4
+
+## Steps and requirements
+
+- Only one identity server by account can be set. The user's choice is stored in account data with key `m.identity_server`. But every clients will managed its own token to log in to the identity server
+```json
+{
+ "type": "m.identity_server",
+ "content": {
+ "base_url": "https://matrix.org"
+ }
+}
+```
+- The accepted terms are stored in the account data:
+```json
+{
+ "type": "m.accepted_terms",
+ "content": {
+ "accepted": [
+ "https://vector.im/identity-server-privacy-notice-1"
+ ]
+ }
+}
+```
+
+- Default identity server URL, from Wellknown data is proposed to the user.
+- Identity server can be set
+- Identity server can be changed on another user's device, so when the change is detected (thanks to account data sync) RiotX should properly disconnect from a previous identity server (I think it was not the case in Riot-Android, where we keep the token forever)
+- Registration to the identity server is managed with an openId token
+- Terms of service can be accepted when configuring the identity server.
+- Terms of service can be accepted after, if they change.
+- Identity server can be modified
+- Identity server can be disconnected with a warning dialog, with special content if there are current bound 3pid on this identity server.
+- Email can be bound
+- Email can be unbound
+- Phone can be bound
+- Phone can be unbound
+- Look up can be performed, to get matrixIds from local contact book (phone and email): Android permission correctly handled (not done yet)
+- Look up pepper can be updated if it is rotated on the identity server
+- Invitation using 3PID can be done (See #548) (not done yet)
+- Homeserver access-token will never be sent to an identity server
+- When user sign-out: logout from the identity server if any.
+- When user deactivate account: logout from the identity server if any.
+
+## Screens
+
+### Settings
+
+Identity server settings can be accessed from the internal setting of the application, both from "Discovery" section and from identity detail section.
+
+### Discovery screen
+
+This screen displays the identity server configuration and the binding of the user's ThreePid (email and msisdn). This is the main screen of the feature.
+
+### Set identity server screen
+
+This screen is a form to set a new identity server URL
+
+## Ref:
+- https://matrix.org/blog/2019/09/27/privacy-improvements-in-synapse-1-4-and-riot-1-4 is a good summary of the role of an Identity server and the proper way to configure and use it in respect to the privacy and the consent of the user.
+- API documentation: https://matrix.org/docs/spec/identity_service/latest
+- vector.im TOS: https://vector.im/identity-server-privacy-notice
diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt
index e92da1e424..a60e83ec96 100644
--- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt
+++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt
@@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
import im.vector.matrix.android.api.session.group.model.GroupSummary
+import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
import im.vector.matrix.android.api.session.room.model.RoomSummary
@@ -94,6 +95,11 @@ class RxSession(private val session: Session) {
return session.getPagedUsersLive(filter, excludedUserIds).asObservable()
}
+ fun liveThreePIds(refreshData: Boolean): Observable> {
+ return session.getThreePidsLive(refreshData).asObservable()
+ .startWithCallable { session.getThreePids() }
+ }
+
fun createRoom(roomParams: CreateRoomParams): Single = singleBuilder {
session.createRoom(roomParams, it)
}
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 514d1accae..abc860d1ff 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -158,6 +158,9 @@ dependencies {
// Bus
implementation 'org.greenrobot:eventbus:3.1.1'
+ // Phone number https://github.com/google/libphonenumber
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
+
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
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
index 5bc8653f3d..3ca04a86d1 100644
--- 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
@@ -88,7 +88,8 @@ class CommonTestHelper(context: Context) {
fun syncSession(session: Session) {
val lock = CountDownLatch(1)
- session.open()
+ GlobalScope.launch(Dispatchers.Main) { session.open() }
+
session.startSync(true)
val syncLiveData = runBlocking(Dispatchers.Main) {
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
index 9278bed918..f9aef3604a 100644
--- 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
@@ -246,7 +246,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
assertNotNull(eventWireContent.get("session_id"))
assertNotNull(eventWireContent.get("sender_key"))
- assertEquals(senderSession.sessionParams.credentials.deviceId, eventWireContent.get("device_id"))
+ assertEquals(senderSession.sessionParams.deviceId, eventWireContent.get("device_id"))
assertNotNull(event.eventId)
assertEquals(roomId, event.roomId)
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt
index f8d30a2679..da3bbdc23c 100644
--- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt
@@ -122,7 +122,7 @@ class XSigningTest : InstrumentedTest {
// We will want to test that in alice POV, this new device would be trusted by cross signing
val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true))
- val bobSecondDeviceId = bobSession2.sessionParams.credentials.deviceId!!
+ val bobSecondDeviceId = bobSession2.sessionParams.deviceId!!
// Check that bob first session sees the new login
val data = mTestHelper.doSync> {
diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt
index bb6e020d89..57ab4aaf33 100644
--- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt
@@ -148,7 +148,7 @@ class KeyShareTests : InstrumentedTest {
// Mark the device as trusted
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
- aliceSession2.sessionParams.credentials.deviceId ?: "")
+ aliceSession2.sessionParams.deviceId ?: "")
// Re request
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
@@ -253,12 +253,12 @@ class KeyShareTests : InstrumentedTest {
})
val txId: String = "m.testVerif12"
- aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.credentials.deviceId
+ aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
?: "", txId)
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
- aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.credentials.deviceId ?: "")?.isVerified == true
+ aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
}
}
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
index 59ef24beec..2e698a929e 100644
--- 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
@@ -835,7 +835,7 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(signature.valid)
assertNotNull(signature.device)
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
- assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.credentials.deviceId)
+ assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(mTestHelper)
@@ -997,7 +997,7 @@ class KeysBackupTest : InstrumentedTest {
keysBackup.backupAllGroupSessions(null, it)
}
- val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!!
+ val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId!!
val oldKeyBackupVersion = keysBackup.currentBackupVersion
val aliceUserId = cryptoTestData.firstSession.myUserId
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
index 1ac70d7f2b..9bdd8f1131 100644
--- 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
@@ -579,7 +579,7 @@ class SASTest : InstrumentedTest {
requestID!!,
cryptoTestData.roomId,
bobSession.myUserId,
- bobSession.sessionParams.credentials.deviceId!!,
+ bobSession.sessionParams.deviceId!!,
null)
bobVerificationService.beginKeyVerificationInDMs(
@@ -587,7 +587,7 @@ class SASTest : InstrumentedTest {
requestID!!,
cryptoTestData.roomId,
aliceSession.myUserId,
- aliceSession.sessionParams.credentials.deviceId!!,
+ aliceSession.sessionParams.deviceId!!,
null)
// we should reach SHOW SAS on both
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/AuthenticationService.kt
index 5150420de2..effeae596a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/AuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/AuthenticationService.kt
@@ -20,7 +20,6 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.LoginFlowResult
-import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.login.LoginWizard
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
@@ -37,6 +36,11 @@ interface AuthenticationService {
*/
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback): Cancelable
+ /**
+ * Request the supported login flows for the corresponding sessionId.
+ */
+ fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback): Cancelable
+
/**
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
*/
@@ -74,15 +78,6 @@ interface AuthenticationService {
*/
fun getLastAuthenticatedSession(): Session?
- /**
- * Get an authenticated session. You should at least call authenticate one time before.
- * If you logout, this session will no longer be valid.
- *
- * @param sessionParams the sessionParams to open with.
- * @return the associated session if any, or null
- */
- fun getSession(sessionParams: SessionParams): Session?
-
/**
* Create a session after a SSO successful login
*/
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/SessionParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/SessionParams.kt
index 2d65cac43d..1cbba50af7 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/SessionParams.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/SessionParams.kt
@@ -21,7 +21,48 @@ package im.vector.matrix.android.api.auth.data
* You don't have to manually instantiate it.
*/
data class SessionParams(
+ /**
+ * Please consider using shortcuts instead
+ */
val credentials: Credentials,
+
+ /**
+ * Please consider using shortcuts instead
+ */
val homeServerConnectionConfig: HomeServerConnectionConfig,
+
+ /**
+ * Set to false if the current token is not valid anymore. Application should not have to use this info.
+ */
val isTokenValid: Boolean
-)
+) {
+ /*
+ * Shortcuts. Usually the application should only need to use these shortcuts
+ */
+
+ /**
+ * The userId of the session (Ex: "@user:domain.org")
+ */
+ val userId = credentials.userId
+
+ /**
+ * The deviceId of the session (Ex: "ABCDEFGH")
+ */
+ val deviceId = credentials.deviceId
+
+ /**
+ * The current homeserver Url. It can be different that the homeserver url entered
+ * during login phase, because a redirection may have occurred
+ */
+ val homeServerUrl = homeServerConnectionConfig.homeServerUri.toString()
+
+ /**
+ * The current homeserver host
+ */
+ val homeServerHost = homeServerConnectionConfig.homeServerUri.host
+
+ /**
+ * The default identity server url if any, returned by the homeserver during login phase
+ */
+ val defaultIdentityServerUrl = homeServerConnectionConfig.identityServerUri?.toString()
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/MatrixError.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/MatrixError.kt
index d7a6954fd5..7c9ace5d82 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/MatrixError.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/MatrixError.kt
@@ -39,7 +39,10 @@ data class MatrixError(
// For M_LIMIT_EXCEEDED
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
// For M_UNKNOWN_TOKEN
- @Json(name = "soft_logout") val isSoftLogout: Boolean = false
+ @Json(name = "soft_logout") val isSoftLogout: Boolean = false,
+ // For M_INVALID_PEPPER
+ // {"error": "pepper does not match 'erZvr'", "lookup_pepper": "pQgMS", "algorithm": "sha256", "errcode": "M_INVALID_PEPPER"}
+ @Json(name = "lookup_pepper") val newLookupPepper: String? = null
) {
companion object {
@@ -129,6 +132,11 @@ data class MatrixError(
/** (Not documented yet) */
const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
+ const val M_TERMS_NOT_SIGNED = "M_TERMS_NOT_SIGNED"
+
+ // For identity service
+ const val M_INVALID_PEPPER = "M_INVALID_PEPPER"
+
// Possible value for "limit_type"
const val LIMIT_TYPE_MAU = "monthly_active_user"
}
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 3ba275fd5f..1b9544bed6 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
@@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
+import im.vector.matrix.android.api.session.identity.IdentityService
import im.vector.matrix.android.api.session.profile.ProfileService
import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.api.session.room.RoomDirectoryService
@@ -39,6 +40,7 @@ import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageSer
import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.sync.SyncState
+import im.vector.matrix.android.api.session.terms.TermsService
import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.api.session.widgets.WidgetService
@@ -55,6 +57,7 @@ interface Session :
SignOutService,
FilterService,
FileService,
+ TermsService,
ProfileService,
PushRuleService,
PushersService,
@@ -79,7 +82,7 @@ interface Session :
* Useful shortcut to get access to the userId
*/
val myUserId: String
- get() = sessionParams.credentials.userId
+ get() = sessionParams.userId
/**
* The sessionId
@@ -147,6 +150,11 @@ interface Session :
*/
fun cryptoService(): CryptoService
+ /**
+ * Returns the identity service associated with the session
+ */
+ fun identityService(): IdentityService
+
/**
* Add a listener to the session.
* @param listener the listener to add.
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt
index c8526985e1..1c2b8de83b 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt
@@ -24,7 +24,15 @@ data class HomeServerCapabilities(
/**
* Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet
*/
- val maxUploadFileSize: Long = MAX_UPLOAD_FILE_SIZE_UNKNOWN
+ val maxUploadFileSize: Long = MAX_UPLOAD_FILE_SIZE_UNKNOWN,
+ /**
+ * Last version identity server and binding supported
+ */
+ val lastVersionIdentityServerSupported: Boolean = false,
+ /**
+ * Default identity server url, provided in Wellknown
+ */
+ val defaultIdentityServerUrl: String? = null
) {
companion object {
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/FoundThreePid.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/FoundThreePid.kt
new file mode 100644
index 0000000000..5817699636
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/FoundThreePid.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.api.session.identity
+
+data class FoundThreePid(
+ val threePid: ThreePid,
+ val matrixId: String
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/IdentityService.kt
new file mode 100644
index 0000000000..2f2821d7a8
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/IdentityService.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.api.session.identity
+
+import im.vector.matrix.android.api.MatrixCallback
+import im.vector.matrix.android.api.util.Cancelable
+
+/**
+ * Provides access to the identity server configuration and services identity server can provide
+ */
+interface IdentityService {
+ /**
+ * Return the default identity server of the user, which may have been provided at login time by the homeserver,
+ * or by the Well-known setup of the homeserver
+ * It may be different from the current configured identity server
+ */
+ fun getDefaultIdentityServer(): String?
+
+ /**
+ * Return the current identity server URL used by this account. Returns null if no identity server is configured.
+ */
+ fun getCurrentIdentityServerUrl(): String?
+
+ /**
+ * Check if the identity server is valid
+ * See https://matrix.org/docs/spec/identity_service/latest#status-check
+ * RiotX SDK only supports identity server API v2
+ */
+ fun isValidIdentityServer(url: String, callback: MatrixCallback): Cancelable
+
+ /**
+ * Update the identity server url.
+ * If successful, any previous identity server will be disconnected.
+ * In case of error, any previous identity server will remain configured.
+ * @param url the new url.
+ * @param callback will notify the user if change is successful. The String will be the final url of the identity server.
+ * The SDK can prepend "https://" for instance.
+ */
+ fun setNewIdentityServer(url: String, callback: MatrixCallback): Cancelable
+
+ /**
+ * Disconnect (logout) from the current identity server
+ */
+ fun disconnect(callback: MatrixCallback): Cancelable
+
+ /**
+ * This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid
+ */
+ fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable
+
+ /**
+ * This will cancel a pending binding of threePid.
+ */
+ fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable
+
+ /**
+ * This will ask the identity server to send an new email or a new SMS to let the user confirm he owns the ThreePid
+ */
+ fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback): Cancelable
+
+ /**
+ * Submit the code that the identity server has sent to the user (in email or SMS)
+ * Once successful, you will have to call [finalizeBindThreePid]
+ * @param code the code sent to the user
+ */
+ fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback): Cancelable
+
+ /**
+ * This will perform the actual association of ThreePid and Matrix account
+ */
+ fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable
+
+ /**
+ * Unbind a threePid
+ * The request will actually be done on the homeserver
+ */
+ fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable
+
+ /**
+ * Search MatrixId of users providing email and phone numbers
+ */
+ fun lookUp(threePids: List, callback: MatrixCallback>): Cancelable
+
+ /**
+ * Get the status of the current user's threePid
+ * A lookup will be performed, but also pending binding state will be restored
+ *
+ * @param threePids the list of threePid the user owns (retrieved form the homeserver)
+ * @param callback onSuccess will be called with a map of ThreePid -> SharedState
+ */
+ fun getShareStatus(threePids: List, callback: MatrixCallback>): Cancelable
+
+ fun addListener(listener: IdentityServiceListener)
+ fun removeListener(listener: IdentityServiceListener)
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/IdentityServiceError.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/IdentityServiceError.kt
new file mode 100644
index 0000000000..83fb949946
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/IdentityServiceError.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.api.session.identity
+
+sealed class IdentityServiceError : Throwable() {
+ object OutdatedIdentityServer : IdentityServiceError()
+ object OutdatedHomeServer : IdentityServiceError()
+ object NoIdentityServerConfigured : IdentityServiceError()
+ object TermsNotSignedException : IdentityServiceError()
+ object BulkLookupSha256NotSupported : IdentityServiceError()
+ object BindingError : IdentityServiceError()
+ object NoCurrentBindingError : IdentityServiceError()
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/IdentityServiceListener.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/IdentityServiceListener.kt
new file mode 100644
index 0000000000..13f622fe77
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/IdentityServiceListener.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.api.session.identity
+
+interface IdentityServiceListener {
+ fun onIdentityServerChange()
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/SharedState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/SharedState.kt
new file mode 100644
index 0000000000..88cac776d6
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/SharedState.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.api.session.identity
+
+enum class SharedState {
+ SHARED,
+ NOT_SHARED,
+ BINDING_IN_PROGRESS
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/ThreePid.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/ThreePid.kt
new file mode 100644
index 0000000000..2a453ca1a6
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/identity/ThreePid.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.api.session.identity
+
+import com.google.i18n.phonenumbers.NumberParseException
+import com.google.i18n.phonenumbers.PhoneNumberUtil
+import im.vector.matrix.android.internal.session.profile.ThirdPartyIdentifier
+
+sealed class ThreePid(open val value: String) {
+ data class Email(val email: String) : ThreePid(email)
+ data class Msisdn(val msisdn: String) : ThreePid(msisdn)
+}
+
+internal fun ThreePid.toMedium(): String {
+ return when (this) {
+ is ThreePid.Email -> ThirdPartyIdentifier.MEDIUM_EMAIL
+ is ThreePid.Msisdn -> ThirdPartyIdentifier.MEDIUM_MSISDN
+ }
+}
+
+@Throws(NumberParseException::class)
+internal fun ThreePid.Msisdn.getCountryCode(): String {
+ return with(PhoneNumberUtil.getInstance()) {
+ getRegionCodeForCountryCode(parse("+$msisdn", null).countryCode)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/profile/ProfileService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/profile/ProfileService.kt
index c1dc9a8afa..92f9359e34 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/profile/ProfileService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/profile/ProfileService.kt
@@ -17,7 +17,9 @@
package im.vector.matrix.android.api.session.profile
+import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
+import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.api.util.Optional
@@ -53,4 +55,15 @@ interface ProfileService {
*
*/
fun getProfile(userId: String, matrixCallback: MatrixCallback): Cancelable
+
+ /**
+ * Get the current user 3Pids
+ */
+ fun getThreePids(): List
+
+ /**
+ * Get the current user 3Pids Live
+ * @param refreshData set to true to fetch data from the homeserver
+ */
+ fun getThreePidsLive(refreshData: Boolean): LiveData>
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineSettings.kt
index 992cad41ca..154074b722 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineSettings.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineSettings.kt
@@ -28,6 +28,10 @@ data class TimelineSettings(
* A flag to filter edit events
*/
val filterEdits: Boolean = false,
+ /**
+ * A flag to filter redacted events
+ */
+ val filterRedacted: Boolean = false,
/**
* A flag to filter by types. It should be used with [allowedTypes] field
*/
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/terms/GetTermsResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/terms/GetTermsResponse.kt
new file mode 100644
index 0000000000..29c6a7a921
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/terms/GetTermsResponse.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.api.session.terms
+
+import im.vector.matrix.android.internal.session.terms.TermsResponse
+
+data class GetTermsResponse(
+ val serverResponse: TermsResponse,
+ val alreadyAcceptedTermUrls: Set
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/terms/TermsService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/terms/TermsService.kt
new file mode 100644
index 0000000000..36e6a411e3
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/terms/TermsService.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.api.session.terms
+
+import im.vector.matrix.android.api.MatrixCallback
+import im.vector.matrix.android.api.util.Cancelable
+
+interface TermsService {
+ enum class ServiceType {
+ IntegrationManager,
+ IdentityService
+ }
+
+ fun getTerms(serviceType: ServiceType,
+ baseUrl: String,
+ callback: MatrixCallback): Cancelable
+
+ fun agreeToTerms(serviceType: ServiceType,
+ baseUrl: String,
+ agreedUrls: List,
+ token: String?,
+ callback: MatrixCallback): Cancelable
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/model/User.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/model/User.kt
index 753c9b609c..9f4f997b3b 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/model/User.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/model/User.kt
@@ -22,6 +22,9 @@ package im.vector.matrix.android.api.session.user.model
*/
data class User(
val userId: String,
+ /**
+ * For usage in UI, consider using [getBestName]
+ */
val displayName: String? = null,
val avatarUrl: String? = null
) {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthModule.kt
index 232cb3f541..68f404cb71 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthModule.kt
@@ -25,16 +25,15 @@ import im.vector.matrix.android.internal.auth.db.AuthRealmMigration
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
-import im.vector.matrix.android.internal.auth.wellknown.DefaultDirectLoginTask
-import im.vector.matrix.android.internal.auth.wellknown.DefaultGetWellknownTask
-import im.vector.matrix.android.internal.auth.wellknown.DirectLoginTask
-import im.vector.matrix.android.internal.auth.wellknown.GetWellknownTask
+import im.vector.matrix.android.internal.auth.login.DefaultDirectLoginTask
+import im.vector.matrix.android.internal.auth.login.DirectLoginTask
import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.di.AuthDatabase
+import im.vector.matrix.android.internal.wellknown.WellknownModule
import io.realm.RealmConfiguration
import java.io.File
-@Module
+@Module(includes = [WellknownModule::class])
internal abstract class AuthModule {
@Module
@@ -74,9 +73,6 @@ internal abstract class AuthModule {
@Binds
abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator
- @Binds
- abstract fun bindGetWellknownTask(task: DefaultGetWellknownTask): GetWellknownTask
-
@Binds
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
}
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 997cf70e5a..b543fa7507 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
@@ -23,7 +23,6 @@ import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.LoginFlowResult
-import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.data.Versions
import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
import im.vector.matrix.android.api.auth.data.isSupportedBySdk
@@ -33,14 +32,14 @@ import im.vector.matrix.android.api.auth.wellknown.WellknownResult
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable
+import im.vector.matrix.android.api.util.NoOpCancellable
import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
import im.vector.matrix.android.internal.auth.data.RiotConfig
import im.vector.matrix.android.internal.auth.db.PendingSessionData
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
+import im.vector.matrix.android.internal.auth.login.DirectLoginTask
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
-import im.vector.matrix.android.internal.auth.wellknown.DirectLoginTask
-import im.vector.matrix.android.internal.auth.wellknown.GetWellknownTask
import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.network.executeRequest
@@ -50,7 +49,7 @@ import im.vector.matrix.android.internal.task.launchToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.exhaustive
import im.vector.matrix.android.internal.util.toCancelable
-import kotlinx.coroutines.GlobalScope
+import im.vector.matrix.android.internal.wellknown.GetWellknownTask
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
@@ -87,14 +86,21 @@ internal class DefaultAuthenticationService @Inject constructor(
}
}
- override fun getSession(sessionParams: SessionParams): Session? {
- return sessionManager.getOrCreateSession(sessionParams)
+ override fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback): Cancelable {
+ val homeServerConnectionConfig = sessionParamsStore.get(sessionId)?.homeServerConnectionConfig
+
+ return if (homeServerConnectionConfig == null) {
+ callback.onFailure(IllegalStateException("Session not found"))
+ NoOpCancellable
+ } else {
+ getLoginFlow(homeServerConnectionConfig, callback)
+ }
}
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback): Cancelable {
pendingSessionData = null
- return GlobalScope.launch(coroutineDispatchers.main) {
+ return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
pendingSessionStore.delete()
val result = runCatching {
@@ -246,7 +252,8 @@ internal class DefaultAuthenticationService @Inject constructor(
retrofitFactory,
coroutineDispatchers,
sessionCreator,
- pendingSessionStore
+ pendingSessionStore,
+ taskExecutor.executorScope
).also {
currentRegistrationWizard = it
}
@@ -266,7 +273,8 @@ internal class DefaultAuthenticationService @Inject constructor(
retrofitFactory,
coroutineDispatchers,
sessionCreator,
- pendingSessionStore
+ pendingSessionStore,
+ taskExecutor.executorScope
).also {
currentLoginWizard = it
}
@@ -283,7 +291,7 @@ internal class DefaultAuthenticationService @Inject constructor(
pendingSessionData = pendingSessionData?.homeServerConnectionConfig
?.let { PendingSessionData(it) }
.also {
- GlobalScope.launch(coroutineDispatchers.main) {
+ taskExecutor.executorScope.launch(coroutineDispatchers.main) {
if (it == null) {
// Should not happen
pendingSessionStore.delete()
@@ -300,7 +308,7 @@ internal class DefaultAuthenticationService @Inject constructor(
pendingSessionData = null
- GlobalScope.launch(coroutineDispatchers.main) {
+ taskExecutor.executorScope.launch(coroutineDispatchers.main) {
pendingSessionStore.delete()
}
}
@@ -308,7 +316,7 @@ internal class DefaultAuthenticationService @Inject constructor(
override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials,
callback: MatrixCallback): Cancelable {
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
createSessionFromSso(credentials, homeServerConnectionConfig)
}
}
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 ebd50a6924..a4db0e84f7 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
@@ -51,7 +51,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
}
return SessionParamsEntity(
sessionParams.credentials.sessionId(),
- sessionParams.credentials.userId,
+ sessionParams.userId,
credentialsJson,
homeServerConnectionConfigJson,
sessionParams.isTokenValid)
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 4d98ddcf08..132073b340 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
@@ -38,7 +38,7 @@ import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.launchToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
-import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
@@ -47,7 +47,8 @@ internal class DefaultLoginWizard(
retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionCreator: SessionCreator,
- private val pendingSessionStore: PendingSessionStore
+ private val pendingSessionStore: PendingSessionStore,
+ private val coroutineScope: CoroutineScope
) : LoginWizard {
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
@@ -59,7 +60,7 @@ internal class DefaultLoginWizard(
password: String,
deviceName: String,
callback: MatrixCallback): Cancelable {
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
loginInternal(login, password, deviceName)
}
}
@@ -80,7 +81,7 @@ internal class DefaultLoginWizard(
}
override fun resetPassword(email: String, newPassword: String, callback: MatrixCallback): Cancelable {
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
resetPasswordInternal(email, newPassword)
}
}
@@ -108,7 +109,7 @@ internal class DefaultLoginWizard(
callback.onFailure(IllegalStateException("developer error, no reset password in progress"))
return NoOpCancellable
}
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
resetPasswordMailConfirmedInternal(safeResetPasswordData)
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DirectLoginTask.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/DirectLoginTask.kt
rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DirectLoginTask.kt
index 01a3ab192d..90eddf2e14 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/DirectLoginTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DirectLoginTask.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package im.vector.matrix.android.internal.auth.wellknown
+package im.vector.matrix.android.internal.auth.login
import dagger.Lazy
import im.vector.matrix.android.api.auth.data.Credentials
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationWizard.kt
index 29970b6c0c..5a39de72ca 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationWizard.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationWizard.kt
@@ -33,7 +33,7 @@ import im.vector.matrix.android.internal.auth.db.PendingSessionData
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.task.launchToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
-import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import okhttp3.OkHttpClient
@@ -45,7 +45,8 @@ internal class DefaultRegistrationWizard(
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionCreator: SessionCreator,
- private val pendingSessionStore: PendingSessionStore
+ private val pendingSessionStore: PendingSessionStore,
+ private val coroutineScope: CoroutineScope
) : RegistrationWizard {
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
@@ -72,7 +73,7 @@ internal class DefaultRegistrationWizard(
override fun getRegistrationFlow(callback: MatrixCallback): Cancelable {
val params = RegistrationParams()
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
}
}
@@ -86,7 +87,7 @@ internal class DefaultRegistrationWizard(
password = password,
initialDeviceDisplayName = initialDeviceDisplayName
)
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
.also {
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
@@ -101,7 +102,7 @@ internal class DefaultRegistrationWizard(
return NoOpCancellable
}
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
}
}
@@ -112,13 +113,13 @@ internal class DefaultRegistrationWizard(
return NoOpCancellable
}
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
}
}
override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback): Cancelable {
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
.also { pendingSessionStore.savePendingSessionData(it) }
@@ -131,7 +132,7 @@ internal class DefaultRegistrationWizard(
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
sendThreePid(safeCurrentThreePid)
}
}
@@ -177,13 +178,13 @@ internal class DefaultRegistrationWizard(
callback.onFailure(IllegalStateException("developer error, no pending three pid"))
return NoOpCancellable
}
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(safeParam, delayMillis)
}
}
override fun handleValidateThreePid(code: String, callback: MatrixCallback): Cancelable {
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
validateThreePid(code)
}
}
@@ -199,7 +200,7 @@ internal class DefaultRegistrationWizard(
code = code
)
val validationResponse = validateCodeTask.execute(ValidateCodeTask.Params(url, validationBody))
- if (validationResponse.success == true) {
+ if (validationResponse.isSuccess()) {
// The entered code is correct
// Same than validate email
return performRegistrationRequest(registrationParams, 3_000)
@@ -214,7 +215,7 @@ internal class DefaultRegistrationWizard(
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
performRegistrationRequest(params)
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/LocalizedFlowDataLoginTerms.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/LocalizedFlowDataLoginTerms.kt
index 2cd52f702e..5bdc9579e0 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/LocalizedFlowDataLoginTerms.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/LocalizedFlowDataLoginTerms.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.matrix.androidsdk.rest.model.login
+package im.vector.matrix.android.internal.auth.registration
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/SuccessResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/SuccessResult.kt
index 8bfa3dda1d..1d19d1a5e5 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/SuccessResult.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/SuccessResult.kt
@@ -18,9 +18,12 @@ package im.vector.matrix.android.internal.auth.registration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
+import im.vector.matrix.android.api.extensions.orFalse
@JsonClass(generateAdapter = true)
data class SuccessResult(
@Json(name = "success")
val success: Boolean?
-)
+) {
+ fun isSuccess() = success.orFalse()
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt
index e83895709e..19243f1a23 100755
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt
@@ -199,7 +199,7 @@ internal object MXEncryptedAttachments {
.replace('_', '/')
}
- private fun base64ToBase64Url(base64: String): String {
+ internal fun base64ToBase64Url(base64: String): String {
return base64.replace("\n".toRegex(), "")
.replace("\\+".toRegex(), "-")
.replace('/', '_')
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/Tools.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/Tools.kt
index c3d2c30079..5e406fdc4f 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/Tools.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/Tools.kt
@@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.tools
import org.matrix.olm.OlmPkDecryption
import org.matrix.olm.OlmPkEncryption
import org.matrix.olm.OlmPkSigning
+import org.matrix.olm.OlmUtility
fun withOlmEncryption(block: (OlmPkEncryption) -> T): T {
val olmPkEncryption = OlmPkEncryption()
@@ -46,3 +47,12 @@ fun withOlmSigning(block: (OlmPkSigning) -> T): T {
olmPkSigning.releaseSigning()
}
}
+
+fun withOlmUtility(block: (OlmUtility) -> T): T {
+ val olmUtility = OlmUtility()
+ try {
+ return block(olmUtility)
+ } finally {
+ olmUtility.releaseUtility()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
index 689829b8e3..1480029d6d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
@@ -121,7 +121,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
// }
//
// val requestMessage = KeyVerificationRequest(
-// fromDevice = session.sessionParams.credentials.deviceId ?: "",
+// fromDevice = session.sessionParams.deviceId ?: "",
// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
// timestamp = System.currentTimeMillis().toInt(),
// transactionId = transactionId
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt
index 04a3560223..7479c55aa3 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt
@@ -81,9 +81,9 @@ import im.vector.matrix.android.internal.crypto.verification.qrcode.generateShar
import im.vector.matrix.android.internal.di.DeviceId
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.SessionScope
+import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.UUID
@@ -104,7 +104,8 @@ internal class DefaultVerificationService @Inject constructor(
private val verificationTransportRoomMessageFactory: VerificationTransportRoomMessageFactory,
private val verificationTransportToDeviceFactory: VerificationTransportToDeviceFactory,
private val crossSigningService: CrossSigningService,
- private val cryptoCoroutineScope: CoroutineScope
+ private val cryptoCoroutineScope: CoroutineScope,
+ private val taskExecutor: TaskExecutor
) : DefaultVerificationTransaction.Listener, VerificationService {
private val uiHandler = Handler(Looper.getMainLooper())
@@ -161,7 +162,7 @@ internal class DefaultVerificationService @Inject constructor(
}
fun onRoomEvent(event: Event) {
- GlobalScope.launch(coroutineDispatchers.crypto) {
+ cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
when (event.getClearType()) {
EventType.KEY_VERIFICATION_START -> {
onRoomStartRequestReceived(event)
@@ -301,7 +302,7 @@ internal class DefaultVerificationService @Inject constructor(
// We don't want to block here
val otherDeviceId = validRequestInfo.fromDevice
- GlobalScope.launch {
+ cryptoCoroutineScope.launch {
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
Timber.e("## Verification device $otherDeviceId is not known")
}
@@ -340,7 +341,7 @@ internal class DefaultVerificationService @Inject constructor(
}
// We don't want to block here
- GlobalScope.launch {
+ taskExecutor.executorScope.launch {
if (checkKeysAreDownloaded(senderId, validRequestInfo.fromDevice) == null) {
Timber.e("## SAS Verification device ${validRequestInfo.fromDevice} is not known")
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt
index 77234e82f4..e0bbffc23b 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt
@@ -48,10 +48,11 @@ import im.vector.matrix.android.internal.di.SessionId
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.di.WorkManagerProvider
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
+import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.StringProvider
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.UUID
@@ -66,7 +67,8 @@ internal class VerificationTransportRoomMessage(
private val userDeviceId: String?,
private val roomId: String,
private val localEchoEventFactory: LocalEchoEventFactory,
- private val tx: DefaultVerificationTransaction?
+ private val tx: DefaultVerificationTransaction?,
+ private val coroutineScope: CoroutineScope
) : VerificationTransport {
override fun sendToOther(type: String,
@@ -131,7 +133,7 @@ internal class VerificationTransportRoomMessage(
}
// TODO listen to DB to get synced info
- GlobalScope.launch(Dispatchers.Main) {
+ coroutineScope.launch(Dispatchers.Main) {
workLiveData.observeForever(observer)
}
}
@@ -212,7 +214,7 @@ internal class VerificationTransportRoomMessage(
}
// TODO listen to DB to get synced info
- GlobalScope.launch(Dispatchers.Main) {
+ coroutineScope.launch(Dispatchers.Main) {
workLiveData.observeForever(observer)
}
}
@@ -265,7 +267,7 @@ internal class VerificationTransportRoomMessage(
}
// TODO listen to DB to get synced info
- GlobalScope.launch(Dispatchers.Main) {
+ coroutineScope.launch(Dispatchers.Main) {
workLiveData.observeForever(observer)
}
}
@@ -384,9 +386,19 @@ internal class VerificationTransportRoomMessageFactory @Inject constructor(
private val userId: String,
@DeviceId
private val deviceId: String?,
- private val localEchoEventFactory: LocalEchoEventFactory) {
+ private val localEchoEventFactory: LocalEchoEventFactory,
+ private val taskExecutor: TaskExecutor
+) {
fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage {
- return VerificationTransportRoomMessage(workManagerProvider, stringProvider, sessionId, userId, deviceId, roomId, localEchoEventFactory, tx)
+ return VerificationTransportRoomMessage(workManagerProvider,
+ stringProvider,
+ sessionId,
+ userId,
+ deviceId,
+ roomId,
+ localEchoEventFactory,
+ tx,
+ taskExecutor.executorScope)
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt
index a0d3662e03..a66f587cec 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt
@@ -20,21 +20,16 @@ import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity
/**
- * HomeServerCapabilitiesEntity <-> HomeSeverCapabilities
+ * HomeServerCapabilitiesEntity -> HomeSeverCapabilities
*/
internal object HomeServerCapabilitiesMapper {
fun map(entity: HomeServerCapabilitiesEntity): HomeServerCapabilities {
return HomeServerCapabilities(
canChangePassword = entity.canChangePassword,
- maxUploadFileSize = entity.maxUploadFileSize
- )
- }
-
- fun map(domain: HomeServerCapabilities): HomeServerCapabilitiesEntity {
- return HomeServerCapabilitiesEntity(
- canChangePassword = domain.canChangePassword,
- maxUploadFileSize = domain.maxUploadFileSize
+ maxUploadFileSize = entity.maxUploadFileSize,
+ lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
+ defaultIdentityServerUrl = entity.defaultIdentityServerUrl
)
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt
index 5743597a61..a6b250b8fa 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt
@@ -22,6 +22,8 @@ import io.realm.RealmObject
internal open class HomeServerCapabilitiesEntity(
var canChangePassword: Boolean = true,
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
+ var lastVersionIdentityServerSupported: Boolean = false,
+ var defaultIdentityServerUrl: String? = null,
var lastUpdatedTimestamp: Long = 0L
) : RealmObject() {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt
index 47ae0199cf..03cdbfdf4e 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt
@@ -37,6 +37,7 @@ import io.realm.annotations.RealmModule
UserEntity::class,
IgnoredUserEntity::class,
BreadcrumbsEntity::class,
+ UserThreePidEntity::class,
EventAnnotationsSummaryEntity::class,
ReactionAggregatedSummaryEntity::class,
EditAggregatedSummaryEntity::class,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/UserThreePidEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/UserThreePidEntity.kt
new file mode 100644
index 0000000000..f41ac1baa7
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/UserThreePidEntity.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.matrix.android.internal.database.model
+
+import io.realm.RealmObject
+
+internal open class UserThreePidEntity(
+ var medium: String = "",
+ var address: String = "",
+ var validatedAt: Long = 0,
+ var addedAt: Long = 0
+) : RealmObject()
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt
index 5168d0728e..f798dbcf41 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt
@@ -62,8 +62,8 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
val liveEvents = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes)
if (filterContentRelation) {
liveEvents
- ?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE)
- ?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.RESPONSE_TYPE)
+ ?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
+ ?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
}
val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) {
sendingTimelineEvents
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventFilter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventFilter.kt
new file mode 100644
index 0000000000..ea8122bc6d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventFilter.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.internal.database.query
+
+/**
+ * Query strings used to filter the timeline events regarding the Json raw string of the Event
+ */
+internal object TimelineEventFilter {
+ /**
+ * To apply to Event.content
+ */
+ internal object Content {
+ internal const val EDIT = """{*"m.relates_to"*"rel_type":*"m.replace"*}"""
+ internal const val RESPONSE = """{*"m.relates_to"*"rel_type":*"m.response"*}"""
+ }
+
+ /**
+ * To apply to Event.unsigned
+ */
+ internal object Unsigned {
+ internal const val REDACTED = """{*"redacted_because":*}"""
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/AuthQualifiers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/AuthQualifiers.kt
index 8ee27b3375..0ceb94caa7 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/AuthQualifiers.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/AuthQualifiers.kt
@@ -20,8 +20,12 @@ import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
-annotation class Authenticated
+internal annotation class Authenticated
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
-annotation class Unauthenticated
+internal annotation class AuthenticatedIdentity
+
+@Qualifier
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class Unauthenticated
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/DbQualifiers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/DbQualifiers.kt
index 3fdeb7eacc..4501ae5746 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/DbQualifiers.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/DbQualifiers.kt
@@ -20,12 +20,16 @@ import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
-annotation class AuthDatabase
+internal annotation class AuthDatabase
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
-annotation class SessionDatabase
+internal annotation class SessionDatabase
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
-annotation class CryptoDatabase
+internal annotation class CryptoDatabase
+
+@Qualifier
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class IdentityDatabase
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/FileQualifiers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/FileQualifiers.kt
index aa39fc6fe8..5dfc04539a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/FileQualifiers.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/FileQualifiers.kt
@@ -20,16 +20,16 @@ import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
-annotation class SessionFilesDirectory
+internal annotation class SessionFilesDirectory
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
-annotation class SessionCacheDirectory
+internal annotation class SessionCacheDirectory
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
-annotation class CacheDirectory
+internal annotation class CacheDirectory
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
-annotation class ExternalFilesDirectory
+internal annotation class ExternalFilesDirectory
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 c802d4b63a..a15f660790 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
@@ -16,20 +16,16 @@
package im.vector.matrix.android.internal.network
-import im.vector.matrix.android.internal.auth.SessionParamsStore
-import im.vector.matrix.android.internal.di.SessionId
+import im.vector.matrix.android.internal.network.token.AccessTokenProvider
import okhttp3.Interceptor
import okhttp3.Response
-import javax.inject.Inject
-internal class AccessTokenInterceptor @Inject constructor(
- @SessionId private val sessionId: String,
- private val sessionParamsStore: SessionParamsStore) : Interceptor {
+internal class AccessTokenInterceptor(private val accessTokenProvider: AccessTokenProvider) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
- accessToken?.let {
+ accessTokenProvider.getToken()?.let {
val newRequestBuilder = request.newBuilder()
// Add the access token to all requests if it is set
newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer $it")
@@ -38,7 +34,4 @@ internal class AccessTokenInterceptor @Inject constructor(
return chain.proceed(request)
}
-
- private val accessToken
- get() = sessionParamsStore.get(sessionId)?.credentials?.accessToken
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt
index ab6745148f..56e6ee0953 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt
@@ -28,8 +28,9 @@ internal object NetworkConstants {
const val URI_API_MEDIA_PREFIX_PATH_R0 = "$URI_API_MEDIA_PREFIX_PATH/r0/"
// Identity server
- const val URI_IDENTITY_PATH = "_matrix/identity/api/v1/"
- const val URI_IDENTITY_PATH_V2 = "_matrix/identity/v2/"
+ const val URI_IDENTITY_PREFIX_PATH = "_matrix/identity/v2"
+ const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/"
- const val URI_API_PREFIX_IDENTITY = "_matrix/identity/api/v1"
+ // TODO Ganfra, use correct value
+ const val URI_INTEGRATION_MANAGER_PATH = "TODO/"
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/httpclient/OkHttpClientUtil.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/httpclient/OkHttpClientUtil.kt
new file mode 100644
index 0000000000..8ffa0553e9
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/httpclient/OkHttpClientUtil.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.network.httpclient
+
+import im.vector.matrix.android.internal.network.AccessTokenInterceptor
+import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
+import im.vector.matrix.android.internal.network.token.AccessTokenProvider
+import okhttp3.OkHttpClient
+
+internal fun OkHttpClient.addAccessTokenInterceptor(accessTokenProvider: AccessTokenProvider): OkHttpClient {
+ return newBuilder()
+ .apply {
+ // Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor
+ val existingCurlInterceptors = interceptors().filterIsInstance()
+ interceptors().removeAll(existingCurlInterceptors)
+
+ addInterceptor(AccessTokenInterceptor(accessTokenProvider))
+
+ // Re add eventually the curl logging interceptors
+ existingCurlInterceptors.forEach {
+ addInterceptor(it)
+ }
+ }
+ .build()
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/token/AccessTokenProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/token/AccessTokenProvider.kt
new file mode 100644
index 0000000000..08176392fa
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/token/AccessTokenProvider.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.network.token
+
+internal interface AccessTokenProvider {
+ fun getToken(): String?
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/token/HomeserverAccessTokenProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/token/HomeserverAccessTokenProvider.kt
new file mode 100644
index 0000000000..b570cb362e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/token/HomeserverAccessTokenProvider.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.network.token
+
+import im.vector.matrix.android.internal.auth.SessionParamsStore
+import im.vector.matrix.android.internal.di.SessionId
+import javax.inject.Inject
+
+internal class HomeserverAccessTokenProvider @Inject constructor(
+ @SessionId private val sessionId: String,
+ private val sessionParamsStore: SessionParamsStore
+) : AccessTokenProvider {
+ override fun getToken() = sessionParamsStore.get(sessionId)?.credentials?.accessToken
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt
index cfb0d23f2b..0cdd39f117 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt
@@ -28,10 +28,10 @@ import im.vector.matrix.android.internal.di.ExternalFilesDirectory
import im.vector.matrix.android.internal.di.SessionCacheDirectory
import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.extensions.foldToCallback
+import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.toCancelable
import im.vector.matrix.android.internal.util.writeToFile
-import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
@@ -51,7 +51,9 @@ internal class DefaultFileService @Inject constructor(
private val contentUrlResolver: ContentUrlResolver,
@Unauthenticated
private val okHttpClient: OkHttpClient,
- private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService {
+ private val coroutineDispatchers: MatrixCoroutineDispatchers,
+ private val taskExecutor: TaskExecutor
+) : FileService {
/**
* Download file in the cache folder, and eventually decrypt it
@@ -63,7 +65,7 @@ internal class DefaultFileService @Inject constructor(
url: String?,
elementToDecrypt: ElementToDecrypt?,
callback: MatrixCallback): Cancelable {
- return GlobalScope.launch(coroutineDispatchers.main) {
+ return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
withContext(coroutineDispatchers.io) {
Try {
val folder = File(sessionCacheDirectory, "MF")
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 aa9df9c496..0052c1d7a2 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
@@ -43,6 +43,7 @@ import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageSer
import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.sync.SyncState
+import im.vector.matrix.android.api.session.terms.TermsService
import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.api.session.widgets.WidgetService
import im.vector.matrix.android.internal.auth.SessionParamsStore
@@ -51,14 +52,16 @@ import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater
import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.di.SessionId
import im.vector.matrix.android.internal.di.WorkManagerProvider
+import im.vector.matrix.android.internal.session.identity.DefaultIdentityService
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecryptor
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.job.SyncThread
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import im.vector.matrix.android.internal.session.widgets.WidgetManager
+import im.vector.matrix.android.internal.task.TaskExecutor
+import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
@@ -85,6 +88,7 @@ internal class DefaultSession @Inject constructor(
private val signOutService: Lazy,
private val pushRuleService: Lazy,
private val pushersService: Lazy,
+ private val termsService: Lazy,
private val cryptoService: Lazy,
private val fileService: Lazy,
private val secureStorageService: Lazy,
@@ -103,8 +107,11 @@ internal class DefaultSession @Inject constructor(
private val timelineEventDecryptor: TimelineEventDecryptor,
private val integrationManager: IntegrationManager,
private val widgetManager: WidgetManager,
- private val shieldTrustUpdater: ShieldTrustUpdater)
- : Session,
+ private val shieldTrustUpdater: ShieldTrustUpdater,
+ private val coroutineDispatchers: MatrixCoroutineDispatchers,
+ private val defaultIdentityService: DefaultIdentityService,
+ private val taskExecutor: TaskExecutor
+) : Session,
RoomService by roomService.get(),
RoomDirectoryService by roomDirectoryService.get(),
GroupService by groupService.get(),
@@ -114,6 +121,7 @@ internal class DefaultSession @Inject constructor(
PushRuleService by pushRuleService.get(),
PushersService by pushersService.get(),
FileService by fileService.get(),
+ TermsService by termsService.get(),
InitialSyncProgressService by initialSyncProgressService.get(),
SecureStorageService by secureStorageService.get(),
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
@@ -142,6 +150,7 @@ internal class DefaultSession @Inject constructor(
shieldTrustUpdater.start()
integrationManager.start()
widgetManager.start()
+ defaultIdentityService.start()
}
override fun requireBackgroundSync() {
@@ -186,6 +195,10 @@ internal class DefaultSession @Inject constructor(
shieldTrustUpdater.stop()
integrationManager.stop()
widgetManager.stop()
+ taskExecutor.executorScope.launch(coroutineDispatchers.main) {
+ // This has to be done on main thread
+ defaultIdentityService.stop()
+ }
}
override fun getSyncStateLive(): LiveData {
@@ -215,7 +228,7 @@ internal class DefaultSession @Inject constructor(
if (globalError is GlobalError.InvalidToken
&& globalError.softLogout) {
// Mark the token has invalid
- GlobalScope.launch(Dispatchers.IO) {
+ taskExecutor.executorScope.launch(Dispatchers.IO) {
sessionParamsStore.setTokenInvalid(sessionId)
}
}
@@ -229,6 +242,8 @@ internal class DefaultSession @Inject constructor(
override fun cryptoService(): CryptoService = cryptoService.get()
+ override fun identityService() = defaultIdentityService
+
override fun addListener(listener: Session.Listener) {
sessionListeners.addListener(listener)
}
@@ -239,6 +254,6 @@ internal class DefaultSession @Inject constructor(
// For easy debugging
override fun toString(): String {
- return "$myUserId - ${sessionParams.credentials.deviceId}"
+ return "$myUserId - ${sessionParams.deviceId}"
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt
index a99c2ba0aa..3fd8472edf 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt
@@ -36,6 +36,7 @@ import im.vector.matrix.android.internal.session.filter.FilterModule
import im.vector.matrix.android.internal.session.group.GetGroupDataWorker
import im.vector.matrix.android.internal.session.group.GroupModule
import im.vector.matrix.android.internal.session.homeserver.HomeServerCapabilitiesModule
+import im.vector.matrix.android.internal.session.identity.IdentityModule
import im.vector.matrix.android.internal.session.openid.OpenIdModule
import im.vector.matrix.android.internal.session.profile.ProfileModule
import im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker
@@ -51,6 +52,7 @@ import im.vector.matrix.android.internal.session.sync.SyncModule
import im.vector.matrix.android.internal.session.sync.SyncTask
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
+import im.vector.matrix.android.internal.session.terms.TermsModule
import im.vector.matrix.android.internal.session.user.UserModule
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule
import im.vector.matrix.android.internal.session.widgets.WidgetModule
@@ -74,6 +76,8 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
PushersModule::class,
OpenIdModule::class,
WidgetModule::class,
+ IdentityModule::class,
+ TermsModule::class,
AccountDataModule::class,
ProfileModule::class,
SessionAssistedInjectModule::class,
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 8bdfff062f..9d5772b82a 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
@@ -50,14 +50,15 @@ import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.di.UserMd5
import im.vector.matrix.android.internal.eventbus.EventBusTimberLogger
-import im.vector.matrix.android.internal.network.AccessTokenInterceptor
import im.vector.matrix.android.internal.network.DefaultNetworkConnectivityChecker
import im.vector.matrix.android.internal.network.FallbackNetworkCallbackStrategy
import im.vector.matrix.android.internal.network.NetworkCallbackStrategy
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.network.PreferredNetworkCallbackStrategy
import im.vector.matrix.android.internal.network.RetrofitFactory
-import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
+import im.vector.matrix.android.internal.network.httpclient.addAccessTokenInterceptor
+import im.vector.matrix.android.internal.network.token.AccessTokenProvider
+import im.vector.matrix.android.internal.network.token.HomeserverAccessTokenProvider
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
import im.vector.matrix.android.internal.session.homeserver.DefaultHomeServerCapabilitiesService
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
@@ -175,21 +176,8 @@ internal abstract class SessionModule {
@SessionScope
@Authenticated
fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
- accessTokenInterceptor: AccessTokenInterceptor): OkHttpClient {
- return okHttpClient.newBuilder()
- .apply {
- // Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor
- val existingCurlInterceptors = interceptors().filterIsInstance()
- interceptors().removeAll(existingCurlInterceptors)
-
- addInterceptor(accessTokenInterceptor)
-
- // Re add eventually the curl logging interceptors
- existingCurlInterceptors.forEach {
- addInterceptor(it)
- }
- }
- .build()
+ @Authenticated accessTokenProvider: AccessTokenProvider): OkHttpClient {
+ return okHttpClient.addAccessTokenInterceptor(accessTokenProvider)
}
@JvmStatic
@@ -233,6 +221,10 @@ internal abstract class SessionModule {
}
}
+ @Binds
+ @Authenticated
+ abstract fun bindAccessTokenProvider(provider: HomeserverAccessTokenProvider): AccessTokenProvider
+
@Binds
abstract fun bindSession(session: DefaultSession): Session
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/account/DeactivateAccountTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/account/DeactivateAccountTask.kt
index f5b105cfee..b993534ba1 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/account/DeactivateAccountTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/account/DeactivateAccountTask.kt
@@ -19,8 +19,10 @@ package im.vector.matrix.android.internal.session.account
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.cleanup.CleanupSession
+import im.vector.matrix.android.internal.session.identity.IdentityDisconnectTask
import im.vector.matrix.android.internal.task.Task
import org.greenrobot.eventbus.EventBus
+import timber.log.Timber
import javax.inject.Inject
internal interface DeactivateAccountTask : Task {
@@ -34,6 +36,7 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
private val accountAPI: AccountAPI,
private val eventBus: EventBus,
@UserId private val userId: String,
+ private val identityDisconnectTask: IdentityDisconnectTask,
private val cleanupSession: CleanupSession
) : DeactivateAccountTask {
@@ -44,6 +47,10 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
apiCall = accountAPI.deactivate(deactivateAccountParams)
}
+ // Logout from identity server if any, ignoring errors
+ runCatching { identityDisconnectTask.execute(Unit) }
+ .onFailure { Timber.w(it, "Unable to disconnect identity server") }
+
cleanupSession.handle()
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/CapabilitiesAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/CapabilitiesAPI.kt
index f37bbfe798..880a8fbc31 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/CapabilitiesAPI.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/CapabilitiesAPI.kt
@@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.session.homeserver
+import im.vector.matrix.android.api.auth.data.Versions
import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.GET
@@ -38,5 +39,11 @@ internal interface CapabilitiesAPI {
* Request the versions
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions")
- fun getVersions(): Call
+ fun getVersions(): Call
+
+ /**
+ * Ping the homeserver. We do not care about the returned data, so there is no use to parse them
+ */
+ @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions")
+ fun ping(): Call
}
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 9f068381f0..be5b0d3949 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
@@ -17,9 +17,14 @@
package im.vector.matrix.android.internal.session.homeserver
import com.zhuinden.monarchy.Monarchy
+import im.vector.matrix.android.api.auth.data.Versions
+import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
+import im.vector.matrix.android.api.auth.wellknown.WellknownResult
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
+import im.vector.matrix.android.internal.wellknown.GetWellknownTask
import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity
import im.vector.matrix.android.internal.database.query.getOrCreate
+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 im.vector.matrix.android.internal.util.awaitTransaction
@@ -32,7 +37,10 @@ internal interface GetHomeServerCapabilitiesTask : Task
internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
private val capabilitiesAPI: CapabilitiesAPI,
private val monarchy: Monarchy,
- private val eventBus: EventBus
+ private val eventBus: EventBus,
+ private val getWellknownTask: GetWellknownTask,
+ @UserId
+ private val userId: String
) : GetHomeServerCapabilitiesTask {
override suspend fun execute(params: Unit) {
@@ -47,29 +55,54 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
return
}
- val uploadCapabilities = executeRequest(eventBus) {
- apiCall = capabilitiesAPI.getUploadCapabilities()
- }
-
val capabilities = runCatching {
executeRequest(eventBus) {
apiCall = capabilitiesAPI.getCapabilities()
}
}.getOrNull()
- // TODO Add other call here (get version, etc.)
+ val uploadCapabilities = runCatching {
+ executeRequest(eventBus) {
+ apiCall = capabilitiesAPI.getUploadCapabilities()
+ }
+ }.getOrNull()
- insertInDb(capabilities, uploadCapabilities)
+ val versions = runCatching {
+ executeRequest(null) {
+ apiCall = capabilitiesAPI.getVersions()
+ }
+ }.getOrNull()
+
+ val wellknownResult = runCatching {
+ getWellknownTask.execute(GetWellknownTask.Params(userId))
+ }.getOrNull()
+
+ insertInDb(capabilities, uploadCapabilities, versions, wellknownResult)
}
- private suspend fun insertInDb(getCapabilitiesResult: GetCapabilitiesResult?, getUploadCapabilitiesResult: GetUploadCapabilitiesResult) {
+ private suspend fun insertInDb(getCapabilitiesResult: GetCapabilitiesResult?,
+ getUploadCapabilitiesResult: GetUploadCapabilitiesResult?,
+ getVersionResult: Versions?,
+ getWellknownResult: WellknownResult?) {
monarchy.awaitTransaction { realm ->
val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm)
- homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword()
+ if (getCapabilitiesResult != null) {
+ homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword()
+ }
- homeServerCapabilitiesEntity.maxUploadFileSize = getUploadCapabilitiesResult.maxUploadSize
- ?: HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN
+ if (getUploadCapabilitiesResult != null) {
+ homeServerCapabilitiesEntity.maxUploadFileSize = getUploadCapabilitiesResult.maxUploadSize
+ ?: HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN
+ }
+
+ if (getVersionResult != null) {
+ homeServerCapabilitiesEntity.lastVersionIdentityServerSupported = getVersionResult.isLoginAndRegistrationSupportedBySdk()
+ }
+
+ if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
+ homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl
+ }
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/GetCapabilitiesResult.kt
index ffaa998789..9a625571a8 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/GetCapabilitiesResult.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/GetCapabilitiesResult.kt
@@ -53,6 +53,6 @@ internal data class ChangePassword(
)
// The spec says: If not present, the client should assume that password changes are possible via the API
-internal fun GetCapabilitiesResult?.canChangePassword(): Boolean {
- return this?.capabilities?.changePassword?.enabled.orTrue()
+internal fun GetCapabilitiesResult.canChangePassword(): Boolean {
+ return capabilities?.changePassword?.enabled.orTrue()
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/HomeServerCapabilitiesModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/HomeServerCapabilitiesModule.kt
index f2b249ab0c..91ec3fe305 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/HomeServerCapabilitiesModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/HomeServerCapabilitiesModule.kt
@@ -20,9 +20,10 @@ import dagger.Binds
import dagger.Module
import dagger.Provides
import im.vector.matrix.android.internal.session.SessionScope
+import im.vector.matrix.android.internal.wellknown.WellknownModule
import retrofit2.Retrofit
-@Module
+@Module(includes = [WellknownModule::class])
internal abstract class HomeServerCapabilitiesModule {
@Module
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/HomeServerPinger.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/HomeServerPinger.kt
index e220c0064d..705deb4e57 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/HomeServerPinger.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/HomeServerPinger.kt
@@ -35,7 +35,7 @@ internal class HomeServerPinger @Inject constructor(private val taskExecutor: Ta
suspend fun canReachHomeServer(): Boolean {
return try {
executeRequest(null) {
- apiCall = capabilitiesAPI.getVersions()
+ apiCall = capabilitiesAPI.ping()
}
true
} catch (throwable: Throwable) {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt
new file mode 100644
index 0000000000..1a271e659e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt
@@ -0,0 +1,341 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import dagger.Lazy
+import im.vector.matrix.android.api.MatrixCallback
+import im.vector.matrix.android.api.auth.data.SessionParams
+import im.vector.matrix.android.api.extensions.tryThis
+import im.vector.matrix.android.api.failure.Failure
+import im.vector.matrix.android.api.failure.MatrixError
+import im.vector.matrix.android.api.session.events.model.toModel
+import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
+import im.vector.matrix.android.api.session.identity.FoundThreePid
+import im.vector.matrix.android.api.session.identity.IdentityService
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
+import im.vector.matrix.android.api.session.identity.IdentityServiceListener
+import im.vector.matrix.android.api.session.identity.SharedState
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.matrix.android.api.util.Cancelable
+import im.vector.matrix.android.api.util.NoOpCancellable
+import im.vector.matrix.android.internal.di.AuthenticatedIdentity
+import im.vector.matrix.android.internal.di.Unauthenticated
+import im.vector.matrix.android.internal.network.RetrofitFactory
+import im.vector.matrix.android.internal.session.SessionScope
+import im.vector.matrix.android.internal.session.identity.data.IdentityStore
+import im.vector.matrix.android.internal.session.identity.todelete.AccountDataDataSource
+import im.vector.matrix.android.internal.session.identity.todelete.observeNotNull
+import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
+import im.vector.matrix.android.internal.session.profile.BindThreePidsTask
+import im.vector.matrix.android.internal.session.profile.UnbindThreePidsTask
+import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
+import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
+import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
+import im.vector.matrix.android.internal.task.TaskExecutor
+import im.vector.matrix.android.internal.task.launchToCallback
+import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
+import im.vector.matrix.android.internal.util.ensureProtocol
+import kotlinx.coroutines.withContext
+import okhttp3.OkHttpClient
+import timber.log.Timber
+import javax.inject.Inject
+import javax.net.ssl.HttpsURLConnection
+
+@SessionScope
+internal class DefaultIdentityService @Inject constructor(
+ private val identityStore: IdentityStore,
+ private val getOpenIdTokenTask: GetOpenIdTokenTask,
+ private val identityBulkLookupTask: IdentityBulkLookupTask,
+ private val identityRegisterTask: IdentityRegisterTask,
+ private val identityPingTask: IdentityPingTask,
+ private val identityDisconnectTask: IdentityDisconnectTask,
+ private val identityRequestTokenForBindingTask: IdentityRequestTokenForBindingTask,
+ @Unauthenticated
+ private val unauthenticatedOkHttpClient: Lazy,
+ @AuthenticatedIdentity
+ private val okHttpClient: Lazy,
+ private val retrofitFactory: RetrofitFactory,
+ private val coroutineDispatchers: MatrixCoroutineDispatchers,
+ private val updateUserAccountDataTask: UpdateUserAccountDataTask,
+ private val bindThreePidsTask: BindThreePidsTask,
+ private val submitTokenForBindingTask: IdentitySubmitTokenForBindingTask,
+ private val unbindThreePidsTask: UnbindThreePidsTask,
+ private val identityApiProvider: IdentityApiProvider,
+ private val accountDataDataSource: AccountDataDataSource,
+ private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
+ private val sessionParams: SessionParams,
+ private val taskExecutor: TaskExecutor
+) : IdentityService {
+
+ private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
+ private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
+
+ private val listeners = mutableSetOf()
+
+ fun start() {
+ lifecycleRegistry.currentState = Lifecycle.State.STARTED
+ // Observe the account data change
+ accountDataDataSource
+ .getLiveAccountDataEvent(UserAccountData.TYPE_IDENTITY_SERVER)
+ .observeNotNull(lifecycleOwner) {
+ notifyIdentityServerUrlChange(it.getOrNull()?.content?.toModel()?.baseUrl)
+ }
+
+ // Init identityApi
+ updateIdentityAPI(identityStore.getIdentityData()?.identityServerUrl)
+ }
+
+ private fun notifyIdentityServerUrlChange(baseUrl: String?) {
+ // This is maybe not a real change (echo of account data we are just setting)
+ if (identityStore.getIdentityData()?.identityServerUrl == baseUrl) {
+ Timber.d("Echo of local identity server url change, or no change")
+ } else {
+ // Url has changed, we have to reset our store, update internal configuration and notify listeners
+ identityStore.setUrl(baseUrl)
+ updateIdentityAPI(baseUrl)
+ listeners.toList().forEach { tryThis { it.onIdentityServerChange() } }
+ }
+ }
+
+ fun stop() {
+ lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
+ }
+
+ /**
+ * First return the identity server provided during login phase.
+ * If null, provide the one in wellknown configuration of the homeserver
+ * Else return null
+ */
+ override fun getDefaultIdentityServer(): String? {
+ return sessionParams.defaultIdentityServerUrl
+ ?.takeIf { it.isNotEmpty() }
+ ?: homeServerCapabilitiesService.getHomeServerCapabilities().defaultIdentityServerUrl
+ }
+
+ override fun getCurrentIdentityServerUrl(): String? {
+ return identityStore.getIdentityData()?.identityServerUrl
+ }
+
+ override fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable {
+ if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) {
+ callback.onFailure(IdentityServiceError.OutdatedHomeServer)
+ return NoOpCancellable
+ }
+
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+ identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, false))
+ }
+ }
+
+ override fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable {
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+ identityStore.deletePendingBinding(threePid)
+ }
+ }
+
+ override fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback): Cancelable {
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+ identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, true))
+ }
+ }
+
+ override fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable {
+ if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) {
+ callback.onFailure(IdentityServiceError.OutdatedHomeServer)
+ return NoOpCancellable
+ }
+
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+ bindThreePidsTask.execute(BindThreePidsTask.Params(threePid))
+ }
+ }
+
+ override fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback): Cancelable {
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+ submitTokenForBindingTask.execute(IdentitySubmitTokenForBindingTask.Params(threePid, code))
+ }
+ }
+
+ override fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable {
+ if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) {
+ callback.onFailure(IdentityServiceError.OutdatedHomeServer)
+ return NoOpCancellable
+ }
+
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+ unbindThreePidsTask.execute(UnbindThreePidsTask.Params(threePid))
+ }
+ }
+
+ override fun isValidIdentityServer(url: String, callback: MatrixCallback): Cancelable {
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+ val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
+
+ identityPingTask.execute(IdentityPingTask.Params(api))
+ }
+ }
+
+ override fun disconnect(callback: MatrixCallback): Cancelable {
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+ identityDisconnectTask.execute(Unit)
+
+ identityStore.setUrl(null)
+ updateIdentityAPI(null)
+ updateAccountData(null)
+ }
+ }
+
+ override fun setNewIdentityServer(url: String, callback: MatrixCallback): Cancelable {
+ val urlCandidate = url.ensureProtocol()
+
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+ val current = getCurrentIdentityServerUrl()
+ if (urlCandidate == current) {
+ // Nothing to do
+ Timber.d("Same URL, nothing to do")
+ } else {
+ // Disconnect previous one if any, first, because the token will change.
+ // In case of error when configuring the new identity server, this is not a big deal,
+ // we will ask for a new token on the previous Identity server
+ runCatching { identityDisconnectTask.execute(Unit) }
+ .onFailure { Timber.w(it, "Unable to disconnect identity server") }
+
+ // Try to get a token
+ val token = getNewIdentityServerToken(urlCandidate)
+
+ identityStore.setUrl(urlCandidate)
+ identityStore.setToken(token)
+ updateIdentityAPI(urlCandidate)
+
+ updateAccountData(urlCandidate)
+ }
+ urlCandidate
+ }
+ }
+
+ private suspend fun updateAccountData(url: String?) {
+ // Also notify the listener
+ withContext(coroutineDispatchers.main) {
+ listeners.toList().forEach { tryThis { it.onIdentityServerChange() } }
+ }
+
+ updateUserAccountDataTask.execute(UpdateUserAccountDataTask.IdentityParams(
+ identityContent = IdentityServerContent(baseUrl = url)
+ ))
+ }
+
+ override fun lookUp(threePids: List, callback: MatrixCallback>): Cancelable {
+ if (threePids.isEmpty()) {
+ callback.onSuccess(emptyList())
+ return NoOpCancellable
+ }
+
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+ lookUpInternal(true, threePids)
+ }
+ }
+
+ override fun getShareStatus(threePids: List, callback: MatrixCallback>): Cancelable {
+ if (threePids.isEmpty()) {
+ callback.onSuccess(emptyMap())
+ return NoOpCancellable
+ }
+
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+ val lookupResult = lookUpInternal(true, threePids)
+
+ threePids.associateWith { threePid ->
+ // If not in lookup result, check if there is a pending binding
+ if (lookupResult.firstOrNull { it.threePid == threePid } == null) {
+ if (identityStore.getPendingBinding(threePid) == null) {
+ SharedState.NOT_SHARED
+ } else {
+ SharedState.BINDING_IN_PROGRESS
+ }
+ } else {
+ SharedState.SHARED
+ }
+ }
+ }
+ }
+
+ private suspend fun lookUpInternal(canRetry: Boolean, threePids: List): List {
+ ensureToken()
+
+ return try {
+ identityBulkLookupTask.execute(IdentityBulkLookupTask.Params(threePids))
+ } catch (throwable: Throwable) {
+ // Refresh token?
+ when {
+ throwable.isInvalidToken() && canRetry -> {
+ identityStore.setToken(null)
+ lookUpInternal(false, threePids)
+ }
+ throwable.isTermsNotSigned() -> throw IdentityServiceError.TermsNotSignedException
+ else -> throw throwable
+ }
+ }
+ }
+
+ private suspend fun ensureToken() {
+ val identityData = identityStore.getIdentityData() ?: throw IdentityServiceError.NoIdentityServerConfigured
+ val url = identityData.identityServerUrl ?: throw IdentityServiceError.NoIdentityServerConfigured
+
+ if (identityData.token == null) {
+ // Try to get a token
+ val token = getNewIdentityServerToken(url)
+ identityStore.setToken(token)
+ }
+ }
+
+ private suspend fun getNewIdentityServerToken(url: String): String {
+ val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
+
+ val openIdToken = getOpenIdTokenTask.execute(Unit)
+ val token = identityRegisterTask.execute(IdentityRegisterTask.Params(api, openIdToken))
+
+ return token.token
+ }
+
+ override fun addListener(listener: IdentityServiceListener) {
+ listeners.add(listener)
+ }
+
+ override fun removeListener(listener: IdentityServiceListener) {
+ listeners.remove(listener)
+ }
+
+ private fun updateIdentityAPI(url: String?) {
+ identityApiProvider.identityApi = url
+ ?.let { retrofitFactory.create(okHttpClient, it) }
+ ?.create(IdentityAPI::class.java)
+ }
+}
+
+private fun Throwable.isInvalidToken(): Boolean {
+ return this is Failure.ServerError
+ && httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */
+}
+
+private fun Throwable.isTermsNotSigned(): Boolean {
+ return this is Failure.ServerError
+ && httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */
+ && error.code == MatrixError.M_TERMS_NOT_SIGNED
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityAPI.kt
new file mode 100644
index 0000000000..ca361b4265
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityAPI.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity
+
+import im.vector.matrix.android.internal.auth.registration.SuccessResult
+import im.vector.matrix.android.internal.network.NetworkConstants
+import im.vector.matrix.android.internal.session.identity.model.IdentityAccountResponse
+import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
+import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpParams
+import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpResponse
+import im.vector.matrix.android.internal.session.identity.model.IdentityRequestOwnershipParams
+import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForEmailBody
+import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForMsisdnBody
+import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenResponse
+import retrofit2.Call
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.POST
+import retrofit2.http.Path
+
+/**
+ * Ref: https://matrix.org/docs/spec/identity_service/latest
+ * This contain the requests which need an identity server token
+ */
+internal interface IdentityAPI {
+ /**
+ * Gets information about what user owns the access token used in the request.
+ * Will return a 403 for when terms are not signed
+ * Ref: https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2-account
+ */
+ @GET(NetworkConstants.URI_IDENTITY_PATH_V2 + "account")
+ fun getAccount(): Call
+
+ /**
+ * Logs out the access token, preventing it from being used to authenticate future requests to the server.
+ */
+ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/logout")
+ fun logout(): Call
+
+ /**
+ * Request the hash detail to request a bunch of 3PIDs
+ * Ref: https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2-hash-details
+ */
+ @GET(NetworkConstants.URI_IDENTITY_PATH_V2 + "hash_details")
+ fun hashDetails(): Call
+
+ /**
+ * Request a bunch of 3PIDs
+ * Ref: https://matrix.org/docs/spec/identity_service/latest#post-matrix-identity-v2-lookup
+ *
+ * @param body the body request
+ */
+ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "lookup")
+ fun lookup(@Body body: IdentityLookUpParams): Call
+
+ /**
+ * Create a session to change the bind status of an email to an identity server
+ * The identity server will also send an email
+ *
+ * @param body
+ * @return the sid
+ */
+ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/email/requestToken")
+ fun requestTokenToBindEmail(@Body body: IdentityRequestTokenForEmailBody): Call
+
+ /**
+ * Create a session to change the bind status of an phone number to an identity server
+ * The identity server will also send an SMS on the ThreePid provided
+ *
+ * @param body
+ * @return the sid
+ */
+ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/msisdn/requestToken")
+ fun requestTokenToBindMsisdn(@Body body: IdentityRequestTokenForMsisdnBody): Call
+
+ /**
+ * Validate ownership of an email address, or a phone number.
+ * Ref:
+ * - https://matrix.org/docs/spec/identity_service/latest#post-matrix-identity-v2-validate-msisdn-submittoken
+ * - https://matrix.org/docs/spec/identity_service/latest#post-matrix-identity-v2-validate-email-submittoken
+ */
+ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken")
+ fun submitToken(@Path("medium") medium: String, @Body body: IdentityRequestOwnershipParams): Call
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityAccessTokenProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityAccessTokenProvider.kt
new file mode 100644
index 0000000000..ee2f18c767
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityAccessTokenProvider.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity
+
+import im.vector.matrix.android.internal.network.token.AccessTokenProvider
+import im.vector.matrix.android.internal.session.identity.data.IdentityStore
+import javax.inject.Inject
+
+internal class IdentityAccessTokenProvider @Inject constructor(
+ private val identityStore: IdentityStore
+) : AccessTokenProvider {
+ override fun getToken() = identityStore.getIdentityData()?.token
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityApiProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityApiProvider.kt
new file mode 100644
index 0000000000..3262a56398
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityApiProvider.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity
+
+import im.vector.matrix.android.internal.session.SessionScope
+import javax.inject.Inject
+
+@SessionScope
+internal class IdentityApiProvider @Inject constructor() {
+
+ var identityApi: IdentityAPI? = null
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityAuthAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityAuthAPI.kt
new file mode 100644
index 0000000000..04abf5fe6c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityAuthAPI.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity
+
+import im.vector.matrix.android.internal.network.NetworkConstants
+import im.vector.matrix.android.internal.session.identity.model.IdentityRegisterResponse
+import im.vector.matrix.android.internal.session.openid.RequestOpenIdTokenResponse
+import retrofit2.Call
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.POST
+
+/**
+ * Ref: https://matrix.org/docs/spec/identity_service/latest
+ * This contain the requests which do not need an identity server token
+ */
+internal interface IdentityAuthAPI {
+
+ /**
+ * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
+ * Simple ping call to check if server exists and is alive
+ *
+ * Ref: https://matrix.org/docs/spec/identity_service/unstable#status-check
+ * https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2
+ *
+ * @return 200 in case of success
+ */
+ @GET(NetworkConstants.URI_IDENTITY_PREFIX_PATH)
+ fun ping(): Call
+
+ /**
+ * Ping v1 will be used to check outdated Identity server
+ */
+ @GET("_matrix/identity/api/v1")
+ fun pingV1(): Call
+
+ /**
+ * Exchanges an OpenID token from the homeserver for an access token to access the identity server.
+ * The request body is the same as the values returned by /openid/request_token in the Client-Server API.
+ */
+ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/register")
+ fun register(@Body openIdToken: RequestOpenIdTokenResponse): Call
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityBulkLookupTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityBulkLookupTask.kt
new file mode 100644
index 0000000000..9f1579af60
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityBulkLookupTask.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity
+
+import im.vector.matrix.android.api.failure.Failure
+import im.vector.matrix.android.api.failure.MatrixError
+import im.vector.matrix.android.api.session.identity.FoundThreePid
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.matrix.android.api.session.identity.toMedium
+import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments.base64ToBase64Url
+import im.vector.matrix.android.internal.crypto.tools.withOlmUtility
+import im.vector.matrix.android.internal.di.UserId
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.session.identity.data.IdentityStore
+import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
+import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpParams
+import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpResponse
+import im.vector.matrix.android.internal.task.Task
+import java.util.Locale
+import javax.inject.Inject
+
+internal interface IdentityBulkLookupTask : Task> {
+ data class Params(
+ val threePids: List
+ )
+}
+
+internal class DefaultIdentityBulkLookupTask @Inject constructor(
+ private val identityApiProvider: IdentityApiProvider,
+ private val identityStore: IdentityStore,
+ @UserId private val userId: String
+) : IdentityBulkLookupTask {
+
+ override suspend fun execute(params: IdentityBulkLookupTask.Params): List {
+ val identityAPI = getIdentityApiAndEnsureTerms(identityApiProvider, userId)
+ val identityData = identityStore.getIdentityData() ?: throw IdentityServiceError.NoIdentityServerConfigured
+ val pepper = identityData.hashLookupPepper
+ val hashDetailResponse = if (pepper == null) {
+ // We need to fetch the hash details first
+ fetchAndStoreHashDetails(identityAPI)
+ } else {
+ IdentityHashDetailResponse(pepper, identityData.hashLookupAlgorithm)
+ }
+
+ if (hashDetailResponse.algorithms.contains("sha256").not()) {
+ // TODO We should ask the user if he is ok to send their 3Pid in clear, but for the moment we do not do it
+ // Also, what we have in cache could be outdated, the identity server maybe now supports sha256
+ throw IdentityServiceError.BulkLookupSha256NotSupported
+ }
+
+ val hashedAddresses = withOlmUtility { olmUtility ->
+ params.threePids.map { threePid ->
+ base64ToBase64Url(
+ olmUtility.sha256(threePid.value.toLowerCase(Locale.ROOT)
+ + " " + threePid.toMedium() + " " + hashDetailResponse.pepper)
+ )
+ }
+ }
+
+ val identityLookUpV2Response = lookUpInternal(identityAPI, hashedAddresses, hashDetailResponse, true)
+
+ // Convert back to List
+ return handleSuccess(params.threePids, hashedAddresses, identityLookUpV2Response)
+ }
+
+ private suspend fun lookUpInternal(identityAPI: IdentityAPI,
+ hashedAddresses: List,
+ hashDetailResponse: IdentityHashDetailResponse,
+ canRetry: Boolean): IdentityLookUpResponse {
+ return try {
+ executeRequest(null) {
+ apiCall = identityAPI.lookup(IdentityLookUpParams(
+ hashedAddresses,
+ IdentityHashDetailResponse.ALGORITHM_SHA256,
+ hashDetailResponse.pepper
+ ))
+ }
+ } catch (failure: Throwable) {
+ // Catch invalid hash pepper and retry
+ if (canRetry && failure is Failure.ServerError && failure.error.code == MatrixError.M_INVALID_PEPPER) {
+ // This is not documented, by the error can contain the new pepper!
+ if (!failure.error.newLookupPepper.isNullOrEmpty()) {
+ // Store it and use it right now
+ hashDetailResponse.copy(pepper = failure.error.newLookupPepper)
+ .also { identityStore.setHashDetails(it) }
+ .let { lookUpInternal(identityAPI, hashedAddresses, it, false /* Avoid infinite loop */) }
+ } else {
+ // Retrieve the new hash details
+ val newHashDetailResponse = fetchAndStoreHashDetails(identityAPI)
+
+ if (hashDetailResponse.algorithms.contains(IdentityHashDetailResponse.ALGORITHM_SHA256).not()) {
+ // TODO We should ask the user if he is ok to send their 3Pid in clear, but for the moment we do not do it
+ // Also, what we have in cache is maybe outdated, the identity server maybe now support sha256
+ throw IdentityServiceError.BulkLookupSha256NotSupported
+ }
+
+ lookUpInternal(identityAPI, hashedAddresses, newHashDetailResponse, false /* Avoid infinite loop */)
+ }
+ } else {
+ // Other error
+ throw failure
+ }
+ }
+ }
+
+ private suspend fun fetchAndStoreHashDetails(identityAPI: IdentityAPI): IdentityHashDetailResponse {
+ return executeRequest(null) {
+ apiCall = identityAPI.hashDetails()
+ }
+ .also { identityStore.setHashDetails(it) }
+ }
+
+ private fun handleSuccess(threePids: List, hashedAddresses: List, identityLookUpResponse: IdentityLookUpResponse): List {
+ return identityLookUpResponse.mappings.keys.map { hashedAddress ->
+ FoundThreePid(threePids[hashedAddresses.indexOf(hashedAddress)], identityLookUpResponse.mappings[hashedAddress] ?: error(""))
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityDisconnectTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityDisconnectTask.kt
new file mode 100644
index 0000000000..abed3962bc
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityDisconnectTask.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity
+
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
+import im.vector.matrix.android.internal.di.AuthenticatedIdentity
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.network.token.AccessTokenProvider
+import im.vector.matrix.android.internal.task.Task
+import timber.log.Timber
+import javax.inject.Inject
+
+internal interface IdentityDisconnectTask : Task
+
+internal class DefaultIdentityDisconnectTask @Inject constructor(
+ private val identityApiProvider: IdentityApiProvider,
+ @AuthenticatedIdentity
+ private val accessTokenProvider: AccessTokenProvider
+) : IdentityDisconnectTask {
+
+ override suspend fun execute(params: Unit) {
+ val identityAPI = identityApiProvider.identityApi ?: throw IdentityServiceError.NoIdentityServerConfigured
+
+ // Ensure we have a token.
+ // We can have an identity server configured, but no token yet.
+ if (accessTokenProvider.getToken() == null) {
+ Timber.d("No token to disconnect identity server.")
+ return
+ }
+
+ executeRequest(null) {
+ apiCall = identityAPI.logout()
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt
new file mode 100644
index 0000000000..7a5790788b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity
+
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import im.vector.matrix.android.internal.database.RealmKeysUtils
+import im.vector.matrix.android.internal.di.AuthenticatedIdentity
+import im.vector.matrix.android.internal.di.IdentityDatabase
+import im.vector.matrix.android.internal.di.SessionFilesDirectory
+import im.vector.matrix.android.internal.di.Unauthenticated
+import im.vector.matrix.android.internal.di.UserMd5
+import im.vector.matrix.android.internal.network.httpclient.addAccessTokenInterceptor
+import im.vector.matrix.android.internal.network.token.AccessTokenProvider
+import im.vector.matrix.android.internal.session.SessionModule
+import im.vector.matrix.android.internal.session.SessionScope
+import im.vector.matrix.android.internal.session.identity.data.IdentityStore
+import im.vector.matrix.android.internal.session.identity.db.IdentityRealmModule
+import im.vector.matrix.android.internal.session.identity.db.RealmIdentityStore
+import io.realm.RealmConfiguration
+import okhttp3.OkHttpClient
+import java.io.File
+
+@Module
+internal abstract class IdentityModule {
+
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ @SessionScope
+ @AuthenticatedIdentity
+ fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
+ @AuthenticatedIdentity accessTokenProvider: AccessTokenProvider): OkHttpClient {
+ return okHttpClient.addAccessTokenInterceptor(accessTokenProvider)
+ }
+
+ @JvmStatic
+ @Provides
+ @IdentityDatabase
+ @SessionScope
+ fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
+ @SessionFilesDirectory directory: File,
+ @UserMd5 userMd5: String): RealmConfiguration {
+ return RealmConfiguration.Builder()
+ .directory(directory)
+ .name("matrix-sdk-identity.realm")
+ .apply {
+ realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
+ }
+ .modules(IdentityRealmModule())
+ .build()
+ }
+ }
+
+ @Binds
+ @AuthenticatedIdentity
+ abstract fun bindAccessTokenProvider(provider: IdentityAccessTokenProvider): AccessTokenProvider
+
+ @Binds
+ abstract fun bindIdentityStore(store: RealmIdentityStore): IdentityStore
+
+ @Binds
+ abstract fun bindIdentityPingTask(task: DefaultIdentityPingTask): IdentityPingTask
+
+ @Binds
+ abstract fun bindIdentityRegisterTask(task: DefaultIdentityRegisterTask): IdentityRegisterTask
+
+ @Binds
+ abstract fun bindIdentityRequestTokenForBindingTask(task: DefaultIdentityRequestTokenForBindingTask): IdentityRequestTokenForBindingTask
+
+ @Binds
+ abstract fun bindIdentitySubmitTokenForBindingTask(task: DefaultIdentitySubmitTokenForBindingTask): IdentitySubmitTokenForBindingTask
+
+ @Binds
+ abstract fun bindIdentityBulkLookupTask(task: DefaultIdentityBulkLookupTask): IdentityBulkLookupTask
+
+ @Binds
+ abstract fun bindIdentityDisconnectTask(task: DefaultIdentityDisconnectTask): IdentityDisconnectTask
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityPingTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityPingTask.kt
new file mode 100644
index 0000000000..50a36097a1
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityPingTask.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity
+
+import im.vector.matrix.android.api.failure.Failure
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.task.Task
+import javax.inject.Inject
+import javax.net.ssl.HttpsURLConnection
+
+internal interface IdentityPingTask : Task {
+ data class Params(
+ val identityAuthAPI: IdentityAuthAPI
+ )
+}
+
+internal class DefaultIdentityPingTask @Inject constructor() : IdentityPingTask {
+
+ override suspend fun execute(params: IdentityPingTask.Params) {
+ try {
+ executeRequest(null) {
+ apiCall = params.identityAuthAPI.ping()
+ }
+ } catch (throwable: Throwable) {
+ if (throwable is Failure.ServerError && throwable.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
+ // Check if API v1 is available
+ executeRequest(null) {
+ apiCall = params.identityAuthAPI.pingV1()
+ }
+ // API V1 is responding, but not V2 -> Outdated
+ throw IdentityServiceError.OutdatedIdentityServer
+ } else {
+ throw throwable
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityRegisterTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityRegisterTask.kt
new file mode 100644
index 0000000000..c72e364ef8
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityRegisterTask.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity
+
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.session.identity.model.IdentityRegisterResponse
+import im.vector.matrix.android.internal.session.openid.RequestOpenIdTokenResponse
+import im.vector.matrix.android.internal.task.Task
+import javax.inject.Inject
+
+internal interface IdentityRegisterTask : Task {
+ data class Params(
+ val identityAuthAPI: IdentityAuthAPI,
+ val openIdTokenResponse: RequestOpenIdTokenResponse
+ )
+}
+
+internal class DefaultIdentityRegisterTask @Inject constructor() : IdentityRegisterTask {
+
+ override suspend fun execute(params: IdentityRegisterTask.Params): IdentityRegisterResponse {
+ return executeRequest(null) {
+ apiCall = params.identityAuthAPI.register(params.openIdTokenResponse)
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityRequestTokenForBindingTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityRequestTokenForBindingTask.kt
new file mode 100644
index 0000000000..313f5f6662
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityRequestTokenForBindingTask.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity
+
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.matrix.android.api.session.identity.getCountryCode
+import im.vector.matrix.android.internal.di.UserId
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.session.identity.data.IdentityPendingBinding
+import im.vector.matrix.android.internal.session.identity.data.IdentityStore
+import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForEmailBody
+import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForMsisdnBody
+import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenResponse
+import im.vector.matrix.android.internal.task.Task
+import java.util.UUID
+import javax.inject.Inject
+
+internal interface IdentityRequestTokenForBindingTask : Task {
+ data class Params(
+ val threePid: ThreePid,
+ // True to request the identity server to send again the email or the SMS
+ val sendAgain: Boolean
+ )
+}
+
+internal class DefaultIdentityRequestTokenForBindingTask @Inject constructor(
+ private val identityApiProvider: IdentityApiProvider,
+ private val identityStore: IdentityStore,
+ @UserId private val userId: String
+) : IdentityRequestTokenForBindingTask {
+
+ override suspend fun execute(params: IdentityRequestTokenForBindingTask.Params) {
+ val identityAPI = getIdentityApiAndEnsureTerms(identityApiProvider, userId)
+
+ val identityPendingBinding = identityStore.getPendingBinding(params.threePid)
+
+ if (params.sendAgain && identityPendingBinding == null) {
+ throw IdentityServiceError.NoCurrentBindingError
+ }
+
+ val clientSecret = identityPendingBinding?.clientSecret ?: UUID.randomUUID().toString()
+ val sendAttempt = identityPendingBinding?.sendAttempt?.inc() ?: 1
+
+ val tokenResponse = executeRequest(null) {
+ apiCall = when (params.threePid) {
+ is ThreePid.Email -> identityAPI.requestTokenToBindEmail(IdentityRequestTokenForEmailBody(
+ clientSecret = clientSecret,
+ sendAttempt = sendAttempt,
+ email = params.threePid.email
+ ))
+ is ThreePid.Msisdn -> {
+ identityAPI.requestTokenToBindMsisdn(IdentityRequestTokenForMsisdnBody(
+ clientSecret = clientSecret,
+ sendAttempt = sendAttempt,
+ phoneNumber = params.threePid.msisdn,
+ countryCode = params.threePid.getCountryCode()
+ ))
+ }
+ }
+ }
+
+ // Store client secret, send attempt and sid
+ identityStore.storePendingBinding(
+ params.threePid,
+ IdentityPendingBinding(
+ clientSecret = clientSecret,
+ sendAttempt = sendAttempt,
+ sid = tokenResponse.sid
+ )
+ )
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentitySubmitTokenForBindingTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentitySubmitTokenForBindingTask.kt
new file mode 100644
index 0000000000..fae1dd1eba
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentitySubmitTokenForBindingTask.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity
+
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.matrix.android.api.session.identity.toMedium
+import im.vector.matrix.android.internal.auth.registration.SuccessResult
+import im.vector.matrix.android.internal.di.UserId
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.session.identity.data.IdentityStore
+import im.vector.matrix.android.internal.session.identity.model.IdentityRequestOwnershipParams
+import im.vector.matrix.android.internal.task.Task
+import javax.inject.Inject
+
+internal interface IdentitySubmitTokenForBindingTask : Task {
+ data class Params(
+ val threePid: ThreePid,
+ val token: String
+ )
+}
+
+internal class DefaultIdentitySubmitTokenForBindingTask @Inject constructor(
+ private val identityApiProvider: IdentityApiProvider,
+ private val identityStore: IdentityStore,
+ @UserId private val userId: String
+) : IdentitySubmitTokenForBindingTask {
+
+ override suspend fun execute(params: IdentitySubmitTokenForBindingTask.Params) {
+ val identityAPI = getIdentityApiAndEnsureTerms(identityApiProvider, userId)
+ val identityPendingBinding = identityStore.getPendingBinding(params.threePid) ?: throw IdentityServiceError.NoCurrentBindingError
+
+ val tokenResponse = executeRequest(null) {
+ apiCall = identityAPI.submitToken(
+ params.threePid.toMedium(),
+ IdentityRequestOwnershipParams(
+ clientSecret = identityPendingBinding.clientSecret,
+ sid = identityPendingBinding.sid,
+ token = params.token
+ ))
+ }
+
+ if (!tokenResponse.isSuccess()) {
+ throw IdentityServiceError.BindingError
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityTaskHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityTaskHelper.kt
new file mode 100644
index 0000000000..bd97a0af2b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityTaskHelper.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity
+
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.session.identity.model.IdentityAccountResponse
+
+internal suspend fun getIdentityApiAndEnsureTerms(identityApiProvider: IdentityApiProvider, userId: String): IdentityAPI {
+ val identityAPI = identityApiProvider.identityApi ?: throw IdentityServiceError.NoIdentityServerConfigured
+
+ // Always check that we have access to the service (regarding terms)
+ val identityAccountResponse = executeRequest(null) {
+ apiCall = identityAPI.getAccount()
+ }
+
+ assert(userId == identityAccountResponse.userId)
+
+ return identityAPI
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/data/IdentityData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/data/IdentityData.kt
new file mode 100644
index 0000000000..f1e57e1ed5
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/data/IdentityData.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.data
+
+internal data class IdentityData(
+ val identityServerUrl: String?,
+ val token: String?,
+ val hashLookupPepper: String?,
+ val hashLookupAlgorithm: List
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/data/IdentityPendingBinding.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/data/IdentityPendingBinding.kt
new file mode 100644
index 0000000000..b7f405cb0a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/data/IdentityPendingBinding.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.data
+
+internal data class IdentityPendingBinding(
+ /* Managed by Riot */
+ val clientSecret: String,
+ /* Managed by Riot */
+ val sendAttempt: Int,
+ /* Provided by the identity server */
+ val sid: String
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/data/IdentityStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/data/IdentityStore.kt
new file mode 100644
index 0000000000..d5cd3277ec
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/data/IdentityStore.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.data
+
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
+
+internal interface IdentityStore {
+
+ fun getIdentityData(): IdentityData?
+
+ fun setUrl(url: String?)
+
+ fun setToken(token: String?)
+
+ fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse)
+
+ /**
+ * Store details about a current binding
+ */
+ fun storePendingBinding(threePid: ThreePid, data: IdentityPendingBinding)
+
+ fun getPendingBinding(threePid: ThreePid): IdentityPendingBinding?
+
+ fun deletePendingBinding(threePid: ThreePid)
+}
+
+internal fun IdentityStore.getIdentityServerUrlWithoutProtocol(): String? {
+ return getIdentityData()?.identityServerUrl?.substringAfter("://")
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityDataEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityDataEntity.kt
new file mode 100644
index 0000000000..76e480bdc9
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityDataEntity.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.db
+
+import io.realm.RealmList
+import io.realm.RealmObject
+
+internal open class IdentityDataEntity(
+ var identityServerUrl: String? = null,
+ var token: String? = null,
+ var hashLookupPepper: String? = null,
+ var hashLookupAlgorithm: RealmList = RealmList()
+) : RealmObject() {
+
+ companion object
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityDataEntityQuery.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityDataEntityQuery.kt
new file mode 100644
index 0000000000..0a07359642
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityDataEntityQuery.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.db
+
+import io.realm.Realm
+import io.realm.RealmList
+import io.realm.kotlin.createObject
+import io.realm.kotlin.where
+
+/**
+ * Only one object can be stored at a time
+ */
+internal fun IdentityDataEntity.Companion.get(realm: Realm): IdentityDataEntity? {
+ return realm.where().findFirst()
+}
+
+private fun IdentityDataEntity.Companion.getOrCreate(realm: Realm): IdentityDataEntity {
+ return get(realm) ?: realm.createObject()
+}
+
+internal fun IdentityDataEntity.Companion.setUrl(realm: Realm,
+ url: String?) {
+ realm.where().findAll().deleteAllFromRealm()
+ // Delete all pending binding if any
+ IdentityPendingBindingEntity.deleteAll(realm)
+
+ if (url != null) {
+ getOrCreate(realm).apply {
+ identityServerUrl = url
+ }
+ }
+}
+
+internal fun IdentityDataEntity.Companion.setToken(realm: Realm,
+ newToken: String?) {
+ get(realm)?.apply {
+ token = newToken
+ }
+}
+
+internal fun IdentityDataEntity.Companion.setHashDetails(realm: Realm,
+ pepper: String,
+ algorithms: List) {
+ get(realm)?.apply {
+ hashLookupPepper = pepper
+ hashLookupAlgorithm = RealmList().apply { addAll(algorithms) }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityMapper.kt
new file mode 100644
index 0000000000..1335e38565
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityMapper.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.db
+
+import im.vector.matrix.android.internal.session.identity.data.IdentityData
+import im.vector.matrix.android.internal.session.identity.data.IdentityPendingBinding
+
+internal object IdentityMapper {
+
+ fun map(entity: IdentityDataEntity): IdentityData {
+ return IdentityData(
+ identityServerUrl = entity.identityServerUrl,
+ token = entity.token,
+ hashLookupPepper = entity.hashLookupPepper,
+ hashLookupAlgorithm = entity.hashLookupAlgorithm.toList()
+ )
+ }
+
+ fun map(entity: IdentityPendingBindingEntity): IdentityPendingBinding {
+ return IdentityPendingBinding(
+ clientSecret = entity.clientSecret,
+ sendAttempt = entity.sendAttempt,
+ sid = entity.sid
+ )
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityPendingBindingEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityPendingBindingEntity.kt
new file mode 100644
index 0000000000..51b195a7fc
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityPendingBindingEntity.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.db
+
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.matrix.android.api.session.identity.toMedium
+import io.realm.RealmObject
+import io.realm.annotations.PrimaryKey
+
+internal open class IdentityPendingBindingEntity(
+ @PrimaryKey var threePid: String = "",
+ /* Managed by Riot */
+ var clientSecret: String = "",
+ /* Managed by Riot */
+ var sendAttempt: Int = 0,
+ /* Provided by the identity server */
+ var sid: String = ""
+) : RealmObject() {
+
+ companion object {
+ fun ThreePid.toPrimaryKey() = "${toMedium()}_$value"
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityPendingBindingEntityQuery.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityPendingBindingEntityQuery.kt
new file mode 100644
index 0000000000..e358be6bbb
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityPendingBindingEntityQuery.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.db
+
+import im.vector.matrix.android.api.session.identity.ThreePid
+import io.realm.Realm
+import io.realm.kotlin.createObject
+import io.realm.kotlin.where
+
+internal fun IdentityPendingBindingEntity.Companion.get(realm: Realm, threePid: ThreePid): IdentityPendingBindingEntity? {
+ return realm.where()
+ .equalTo(IdentityPendingBindingEntityFields.THREE_PID, threePid.toPrimaryKey())
+ .findFirst()
+}
+
+internal fun IdentityPendingBindingEntity.Companion.getOrCreate(realm: Realm, threePid: ThreePid): IdentityPendingBindingEntity {
+ return get(realm, threePid) ?: realm.createObject(threePid.toPrimaryKey())
+}
+
+internal fun IdentityPendingBindingEntity.Companion.delete(realm: Realm, threePid: ThreePid) {
+ get(realm, threePid)?.deleteFromRealm()
+}
+
+internal fun IdentityPendingBindingEntity.Companion.deleteAll(realm: Realm) {
+ realm.where()
+ .findAll()
+ .deleteAllFromRealm()
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityRealmModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityRealmModule.kt
new file mode 100644
index 0000000000..19bd90ee1f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/IdentityRealmModule.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.db
+
+import io.realm.annotations.RealmModule
+
+/**
+ * Realm module for identity server classes
+ */
+@RealmModule(library = true,
+ classes = [
+ IdentityDataEntity::class,
+ IdentityPendingBindingEntity::class
+ ])
+internal class IdentityRealmModule
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/RealmIdentityStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/RealmIdentityStore.kt
new file mode 100644
index 0000000000..c294fbbf4b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/db/RealmIdentityStore.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.db
+
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.matrix.android.internal.di.IdentityDatabase
+import im.vector.matrix.android.internal.session.SessionScope
+import im.vector.matrix.android.internal.session.identity.data.IdentityPendingBinding
+import im.vector.matrix.android.internal.session.identity.data.IdentityData
+import im.vector.matrix.android.internal.session.identity.data.IdentityStore
+import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
+import io.realm.Realm
+import io.realm.RealmConfiguration
+import javax.inject.Inject
+
+@SessionScope
+internal class RealmIdentityStore @Inject constructor(
+ @IdentityDatabase
+ private val realmConfiguration: RealmConfiguration
+) : IdentityStore {
+
+ override fun getIdentityData(): IdentityData? {
+ return Realm.getInstance(realmConfiguration).use { realm ->
+ IdentityDataEntity.get(realm)?.let { IdentityMapper.map(it) }
+ }
+ }
+
+ override fun setUrl(url: String?) {
+ Realm.getInstance(realmConfiguration).use {
+ it.executeTransaction { realm ->
+ IdentityDataEntity.setUrl(realm, url)
+ }
+ }
+ }
+
+ override fun setToken(token: String?) {
+ Realm.getInstance(realmConfiguration).use {
+ it.executeTransaction { realm ->
+ IdentityDataEntity.setToken(realm, token)
+ }
+ }
+ }
+
+ override fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) {
+ Realm.getInstance(realmConfiguration).use {
+ it.executeTransaction { realm ->
+ IdentityDataEntity.setHashDetails(realm, hashDetailResponse.pepper, hashDetailResponse.algorithms)
+ }
+ }
+ }
+
+ override fun storePendingBinding(threePid: ThreePid, data: IdentityPendingBinding) {
+ Realm.getInstance(realmConfiguration).use {
+ it.executeTransaction { realm ->
+ IdentityPendingBindingEntity.getOrCreate(realm, threePid).let { entity ->
+ entity.clientSecret = data.clientSecret
+ entity.sendAttempt = data.sendAttempt
+ entity.sid = data.sid
+ }
+ }
+ }
+ }
+
+ override fun getPendingBinding(threePid: ThreePid): IdentityPendingBinding? {
+ return Realm.getInstance(realmConfiguration).use { realm ->
+ IdentityPendingBindingEntity.get(realm, threePid)?.let { IdentityMapper.map(it) }
+ }
+ }
+
+ override fun deletePendingBinding(threePid: ThreePid) {
+ Realm.getInstance(realmConfiguration).use {
+ it.executeTransaction { realm ->
+ IdentityPendingBindingEntity.delete(realm, threePid)
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityAccountResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityAccountResponse.kt
new file mode 100644
index 0000000000..a72eb75537
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityAccountResponse.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class IdentityAccountResponse(
+ /**
+ * Required. The user ID which registered the token.
+ */
+ @Json(name = "user_id")
+ val userId: String
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityHashDetailResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityHashDetailResponse.kt
new file mode 100644
index 0000000000..16a4e1fc71
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityHashDetailResponse.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * Ref: https://github.com/matrix-org/matrix-doc/blob/hs/hash-identity/proposals/2134-identity-hash-lookup.md
+ */
+@JsonClass(generateAdapter = true)
+internal data class IdentityHashDetailResponse(
+ /**
+ * Required. The pepper the client MUST use in hashing identifiers, and MUST supply to the /lookup endpoint when performing lookups.
+ * Servers SHOULD rotate this string often.
+ */
+ @Json(name = "lookup_pepper")
+ val pepper: String,
+
+ /**
+ * Required. The algorithms the server supports. Must contain at least "sha256".
+ * "none" can be another possible value.
+ */
+ @Json(name = "algorithms")
+ val algorithms: List
+) {
+ companion object {
+ const val ALGORITHM_SHA256 = "sha256"
+ const val ALGORITHM_NONE = "none"
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityLookUpParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityLookUpParams.kt
new file mode 100644
index 0000000000..f87d14e1fe
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityLookUpParams.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * Ref: https://github.com/matrix-org/matrix-doc/blob/hs/hash-identity/proposals/2134-identity-hash-lookup.md
+ */
+@JsonClass(generateAdapter = true)
+internal data class IdentityLookUpParams(
+ /**
+ * Required. The addresses to look up. The format of the entries here depend on the algorithm used.
+ * Note that queries which have been incorrectly hashed or formatted will lead to no matches.
+ */
+ @Json(name = "addresses")
+ val hashedAddresses: List,
+
+ /**
+ * Required. The algorithm the client is using to encode the addresses. This should be one of the available options from /hash_details.
+ */
+ @Json(name = "algorithm")
+ val algorithm: String,
+
+ /**
+ * Required. The pepper from /hash_details. This is required even when the algorithm does not make use of it.
+ */
+ @Json(name = "pepper")
+ val pepper: String
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityLookUpResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityLookUpResponse.kt
new file mode 100644
index 0000000000..a71e2f7366
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityLookUpResponse.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * Ref: https://github.com/matrix-org/matrix-doc/blob/hs/hash-identity/proposals/2134-identity-hash-lookup.md
+ */
+@JsonClass(generateAdapter = true)
+internal data class IdentityLookUpResponse(
+ /**
+ * Required. Any applicable mappings of addresses to Matrix User IDs. Addresses which do not have associations will
+ * not be included, which can make this property be an empty object.
+ */
+ @Json(name = "mappings")
+ val mappings: Map
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityRegisterResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityRegisterResponse.kt
new file mode 100644
index 0000000000..86999d570d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityRegisterResponse.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class IdentityRegisterResponse(
+ /**
+ * Required. An opaque string representing the token to authenticate future requests to the identity server with.
+ */
+ @Json(name = "token")
+ val token: String
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityRequestOwnershipParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityRequestOwnershipParams.kt
new file mode 100644
index 0000000000..9da86cbc48
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityRequestOwnershipParams.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.matrix.android.internal.session.identity.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class IdentityRequestOwnershipParams(
+ /**
+ * Required. The client secret that was supplied to the requestToken call.
+ */
+ @Json(name = "client_secret")
+ val clientSecret: String,
+
+ /**
+ * Required. The session ID, generated by the requestToken call.
+ */
+ @Json(name = "sid")
+ val sid: String,
+
+ /**
+ * Required. The token generated by the requestToken call and sent to the user.
+ */
+ @Json(name = "token")
+ val token: String
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityRequestTokenBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityRequestTokenBody.kt
new file mode 100644
index 0000000000..3e92ebb1d2
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityRequestTokenBody.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+// Just to consider common parameters
+private interface IdentityRequestTokenBody {
+ /**
+ * Required. A unique string generated by the client, and used to identify the validation attempt.
+ * It must be a string consisting of the characters [0-9a-zA-Z.=_-].
+ * Its length must not exceed 255 characters and it must not be empty.
+ */
+ val clientSecret: String
+
+ val sendAttempt: Int
+}
+
+@JsonClass(generateAdapter = true)
+internal data class IdentityRequestTokenForEmailBody(
+ @Json(name = "client_secret")
+ override val clientSecret: String,
+
+ /**
+ * Required. The server will only send an email if the send_attempt is a number greater than the most
+ * recent one which it has seen, scoped to that email + client_secret pair. This is to avoid repeatedly
+ * sending the same email in the case of request retries between the POSTing user and the identity server.
+ * The client should increment this value if they desire a new email (e.g. a reminder) to be sent.
+ * If they do not, the server should respond with success but not resend the email.
+ */
+ @Json(name = "send_attempt")
+ override val sendAttempt: Int,
+
+ /**
+ * Required. The email address to validate.
+ */
+ @Json(name = "email")
+ val email: String
+) : IdentityRequestTokenBody
+
+@JsonClass(generateAdapter = true)
+internal data class IdentityRequestTokenForMsisdnBody(
+ @Json(name = "client_secret")
+ override val clientSecret: String,
+
+ /**
+ * Required. The server will only send an SMS if the send_attempt is a number greater than the most recent one
+ * which it has seen, scoped to that country + phone_number + client_secret triple. This is to avoid repeatedly
+ * sending the same SMS in the case of request retries between the POSTing user and the identity server.
+ * The client should increment this value if they desire a new SMS (e.g. a reminder) to be sent.
+ */
+ @Json(name = "send_attempt")
+ override val sendAttempt: Int,
+
+ /**
+ * Required. The phone number to validate.
+ */
+ @Json(name = "phone_number")
+ val phoneNumber: String,
+
+ /**
+ * Required. The two-letter uppercase ISO-3166-1 alpha-2 country code that the number in phone_number
+ * should be parsed as if it were dialled from.
+ */
+ @Json(name = "country")
+ val countryCode: String
+) : IdentityRequestTokenBody
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityRequestTokenResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityRequestTokenResponse.kt
new file mode 100644
index 0000000000..cb3c257ddb
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/model/IdentityRequestTokenResponse.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class IdentityRequestTokenResponse(
+ /**
+ * Required. The session ID. Session IDs are opaque strings generated by the identity server.
+ * They must consist entirely of the characters [0-9a-zA-Z.=_-].
+ * Their length must not exceed 255 characters and they must not be empty.
+ */
+ @Json(name = "sid")
+ val sid: String
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/todelete/AccountDataDataSource.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/todelete/AccountDataDataSource.kt
new file mode 100644
index 0000000000..37b0da9101
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/todelete/AccountDataDataSource.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.todelete
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Transformations
+import com.zhuinden.monarchy.Monarchy
+import im.vector.matrix.android.api.util.Optional
+import im.vector.matrix.android.api.util.toOptional
+import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
+import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields
+import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
+import io.realm.Realm
+import io.realm.RealmQuery
+import javax.inject.Inject
+
+// There will be a duplicated class when Integration manager will be merged, so delete this one
+internal class AccountDataDataSource @Inject constructor(private val monarchy: Monarchy,
+ private val accountDataMapper: AccountDataMapper) {
+
+ fun getAccountDataEvent(type: String): UserAccountDataEvent? {
+ return getAccountDataEvents(setOf(type)).firstOrNull()
+ }
+
+ fun getLiveAccountDataEvent(type: String): LiveData> {
+ return Transformations.map(getLiveAccountDataEvents(setOf(type))) {
+ it.firstOrNull()?.toOptional()
+ }
+ }
+
+ fun getAccountDataEvents(types: Set): List {
+ return monarchy.fetchAllMappedSync(
+ { accountDataEventsQuery(it, types) },
+ accountDataMapper::map
+ )
+ }
+
+ fun getLiveAccountDataEvents(types: Set): LiveData> {
+ return monarchy.findAllMappedWithChanges(
+ { accountDataEventsQuery(it, types) },
+ accountDataMapper::map
+ )
+ }
+
+ private fun accountDataEventsQuery(realm: Realm, types: Set): RealmQuery {
+ val query = realm.where(UserAccountDataEntity::class.java)
+ if (types.isNotEmpty()) {
+ query.`in`(UserAccountDataEntityFields.TYPE, types.toTypedArray())
+ }
+ return query
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/todelete/AccountDataMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/todelete/AccountDataMapper.kt
new file mode 100644
index 0000000000..4627911b72
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/todelete/AccountDataMapper.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.todelete
+
+import com.squareup.moshi.Moshi
+import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
+import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
+import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
+import javax.inject.Inject
+
+// There will be a duplicated class when Integration manager will be merged, so delete this one
+internal class AccountDataMapper @Inject constructor(moshi: Moshi) {
+
+ private val adapter = moshi.adapter>(JSON_DICT_PARAMETERIZED_TYPE)
+
+ fun map(entity: UserAccountDataEntity): UserAccountDataEvent {
+ return UserAccountDataEvent(
+ type = entity.type ?: "",
+ content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap()
+ )
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/todelete/LiveData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/todelete/LiveData.kt
new file mode 100644
index 0000000000..f84756fa86
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/todelete/LiveData.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.identity.todelete
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+
+// There will be a duplicated class when Integration manager will be merged, so delete this one
+inline fun LiveData.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) {
+ this.observe(owner, Observer { observer(it) })
+}
+
+inline fun LiveData.observeNotNull(owner: LifecycleOwner, crossinline observer: (T) -> Unit) {
+ this.observe(owner, Observer { it?.run(observer) })
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/integrationmanager/IntegrationManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/integrationmanager/IntegrationManager.kt
index f3a06af929..931d1b9bb8 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/integrationmanager/IntegrationManager.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/integrationmanager/IntegrationManager.kt
@@ -91,7 +91,7 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
fun start() {
lifecycleRegistry.currentState = Lifecycle.State.STARTED
accountDataDataSource
- .getLiveAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS)
+ .getLiveAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
.observeNotNull(lifecycleOwner) {
val allowedWidgetsContent = it.getOrNull()?.content?.toModel()
if (allowedWidgetsContent != null) {
@@ -99,7 +99,7 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
}
}
accountDataDataSource
- .getLiveAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_INTEGRATION_PROVISIONING)
+ .getLiveAccountDataEvent(UserAccountData.TYPE_INTEGRATION_PROVISIONING)
.observeNotNull(lifecycleOwner) {
val integrationProvisioningContent = it.getOrNull()?.content?.toModel()
if (integrationProvisioningContent != null) {
@@ -142,7 +142,7 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
* Returns false if the user as disabled integration manager feature
*/
fun isIntegrationEnabled(): Boolean {
- val integrationProvisioningData = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_INTEGRATION_PROVISIONING)
+ val integrationProvisioningData = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_INTEGRATION_PROVISIONING)
val integrationProvisioningContent = integrationProvisioningData?.content?.toModel()
return integrationProvisioningContent?.enabled ?: false
}
@@ -163,7 +163,7 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
}
fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback): Cancelable {
- val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS)
+ val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
val currentContent = currentAllowedWidgets?.content?.toModel()
val newContent = if (currentContent == null) {
val allowedWidget = mapOf(stateEventId to allowed)
@@ -183,13 +183,13 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
}
fun isWidgetAllowed(stateEventId: String): Boolean {
- val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS)
+ val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
val currentContent = currentAllowedWidgets?.content?.toModel()
return currentContent?.widgets?.get(stateEventId) ?: false
}
fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback): Cancelable {
- val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS)
+ val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
val currentContent = currentAllowedWidgets?.content?.toModel()
val newContent = if (currentContent == null) {
val nativeAllowedWidgets = mapOf(widgetType to mapOf(domain to allowed))
@@ -213,7 +213,7 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
}
fun isNativeWidgetAllowed(widgetType: String, domain: String?): Boolean {
- val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS)
+ val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
val currentContent = currentAllowedWidgets?.content?.toModel()
return currentContent?.native?.get(widgetType)?.get(domain) ?: false
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/AccountThreePidsResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/AccountThreePidsResponse.kt
new file mode 100644
index 0000000000..17f12113dd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/AccountThreePidsResponse.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.matrix.android.internal.session.profile
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * Class representing the ThreePids response
+ */
+@JsonClass(generateAdapter = true)
+internal data class AccountThreePidsResponse(
+ @Json(name = "threepids")
+ val threePids: List? = null
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/BindThreePidBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/BindThreePidBody.kt
new file mode 100644
index 0000000000..6ba3ddda4a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/BindThreePidBody.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.matrix.android.internal.session.profile
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class BindThreePidBody(
+ /**
+ * Required. The client secret used in the session with the identity server.
+ */
+ @Json(name = "client_secret")
+ val clientSecret: String,
+
+ /**
+ * Required. The identity server to use. (without "https://")
+ */
+ @Json(name = "id_server")
+ var identityServerUrlWithoutProtocol: String,
+
+ /**
+ * Required. An access token previously registered with the identity server.
+ */
+ @Json(name = "id_access_token")
+ var identityServerAccessToken: String,
+
+ /**
+ * Required. The session identifier given by the identity server.
+ */
+ @Json(name = "sid")
+ var sid: String
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/BindThreePidsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/BindThreePidsTask.kt
new file mode 100644
index 0000000000..0e1987dd5f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/BindThreePidsTask.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.profile
+
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.matrix.android.internal.di.AuthenticatedIdentity
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.network.token.AccessTokenProvider
+import im.vector.matrix.android.internal.session.identity.data.IdentityStore
+import im.vector.matrix.android.internal.session.identity.data.getIdentityServerUrlWithoutProtocol
+import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
+import javax.inject.Inject
+
+internal abstract class BindThreePidsTask : Task {
+ data class Params(
+ val threePid: ThreePid
+ )
+}
+
+internal class DefaultBindThreePidsTask @Inject constructor(private val profileAPI: ProfileAPI,
+ private val identityStore: IdentityStore,
+ @AuthenticatedIdentity
+ private val accessTokenProvider: AccessTokenProvider,
+ private val eventBus: EventBus) : BindThreePidsTask() {
+ override suspend fun execute(params: Params) {
+ val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol() ?: throw IdentityServiceError.NoIdentityServerConfigured
+ val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured
+ val identityPendingBinding = identityStore.getPendingBinding(params.threePid) ?: throw IdentityServiceError.NoCurrentBindingError
+
+ executeRequest(eventBus) {
+ apiCall = profileAPI.bindThreePid(
+ BindThreePidBody(
+ clientSecret = identityPendingBinding.clientSecret,
+ identityServerUrlWithoutProtocol = identityServerUrlWithoutProtocol,
+ identityServerAccessToken = identityServerAccessToken,
+ sid = identityPendingBinding.sid
+ ))
+ }
+
+ // Binding is over, cleanup the store
+ identityStore.deletePendingBinding(params.threePid)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/DefaultProfileService.kt
index e2c18e41d6..a981e8e930 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/DefaultProfileService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/DefaultProfileService.kt
@@ -17,16 +17,23 @@
package im.vector.matrix.android.internal.session.profile
+import androidx.lifecycle.LiveData
+import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
+import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.matrix.android.api.session.profile.ProfileService
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.api.util.Optional
+import im.vector.matrix.android.internal.database.model.UserThreePidEntity
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
+import io.realm.kotlin.where
import javax.inject.Inject
internal class DefaultProfileService @Inject constructor(private val taskExecutor: TaskExecutor,
+ private val monarchy: Monarchy,
+ private val refreshUserThreePidsTask: RefreshUserThreePidsTask,
private val getProfileInfoTask: GetProfileInfoTask) : ProfileService {
override fun getDisplayName(userId: String, matrixCallback: MatrixCallback>): Cancelable {
@@ -73,4 +80,33 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
}
.executeBy(taskExecutor)
}
+
+ override fun getThreePids(): List {
+ return monarchy.fetchAllMappedSync(
+ { it.where() },
+ { it.asDomain() }
+ )
+ }
+
+ override fun getThreePidsLive(refreshData: Boolean): LiveData> {
+ if (refreshData) {
+ // Force a refresh of the values
+ refreshUserThreePidsTask
+ .configureWith()
+ .executeBy(taskExecutor)
+ }
+
+ return monarchy.findAllMappedWithChanges(
+ { it.where() },
+ { it.asDomain() }
+ )
+ }
+}
+
+private fun UserThreePidEntity.asDomain(): ThreePid {
+ return when (medium) {
+ ThirdPartyIdentifier.MEDIUM_EMAIL -> ThreePid.Email(address)
+ ThirdPartyIdentifier.MEDIUM_MSISDN -> ThreePid.Msisdn(address)
+ else -> error("Invalid medium type")
+ }
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileAPI.kt
index 197d85f879..717497e582 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileAPI.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileAPI.kt
@@ -20,10 +20,12 @@ package im.vector.matrix.android.internal.session.profile
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call
+import retrofit2.http.Body
import retrofit2.http.GET
+import retrofit2.http.POST
import retrofit2.http.Path
-interface ProfileAPI {
+internal interface ProfileAPI {
/**
* Get the combined profile information for this user.
@@ -33,4 +35,24 @@ interface ProfileAPI {
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}")
fun getProfile(@Path("userId") userId: String): Call
+
+ /**
+ * List all 3PIDs linked to the Matrix user account.
+ */
+ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid")
+ fun getThreePIDs(): Call
+
+ /**
+ * Bind a threePid
+ * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-bind
+ */
+ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/bind")
+ fun bindThreePid(@Body body: BindThreePidBody): Call
+
+ /**
+ * Unbind a threePid
+ * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-unbind
+ */
+ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/unbind")
+ fun unbindThreePid(@Body body: UnbindThreePidBody): Call
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileModule.kt
index 7005a5341f..0d7ebe5b62 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileModule.kt
@@ -42,4 +42,13 @@ internal abstract class ProfileModule {
@Binds
abstract fun bindGetProfileTask(task: DefaultGetProfileInfoTask): GetProfileInfoTask
+
+ @Binds
+ abstract fun bindRefreshUserThreePidsTask(task: DefaultRefreshUserThreePidsTask): RefreshUserThreePidsTask
+
+ @Binds
+ abstract fun bindBindThreePidsTask(task: DefaultBindThreePidsTask): BindThreePidsTask
+
+ @Binds
+ abstract fun bindUnbindThreePidsTask(task: DefaultUnbindThreePidsTask): UnbindThreePidsTask
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/RefreshUserThreePidsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/RefreshUserThreePidsTask.kt
new file mode 100644
index 0000000000..9e4d683b8e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/RefreshUserThreePidsTask.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.profile
+
+import com.zhuinden.monarchy.Monarchy
+import im.vector.matrix.android.internal.database.model.UserThreePidEntity
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
+import timber.log.Timber
+import javax.inject.Inject
+
+internal abstract class RefreshUserThreePidsTask : Task
+
+internal class DefaultRefreshUserThreePidsTask @Inject constructor(private val profileAPI: ProfileAPI,
+ private val monarchy: Monarchy,
+ private val eventBus: EventBus) : RefreshUserThreePidsTask() {
+
+ override suspend fun execute(params: Unit) {
+ val accountThreePidsResponse = executeRequest(eventBus) {
+ apiCall = profileAPI.getThreePIDs()
+ }
+
+ Timber.d("Get ${accountThreePidsResponse.threePids?.size} threePids")
+ // Store the list in DB
+ monarchy.writeAsync { realm ->
+ realm.where(UserThreePidEntity::class.java).findAll().deleteAllFromRealm()
+ accountThreePidsResponse.threePids?.forEach {
+ val entity = UserThreePidEntity(
+ it.medium?.takeIf { med -> med in ThirdPartyIdentifier.SUPPORTED_MEDIUM } ?: return@forEach,
+ it.address ?: return@forEach,
+ it.validatedAt.toLong(),
+ it.addedAt.toLong())
+ realm.insertOrUpdate(entity)
+ }
+ }
+ }
+}
+
+private fun Any?.toLong(): Long {
+ return when (this) {
+ null -> 0L
+ is Long -> this
+ is Double -> this.toLong()
+ else -> 0L
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ThirdPartyIdentifier.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ThirdPartyIdentifier.kt
new file mode 100755
index 0000000000..76fa3bd80f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ThirdPartyIdentifier.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.matrix.android.internal.session.profile
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class ThirdPartyIdentifier(
+ /**
+ * Required. The medium of the third party identifier. One of: ["email", "msisdn"]
+ */
+ @Json(name = "medium")
+ val medium: String? = null,
+
+ /**
+ * Required. The third party identifier address.
+ */
+ @Json(name = "address")
+ val address: String? = null,
+
+ /**
+ * Required. The timestamp in milliseconds when this 3PID has been validated.
+ * Define as Object because it should be Long and it is a Double.
+ * So, it might change.
+ */
+ @Json(name = "validated_at")
+ val validatedAt: Any? = null,
+
+ /**
+ * Required. The timestamp in milliseconds when this 3PID has been added to the user account.
+ * Define as Object because it should be Long and it is a Double.
+ * So, it might change.
+ */
+ @Json(name = "added_at")
+ val addedAt: Any? = null
+) {
+ companion object {
+ const val MEDIUM_EMAIL = "email"
+ const val MEDIUM_MSISDN = "msisdn"
+
+ val SUPPORTED_MEDIUM = listOf(MEDIUM_EMAIL, MEDIUM_MSISDN)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/UnbindThreePidBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/UnbindThreePidBody.kt
new file mode 100644
index 0000000000..705569ba87
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/UnbindThreePidBody.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.matrix.android.internal.session.profile
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class UnbindThreePidBody(
+ /**
+ * The identity server to unbind from. If not provided, the homeserver MUST use the id_server the identifier was added through.
+ * If the homeserver does not know the original id_server, it MUST return a id_server_unbind_result of no-support.
+ */
+ @Json(name = "id_server")
+ val identityServerUrlWithoutProtocol: String?,
+
+ /**
+ * Required. The medium of the third party identifier being removed. One of: ["email", "msisdn"]
+ */
+ @Json(name = "medium")
+ val medium: String,
+
+ /**
+ * Required. The third party address being removed.
+ */
+ @Json(name = "address")
+ val address: String
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/UnbindThreePidResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/UnbindThreePidResponse.kt
new file mode 100644
index 0000000000..51467ad201
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/UnbindThreePidResponse.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.matrix.android.internal.session.profile
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class UnbindThreePidResponse(
+ @Json(name = "id_server_unbind_result")
+ val idServerUnbindResult: String?
+) {
+ fun isSuccess() = idServerUnbindResult == "success"
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/UnbindThreePidsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/UnbindThreePidsTask.kt
new file mode 100644
index 0000000000..5206ea9bda
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/UnbindThreePidsTask.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.profile
+
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.matrix.android.api.session.identity.toMedium
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.session.identity.data.IdentityStore
+import im.vector.matrix.android.internal.session.identity.data.getIdentityServerUrlWithoutProtocol
+import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
+import javax.inject.Inject
+
+internal abstract class UnbindThreePidsTask : Task {
+ data class Params(
+ val threePid: ThreePid
+ )
+}
+
+internal class DefaultUnbindThreePidsTask @Inject constructor(private val profileAPI: ProfileAPI,
+ private val identityStore: IdentityStore,
+ private val eventBus: EventBus) : UnbindThreePidsTask() {
+ override suspend fun execute(params: Params): Boolean {
+ val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol()
+ ?: throw IdentityServiceError.NoIdentityServerConfigured
+
+ return executeRequest(eventBus) {
+ apiCall = profileAPI.unbindThreePid(
+ UnbindThreePidBody(
+ identityServerUrlWithoutProtocol,
+ params.threePid.toMedium(),
+ params.threePid.value
+ ))
+ }.isSuccess()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt
index f2bee734ce..bf6b81b57c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt
@@ -35,7 +35,7 @@ import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryE
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.model.TimelineEventEntityFields
-import im.vector.matrix.android.internal.database.query.FilterContent
+import im.vector.matrix.android.internal.database.query.TimelineEventFilter
import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.database.query.whereInRoom
@@ -724,8 +724,11 @@ internal class DefaultTimeline(
`in`(TimelineEventEntityFields.ROOT.TYPE, settings.allowedTypes.toTypedArray())
}
if (settings.filterEdits) {
- not().like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE)
- not().like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.RESPONSE_TYPE)
+ not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
+ not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
+ }
+ if (settings.filterRedacted) {
+ not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED)
}
return this
}
@@ -737,13 +740,19 @@ internal class DefaultTimeline(
} else {
true
}
+ if (!filterType) return@filter false
+
val filterEdits = if (settings.filterEdits && it.root.type == EventType.MESSAGE) {
val messageContent = it.root.content.toModel()
messageContent?.relatesTo?.type != RelationType.REPLACE
} else {
true
}
- filterType && filterEdits
+ if (!filterEdits) return@filter false
+
+ val filterRedacted = settings.filterRedacted && it.root.isRedacted()
+
+ filterRedacted
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt
index 056f942211..72e99701cd 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt
@@ -24,7 +24,7 @@ import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntit
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntityFields
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
-import im.vector.matrix.android.internal.database.query.FilterContent
+import im.vector.matrix.android.internal.database.query.TimelineEventFilter
import im.vector.matrix.android.internal.database.query.whereInRoom
import io.realm.OrderedRealmCollectionChangeListener
import io.realm.Realm
@@ -149,16 +149,21 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
*/
private fun RealmQuery.filterReceiptsWithSettings(): RealmQuery {
beginGroup()
+ var needOr = false
if (settings.filterTypes) {
not().`in`("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", settings.allowedTypes.toTypedArray())
- }
- if (settings.filterTypes && settings.filterEdits) {
- or()
+ needOr = true
}
if (settings.filterEdits) {
- like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", FilterContent.EDIT_TYPE)
+ if (needOr) or()
+ like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", TimelineEventFilter.Content.EDIT)
or()
- like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", FilterContent.RESPONSE_TYPE)
+ like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", TimelineEventFilter.Content.RESPONSE)
+ needOr = true
+ }
+ if (settings.filterRedacted) {
+ if (needOr) or()
+ like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.UNSIGNED_DATA}", TimelineEventFilter.Unsigned.REDACTED)
}
endGroup()
return this
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 68df456831..021b3ed066 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
@@ -25,7 +25,6 @@ import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.task.launchToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
-import kotlinx.coroutines.GlobalScope
import javax.inject.Inject
internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
@@ -45,7 +44,7 @@ internal class DefaultSignOutService @Inject constructor(private val signOutTask
override fun updateCredentials(credentials: Credentials,
callback: MatrixCallback): Cancelable {
- return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
sessionParamsStore.updateCredentials(credentials)
}
}
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 0b8902e71b..5763d397c8 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
@@ -43,13 +43,13 @@ internal class DefaultSignInAgainTask @Inject constructor(
apiCall = signOutAPI.loginAgain(
PasswordLoginParams.userIdentifier(
// Reuse the same userId
- sessionParams.credentials.userId,
+ sessionParams.userId,
params.password,
// The spec says the initial device name will be ignored
// https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login
// but https://github.com/matrix-org/synapse/issues/6525
// Reuse the same deviceId
- deviceId = sessionParams.credentials.deviceId
+ deviceId = sessionParams.deviceId
)
)
}
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 610ade5744..cca0af7feb 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
@@ -20,6 +20,7 @@ import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.cleanup.CleanupSession
+import im.vector.matrix.android.internal.session.identity.IdentityDisconnectTask
import im.vector.matrix.android.internal.task.Task
import org.greenrobot.eventbus.EventBus
import timber.log.Timber
@@ -35,6 +36,7 @@ internal interface SignOutTask : Task {
internal class DefaultSignOutTask @Inject constructor(
private val signOutAPI: SignOutAPI,
private val eventBus: EventBus,
+ private val identityDisconnectTask: IdentityDisconnectTask,
private val cleanupSession: CleanupSession
) : SignOutTask {
@@ -60,6 +62,10 @@ internal class DefaultSignOutTask @Inject constructor(
}
}
+ // Logout from identity server if any
+ runCatching { identityDisconnectTask.execute(Unit) }
+ .onFailure { Timber.w(it, "Unable to disconnect identity server") }
+
Timber.d("SignOut: cleanup session...")
cleanupSession.handle()
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt
index 43a5d8a5cb..d758110e09 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt
@@ -30,9 +30,9 @@ abstract class UserAccountData : AccountDataContent {
const val TYPE_PREVIEW_URLS = "org.matrix.preview_urls"
const val TYPE_WIDGETS = "m.widgets"
const val TYPE_PUSH_RULES = "m.push_rules"
-
- const val ACCOUNT_DATA_TYPE_INTEGRATION_PROVISIONING = "im.vector.setting.integration_provisioning"
- const val ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS = "im.vector.setting.allowed_widgets"
-
+ const val TYPE_INTEGRATION_PROVISIONING = "im.vector.setting.integration_provisioning"
+ const val TYPE_ALLOWED_WIDGETS = "im.vector.setting.allowed_widgets"
+ const val TYPE_IDENTITY_SERVER = "m.identity_server"
+ const val TYPE_ACCEPTED_TERMS = "m.accepted_terms"
}
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataAcceptedTerms.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataAcceptedTerms.kt
new file mode 100644
index 0000000000..ef34503463
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataAcceptedTerms.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.sync.model.accountdata
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class UserAccountDataAcceptedTerms(
+ @Json(name = "type") override val type: String = TYPE_ACCEPTED_TERMS,
+ @Json(name = "content") val content: AcceptedTermsContent
+) : UserAccountData()
+
+@JsonClass(generateAdapter = true)
+internal data class AcceptedTermsContent(
+ @Json(name = "accepted") val acceptedTerms: List = emptyList()
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataAllowedWidgets.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataAllowedWidgets.kt
index ae6ca686ed..43f3b3b32a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataAllowedWidgets.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataAllowedWidgets.kt
@@ -22,6 +22,6 @@ import im.vector.matrix.android.internal.session.integrationmanager.AllowedWidge
@JsonClass(generateAdapter = true)
internal data class UserAccountDataAllowedWidgets(
- @Json(name = "type") override val type: String = ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS,
+ @Json(name = "type") override val type: String = TYPE_ALLOWED_WIDGETS,
@Json(name = "content") val content: AllowedWidgetsContent
) : UserAccountData()
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIdentityServer.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIdentityServer.kt
new file mode 100644
index 0000000000..4af2034d64
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIdentityServer.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.sync.model.accountdata
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class UserAccountDataIdentityServer(
+ @Json(name = "type") override val type: String = TYPE_IDENTITY_SERVER,
+ @Json(name = "content") val content: IdentityServerContent? = null
+) : UserAccountData()
+
+@JsonClass(generateAdapter = true)
+internal data class IdentityServerContent(
+ @Json(name = "base_url") val baseUrl: String? = null
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIntegrationProvisioning.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIntegrationProvisioning.kt
index c65bbd82c1..a47bb761cd 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIntegrationProvisioning.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIntegrationProvisioning.kt
@@ -22,6 +22,6 @@ import im.vector.matrix.android.internal.session.integrationmanager.IntegrationP
@JsonClass(generateAdapter = true)
internal data class UserAccountDataIntegrationProvisioning(
- @Json(name = "type") override val type: String = ACCOUNT_DATA_TYPE_INTEGRATION_PROVISIONING,
+ @Json(name = "type") override val type: String = TYPE_INTEGRATION_PROVISIONING,
@Json(name = "content") val content: IntegrationProvisioningContent
) : UserAccountData()
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/AcceptTermsBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/AcceptTermsBody.kt
new file mode 100644
index 0000000000..c5827b822f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/AcceptTermsBody.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.terms
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * This class represent a list of urls of terms the user wants to accept
+ */
+@JsonClass(generateAdapter = true)
+internal data class AcceptTermsBody(
+ @Json(name = "user_accepts")
+ val acceptedTermUrls: List
+)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/DefaultTermsService.kt
new file mode 100644
index 0000000000..6d5e597da8
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/DefaultTermsService.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.terms
+
+import dagger.Lazy
+import im.vector.matrix.android.api.MatrixCallback
+import im.vector.matrix.android.api.session.events.model.toModel
+import im.vector.matrix.android.api.session.terms.GetTermsResponse
+import im.vector.matrix.android.api.session.terms.TermsService
+import im.vector.matrix.android.api.util.Cancelable
+import im.vector.matrix.android.internal.di.Unauthenticated
+import im.vector.matrix.android.internal.network.NetworkConstants
+import im.vector.matrix.android.internal.network.RetrofitFactory
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.session.identity.IdentityAuthAPI
+import im.vector.matrix.android.internal.session.identity.IdentityRegisterTask
+import im.vector.matrix.android.internal.session.identity.todelete.AccountDataDataSource
+import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
+import im.vector.matrix.android.internal.session.sync.model.accountdata.AcceptedTermsContent
+import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
+import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
+import im.vector.matrix.android.internal.task.TaskExecutor
+import im.vector.matrix.android.internal.task.launchToCallback
+import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
+import okhttp3.OkHttpClient
+import javax.inject.Inject
+
+internal class DefaultTermsService @Inject constructor(
+ @Unauthenticated
+ private val unauthenticatedOkHttpClient: Lazy,
+ private val accountDataDataSource: AccountDataDataSource,
+ private val termsAPI: TermsAPI,
+ private val retrofitFactory: RetrofitFactory,
+ private val getOpenIdTokenTask: GetOpenIdTokenTask,
+ private val identityRegisterTask: IdentityRegisterTask,
+ private val updateUserAccountDataTask: UpdateUserAccountDataTask,
+ private val coroutineDispatchers: MatrixCoroutineDispatchers,
+ private val taskExecutor: TaskExecutor
+) : TermsService {
+ override fun getTerms(serviceType: TermsService.ServiceType,
+ baseUrl: String,
+ callback: MatrixCallback): Cancelable {
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+ val sep = if (baseUrl.endsWith("/")) "" else "/"
+
+ val url = when (serviceType) {
+ TermsService.ServiceType.IntegrationManager -> "$baseUrl$sep${NetworkConstants.URI_INTEGRATION_MANAGER_PATH}"
+ TermsService.ServiceType.IdentityService -> "$baseUrl$sep${NetworkConstants.URI_IDENTITY_PATH_V2}"
+ }
+
+ val termsResponse = executeRequest(null) {
+ apiCall = termsAPI.getTerms("${url}terms")
+ }
+
+ GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData())
+ }
+ }
+
+ override fun agreeToTerms(serviceType: TermsService.ServiceType,
+ baseUrl: String,
+ agreedUrls: List,
+ token: String?,
+ callback: MatrixCallback): Cancelable {
+ return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
+ val sep = if (baseUrl.endsWith("/")) "" else "/"
+
+ val url = when (serviceType) {
+ TermsService.ServiceType.IntegrationManager -> "$baseUrl$sep${NetworkConstants.URI_INTEGRATION_MANAGER_PATH}"
+ TermsService.ServiceType.IdentityService -> "$baseUrl$sep${NetworkConstants.URI_IDENTITY_PATH_V2}"
+ }
+
+ val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl)
+
+ executeRequest(null) {
+ apiCall = termsAPI.agreeToTerms("${url}terms", AcceptTermsBody(agreedUrls), "Bearer $tokenToUse")
+ }
+
+ // client SHOULD update this account data section adding any the URLs
+ // of any additional documents that the user agreed to this list.
+ // Get current m.accepted_terms append new ones and update account data
+ val listOfAcceptedTerms = getAlreadyAcceptedTermUrlsFromAccountData()
+
+ val newList = listOfAcceptedTerms.toMutableSet().apply { addAll(agreedUrls) }.toList()
+
+ updateUserAccountDataTask.execute(UpdateUserAccountDataTask.AcceptedTermsParams(
+ acceptedTermsContent = AcceptedTermsContent(newList)
+ ))
+ }
+ }
+
+ private suspend fun getToken(url: String): String {
+ // TODO This is duplicated code see DefaultIdentityService
+ val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
+
+ val openIdToken = getOpenIdTokenTask.execute(Unit)
+ val token = identityRegisterTask.execute(IdentityRegisterTask.Params(api, openIdToken))
+
+ return token.token
+ }
+
+ private fun getAlreadyAcceptedTermUrlsFromAccountData(): Set {
+ return accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ACCEPTED_TERMS)
+ ?.content
+ ?.toModel()
+ ?.acceptedTerms
+ ?.toSet()
+ .orEmpty()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/identity/IdentityPingApi.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsAPI.kt
similarity index 53%
rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/identity/IdentityPingApi.kt
rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsAPI.kt
index 2a0e00704c..03b745f8d7 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/identity/IdentityPingApi.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsAPI.kt
@@ -13,22 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package im.vector.matrix.android.internal.identity
-import im.vector.matrix.android.internal.network.NetworkConstants
+package im.vector.matrix.android.internal.session.terms
+
+import im.vector.matrix.android.internal.network.HttpHeaders
import retrofit2.Call
+import retrofit2.http.Body
import retrofit2.http.GET
+import retrofit2.http.Header
+import retrofit2.http.POST
+import retrofit2.http.Url
-internal interface IdentityPingApi {
+internal interface TermsAPI {
+ /**
+ * This request does not require authentication
+ */
+ @GET
+ fun getTerms(@Url url: String): Call
/**
- * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
- * Simple ping call to check if server alive
- *
- * Ref: https://matrix.org/docs/spec/identity_service/unstable#status-check
- *
- * @return 200 in case of success
+ * This request requires authentication
*/
- @GET(NetworkConstants.URI_API_PREFIX_IDENTITY)
- fun ping(): Call
+ @POST
+ fun agreeToTerms(@Url url: String, @Body params: AcceptTermsBody, @Header(HttpHeaders.Authorization) token: String): Call
}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsModule.kt
new file mode 100644
index 0000000000..eee7e22134
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsModule.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.terms
+
+import dagger.Binds
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import im.vector.matrix.android.api.session.terms.TermsService
+import im.vector.matrix.android.internal.di.Unauthenticated
+import im.vector.matrix.android.internal.network.RetrofitFactory
+import im.vector.matrix.android.internal.session.SessionScope
+import okhttp3.OkHttpClient
+
+@Module
+internal abstract class TermsModule {
+
+ @Module
+ companion object {
+ @Provides
+ @JvmStatic
+ @SessionScope
+ fun providesTermsAPI(@Unauthenticated unauthenticatedOkHttpClient: Lazy,
+ retrofitFactory: RetrofitFactory): TermsAPI {
+ val retrofit = retrofitFactory.create(unauthenticatedOkHttpClient, "https://foo.bar")
+ return retrofit.create(TermsAPI::class.java)
+ }
+ }
+
+ @Binds
+ abstract fun bindTermsService(service: DefaultTermsService): TermsService
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsResponse.kt
new file mode 100644
index 0000000000..7c6451e3a0
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsResponse.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.matrix.android.internal.session.terms
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import im.vector.matrix.android.api.util.JsonDict
+import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
+
+/**
+ * This class represent a localized privacy policy for registration Flow.
+ */
+@JsonClass(generateAdapter = true)
+data class TermsResponse(
+ @Json(name = "policies")
+ val policies: JsonDict? = null
+) {
+
+ fun getLocalizedTerms(userLanguage: String,
+ defaultLanguage: String = "en"): List {
+ return policies?.map {
+ val tos = policies[it.key] as? Map<*, *> ?: return@map null
+ ((tos[userLanguage] ?: tos[defaultLanguage]) as? Map<*, *>)?.let { termsMap ->
+ val name = termsMap[NAME] as? String
+ val url = termsMap[URL] as? String
+ LocalizedFlowDataLoginTerms(
+ policyName = it.key,
+ localizedUrl = url,
+ localizedName = name,
+ version = tos[VERSION] as? String
+ )
+ }
+ }?.filterNotNull() ?: emptyList()
+ }
+
+ private companion object {
+ const val VERSION = "version"
+ const val NAME = "name"
+ const val URL = "url"
+ }
+}
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 22ab61c93a..8db2ad9781 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
@@ -20,7 +20,9 @@ import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.integrationmanager.AllowedWidgetsContent
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationProvisioningContent
+import im.vector.matrix.android.internal.session.sync.model.accountdata.AcceptedTermsContent
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
+import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
import im.vector.matrix.android.internal.task.Task
import org.greenrobot.eventbus.EventBus
@@ -33,6 +35,24 @@ internal interface UpdateUserAccountDataTask : Task>
@@ -52,7 +72,7 @@ internal interface UpdateUserAccountDataTask : Task this
+ !startsWith("http") -> "https://$this"
+ else -> this
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/GetWellknownTask.kt
similarity index 96%
rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/GetWellknownTask.kt
rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/GetWellknownTask.kt
index 8ed2cb3b0f..c6f6b8752d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/GetWellknownTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/GetWellknownTask.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package im.vector.matrix.android.internal.auth.wellknown
+package im.vector.matrix.android.internal.wellknown
import android.util.MalformedJsonException
import dagger.Lazy
@@ -23,10 +23,10 @@ import im.vector.matrix.android.api.auth.data.WellKnown
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.internal.di.Unauthenticated
-import im.vector.matrix.android.internal.identity.IdentityPingApi
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.homeserver.CapabilitiesAPI
+import im.vector.matrix.android.internal.session.identity.IdentityAuthAPI
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.isValidUrl
import okhttp3.OkHttpClient
@@ -119,7 +119,7 @@ internal class DefaultGetWellknownTask @Inject constructor(
try {
executeRequest(null) {
- apiCall = capabilitiesAPI.getVersions()
+ apiCall = capabilitiesAPI.ping()
}
} catch (throwable: Throwable) {
return WellknownResult.FailError
@@ -153,7 +153,7 @@ internal class DefaultGetWellknownTask @Inject constructor(
*/
private suspend fun validateIdentityServer(identityServerBaseUrl: String): Boolean {
val identityPingApi = retrofitFactory.create(okHttpClient, identityServerBaseUrl)
- .create(IdentityPingApi::class.java)
+ .create(IdentityAuthAPI::class.java)
return try {
executeRequest(null) {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/WellKnownAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/WellKnownAPI.kt
similarity index 94%
rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/WellKnownAPI.kt
rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/WellKnownAPI.kt
index 71928123bf..ec62707db7 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/WellKnownAPI.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/WellKnownAPI.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package im.vector.matrix.android.internal.auth.wellknown
+package im.vector.matrix.android.internal.wellknown
import im.vector.matrix.android.api.auth.data.WellKnown
import retrofit2.Call
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/FilterContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/WellknownModule.kt
similarity index 60%
rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/FilterContent.kt
rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/WellknownModule.kt
index 6e89a28b7d..2705803fec 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/FilterContent.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/WellknownModule.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package im.vector.matrix.android.internal.database.query
+package im.vector.matrix.android.internal.wellknown
-internal object FilterContent {
+import dagger.Binds
+import dagger.Module
- internal const val EDIT_TYPE = """{*"m.relates_to"*"rel_type":*"m.replace"*}"""
- internal const val RESPONSE_TYPE = """{*"m.relates_to"*"rel_type":*"m.response"*}"""
+@Module
+internal abstract class WellknownModule {
+
+ @Binds
+ abstract fun bindGetWellknownTask(task: DefaultGetWellknownTask): GetWellknownTask
}
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 dc874c2b94..871d01175e 100644
--- a/matrix-sdk-android/src/main/res/values-de/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-de/strings.xml
@@ -179,8 +179,8 @@
%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 ist dem Raum beigetreten. Grund: %2$s
+ %1$s hat den Raum verlassen. Grund: %2$s
%1$s hat die Einladung abgelehnt. Grund: %2$s
%1$s hat %2$s gekickt. Grund: %3$s
%1$s hat Sperre von %2$s aufgehoben. Grund: %3$s
diff --git a/matrix-sdk-android/src/main/res/values-eo/strings.xml b/matrix-sdk-android/src/main/res/values-eo/strings.xml
index c88d96d610..69600394ac 100644
--- a/matrix-sdk-android/src/main/res/values-eo/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-eo/strings.xml
@@ -3,20 +3,210 @@
%1$s sendis bildon.
%1$s sendis glumarkon.
- invito de %s
- %1$s invitis %2$s
+ Invito de %s
+ %1$s invitis uzanton %2$s
%1$s invitis vin
%1$s alvenis
%1$s foriris
%1$s malakceptis la inviton
- %1$s forpelis %2$s
- %1$s malforbaris %2$s
- %1$s forbaris %2$s
- %1$s malinvitis %2$s
+ %1$s forpelis uzanton %2$s
+ %1$s malforbaris uzanton %2$s
+ %1$s forbaris uzanton %2$s
+ %1$s nuligis inviton por %2$s
%1$s ŝanĝis sian profilbildon
** Ne eblas malĉifri: %s **
La aparato de la sendanto ne sendis al ni la ŝlosilojn por tiu mesaĝo.
- Respondanta al
+ Responde al
+
+ %1$s: %2$s
+ %1$s ŝanĝis sian vidigan nomon al %2$s
+ %1$s ŝanĝis sian vidigan nomon de %2$s al %3$s
+ %1$s forigis sian vidigan nomon (%2$s)
+ %1$s ŝanĝis la temon al: %2$s
+ %1$s ŝanĝis nomon de la ĉambro al: %2$s
+ %s vidvokis.
+ %s voĉvokis.
+ %s respondis la vokon.
+ %s finis la vokon.
+ %1$s videbligis estontan historion de ĉambro al %2$s
+ ĉiuj ĉambranoj, ekde iliaj invitoj.
+ ĉiuj ĉambranoj, ekde iliaj aliĝoj.
+ ĉiuj ĉambranoj.
+ ĉiu ajn.
+ nekonata (%s).
+ %1$s ŝaltis tutvojan ĉifradon (%2$s)
+ %s gradaltigis la ĉambron.
+
+ Mesaĝo foriĝis
+ Mesaĝo foriĝis de %1$s
+ Mesaĝo foriĝis [kialo: %1$s]
+ Mesaĝo foriĝis de %1$s [kialo: %2$s]
+ %1$s ĝisdatigis sian profilon %2$s
+ %1$s sendis aliĝan inviton al %2$s
+ %1$s nuligis la aliĝan inviton por %2$s
+ %1$s akceptis la inviton por %2$s
+
+ Ne povis redakti
+ Ne povas sendi mesaĝon
+
+ Malsukcesis alŝuti bildon
+
+ Reta eraro
+ Matrix-eraro
+
+ Nun ne eblas re-aliĝi al malplena ĉambro
+
+ Ĉifrita mesaĝo
+
+ Retpoŝtadreso
+ Telefonnumero
+
+ sendis bildon.
+ sendis filmon.
+ sendis sondosieron.
+ sendis dosieron.
+
+ Invito de %s
+ Ĉambra invito
+
+ %1$s kaj %2$s
+
+
+ - %1$s kaj 1 alia
+ - %1$s kaj %2$d aliaj
+
+
+ Malplena ĉambro
+
+
+ Hundo
+ Kato
+ Leono
+ Ĉevalo
+ Unukorno
+ Porko
+ Elefanto
+ Kuniklo
+ Pando
+ Koko
+ Pingveno
+ Testudo
+ Fiŝo
+ Polpo
+ Papilio
+ Floro
+ Arbo
+ Kakto
+ Fungo
+ Globo
+ Luno
+ Nubo
+ Fajro
+ Banano
+ Pomo
+ Frago
+ Maizo
+ Pico
+ Kuko
+ Koro
+ Mieneto
+ Roboto
+ Ĉapelo
+ Okulvitroj
+ Boltilo
+ Kristnaska viro
+ Dikfingro supren
+ Ombrelo
+ Sablohorloĝo
+ Horloĝo
+ Donaco
+ Lampo
+ Libro
+ Grifelo
+ Paperkuntenilo
+ Tondilo
+ Seruro
+ Ŝlosilo
+ Martelo
+ Telefono
+ Flago
+ Vagonaro
+ Biciklo
+ Aviadilo
+ Raketo
+ Trofeo
+ Pilko
+ Gitaro
+ Trumpeto
+ Sonorilo
+ Ankro
+ Kapaŭdilo
+ Dosierujo
+ Pinglo
+
+ Komenca spegulado:
+\nEnportante konton…
+ Komenca spegulado:
+\nEnportante ĉifrilojn
+ Komenca spegulado:
+\nEnportante ĉambrojn
+ Komenca spegulado:
+\nEnportante aliĝitajn ĉambrojn
+ Komenca spegulado:
+\nEnportante ĉambrojn de invitoj
+ Komenca spegulado:
+\nEnportante forlasitajn ĉambrojn
+ Komenca spegulado:
+\nEnportante komunumojn
+ Komenca spegulado:
+\nEnportante datumojn de konto
+
+ Sendante mesaĝon…
+ Vakigi sendan atendovicon
+
+ %1$s petis grupan vokon
+ Grupa voko komenciĝis
+ Grupa voko finiĝis
+
+ (ankaŭ profilbildo ŝanĝiĝis)
+ %1$s forigis nomon de la ĉambro
+ %1$s forigis temon de la ĉambro
+ Invito de %1$s. Kialo: %2$s
+ %1$s invitis uzanton %2$s. Kialo: %3$s
+ %1$s invitis vin. Kialo: %2$s
+ %1$s aliĝis al la ĉambro. Kialo: %2$s
+ %1$s foriris de la ĉambro. Kialo: %2$s
+ %1$s rifuzis la inviton. Kialo: %2$s
+ %1$s forpelis uzanton %2$s. Kialo: %3$s
+ %1$s malforbaris uzanton %2$s. Kialo: %3$s
+ %1$s forbaris uzanton %2$s. Kialo: %3$s
+ %1$s sendis inviton al la ĉambro al %2$s. Kialo: %3$s
+ %1$s nuligis la inviton al la ĉambro al %2$s. Kialo: %3$s
+ %1$s akceptis la inviton por %2$s. Kialo: %3$s
+ %1$s nuligis la inviton al %2$s. Kialo: %3$s
+
+
+ - %1$s aldonis %2$s kiel adreson por ĉi tiu ĉambro.
+ - %1$s aldonis %2$s kiel adresojn por ĉi tiu ĉambro.
+
+
+
+ - %1$s forigis %2$s kiel adreson por ĉi tiu ĉambro.
+ - %1$s forigis %2$s kiel adresojn por ĉi tiu ĉambro.
+
+
+ %1$s aldonis %2$s kaj forigis %3$s kiel adresojn por ĉi tiu ĉambro.
+
+ %1$s agordis la ĉefadreson por ĉi tiu ĉambro al %2$s.
+ %1$s forigis la ĉefadreson de ĉi tiu ĉambro.
+
+ %1$s permesis al gastoj aliĝi al la ĉambro.
+ %1$s malpermesis al gastoj aliĝi al la ĉambro.
+
+ %1$s ŝaltis tutvojan ĉifradon.
+ %1$s ŝaltis tutvojan ĉifradon (kun nerekonita algoritmo %2$s).
+
+ %s petas kontrolon de via ŝlosilo, sed via kliento ne subtenas kontrolon de ŝlosiloj en la babilujo. Vi devos uzi malnovecan kontrolon de ŝlosiloj.
diff --git a/matrix-sdk-android/src/main/res/values-et/strings.xml b/matrix-sdk-android/src/main/res/values-et/strings.xml
new file mode 100644
index 0000000000..1d52c2a7a1
--- /dev/null
+++ b/matrix-sdk-android/src/main/res/values-et/strings.xml
@@ -0,0 +1,188 @@
+
+
+ %1$s: %2$s
+ %1$s saatis pildi.
+ %1$s saatis kleepsu.
+
+ Kasutaja %s kutse
+ %1$s kutsus kasutajat %2$s
+ %1$s kutsus sind
+ %1$s liitus jututoaga
+ %1$s lahkus jututoast
+ %1$s lükkas tagasi kutse
+ %1$s müksas kasutajat %2$s
+ %1$s võttis tagasi kutse kasutajale %2$s
+ %1$s muutis oma avatari
+ %1$s määras oma kuvatavaks nimeks %2$s
+ %1$s muutis senise kuvatava nime %2$s uueks nimeks %3$s
+ %1$s eemaldas oma kuvatava nime (%2$s)
+ %1$s muutis uueks teemaks %2$s
+ %1$s muutis jututoa uueks nimeks %2$s
+ %s alustas videokõnet.
+ %s alustas häälkõnet.
+ %s vastas kõnele.
+ %s lõpetas kõne.
+ %1$s seadistas, et tulevane jututoa ajalugu on nähtav kasutajale %2$s
+ kõikidele jututoa liikmetele alates kutsumise hetkest.
+ kõikidele jututoa liikmetele alates liitumise hetkest.
+ kõikidele jututoa liikmetele.
+ kõikidele.
+ teadmata (%s).
+ %1$s lülitas sisse läbiva krüptimise (%2$s)
+ %s uuendas seda jututuba.
+
+ %1$s saatis VoIP konverentsi kutse
+ VoIP-konverents algas
+ VoIP-konverents lõppes
+
+ (samuti sai avatar muudetud)
+ %1$s eemaldas jututoa nime
+ %1$s eemaldas jututoa teema
+ Sõnum on eemaldatud
+ Sõnum on eemaldatud %1$s poolt
+ Sõnum on eemaldatud [põhjus: %1$s]
+ Sõnum on eemaldatud %1$s poolt [põhjus: %2$s]
+ %1$s uuendas oma profiili %2$s
+ %1$s saatis jututoaga liitumiseks kutse kasutajale %2$s
+ %1$s võttis tagasi jututoaga liitumise kutse kasutajalt %2$s
+ %1$s võttis vastu kutse %2$s nimel
+
+ ** Ei õnnestu dekrüptida: %s **
+ Sõnumi saatja seade ei ole selle sõnumi jaoks saatnud dekrüptimisvõtmeid.
+
+ Vastuseks kasutajale
+
+ Ei saanud muuta sõnumit
+ Sõnumi saatmine ei õnnestunud
+
+ Faili üles laadimine ei õnnestunud
+
+ Võrguühenduse viga
+ Matrix\'i viga
+
+ Hetkel ei ole võimalik uuesti liituda tühja jututoaga.
+
+ Krüptitud sõnum
+
+ E-posti aadress
+ Telefoninumber
+
+ saatis pildi.
+ saatis video.
+ saatis helifaili.
+ saatis faili.
+
+ Kutse kasutajalt %s
+ Kutse jututuppa
+
+ %1$s ja %2$s
+
+
+ - %1$s ja üks muu
+ - %1$s ja %2$d muud
+
+
+ Tühi jututuba
+
+
+ Koer
+ Kass
+ Lõvi
+ Hobune
+ Ükssarvik
+ Siga
+ Elevant
+ Jänes
+ Panda
+ Kukk
+ Pingviin
+ Kilpkonn
+ Kala
+ Kaheksajalg
+ Liblikas
+ Lill
+ Puu
+ Kaktus
+ Seen
+ Maakera
+ Kuu
+ Pilv
+ Tuli
+ Banaan
+ Õun
+ Maasikas
+ Mais
+ Pitsa
+ Kook
+ Süda
+ Smaili
+ Robot
+ Müts
+ Prillid
+ Mutrivõti
+ Jõuluvana
+ Pöidlad püsti
+ Vihmavari
+ Liivakell
+ Kell
+ Kingitus
+ Lambipirn
+ Raamat
+ Pliiats
+ Kirjaklamber
+ Käärid
+ Lukk
+ Võti
+ Haamer
+ Telefon
+ Lipp
+ Rong
+ Jalgratas
+ Lennuk
+ Rakett
+ Auhind
+ Pall
+ Kitarr
+ Trompet
+ Kelluke
+ Ankur
+ Kõrvaklapid
+ Kaust
+ Knopka
+
+ Alglaadimine:
+\nImpordin kontot…
+ Alglaadimine:
+\nImpordin krüptoseadistusi
+ Alglaadimine:
+\nImpordin jututubasid
+ Alglaadimine:
+\nImpordin liitutud jututubasid
+ Alglaadimine:
+\nImpordin kutsutud jututubasid
+ Alglaadimine:
+\nImpordin lahkutud jututubasid
+ Alglaadimine:
+\nImpordin kogukondi
+ Alglaadimine:
+\nImpordin kontoandmeid
+
+ Saadan sõnumit…
+ Tühjenda saatmisjärjekord
+
+ Kasutaja %1$s kutse. Põhjus: %2$s
+ %1$s kutsus kasutajat %2$s. Põhjus: %3$s
+ %1$s kutsus sind. Põhjus: %2$s
+ %1$s liitus jututoaga. Põhjus: %2$s
+ %1$s lahkus jututoast. Põhjus: %2$s
+ %1$s lükkas kutse tagasi. Põhjus: %2$s
+ %1$s müksas välja kasutaja %2$s. Põhjus: %3$s
+ %1$s saatis kasutajale %2$s kutse jututoaga liitumiseks. Põhjus: %3$s
+ %1$s tühistas kasutajale %2$s saadetud kutse jututoaga liitumiseks. Põhjus: %3$s
+ %1$s võttis vastu kutse %2$s jututoaga liitumiseks. Põhjus: %3$s
+ %1$s võttis tagasi kasutajale %2$s saadetud kutse. Põhjus: %3$s
+
+ %1$s lülitas sisse läbiva krüptimise.
+ %1$s lülitas sisse läbiva krüptimise (tundmatu algoritm %2$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 9487aa7db4..8dd87b6b6a 100644
--- a/matrix-sdk-android/src/main/res/values-fi/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-fi/strings.xml
@@ -209,4 +209,6 @@
%1$s laittoi päälle osapuolten välisen salauksen.
%1$s laittoi päälle osapuolisten välisen salauksen (tuntematon algoritmi %2$s).
+ %s haluaa varmentaa salausavaimesi, mutta asiakasohjelmasi ei tue keskustelun aikana tapahtuvaa avainten varmennusta. Joudut käyttämään perinteistä varmennustapaa.
+
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 e966f22064..521c805be8 100644
--- a/matrix-sdk-android/src/main/res/values-sq/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-sq/strings.xml
@@ -4,8 +4,8 @@
%1$s dërgoi një figurë.
%1$s ftoi %2$s
%1$s ju ftoi
- %1$s u bë pjesë
- %1$s iku
+ %1$s hyri në dhomë
+ %1$s doli nga dhoma
%1$s hodhi tej ftesën
%1$s përzuri %2$s
%1$s dëboi %2$s
@@ -172,8 +172,8 @@
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 erdhi në dhomë. Arsye: %2$s
+ %1$s doli nga dhoma. 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
diff --git a/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml b/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml
index 38affc0599..cdd9c5eb8d 100644
--- a/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml
@@ -5,8 +5,8 @@
%s 的邀请
%1$s 邀请了 %2$s
%1$s 邀请了您
- %1$s 加入了
- %1$s 离开了
+ %1$s 加入了聊天室
+ %1$s 离开了聊天室
%1$s 拒绝了邀请
%1$s 移除了 %2$s
%1$s 解封了 %2$s
@@ -173,8 +173,8 @@
%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
%1$s 已拒绝邀请。理由:%2$s
%1$s 踢走了 %2$s。理由:%3$s
%1$s 取消封锁了 %2$s。理由:%3$s
diff --git a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml
deleted file mode 100644
index 6eb46fd7df..0000000000
--- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
diff --git a/tools/import_from_riot.sh b/tools/import_from_riot.sh
deleted file mode 100755
index a2b68a347c..0000000000
--- a/tools/import_from_riot.sh
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/usr/bin/env bash
-
-#
-# 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.
-#
-
-# Exit on any error
-set -e
-
-echo
-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
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-cs/strings.xml ./matrix-sdk-android/src/main/res/values-cs/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-da/strings.xml ./matrix-sdk-android/src/main/res/values-da/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-de/strings.xml ./matrix-sdk-android/src/main/res/values-de/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-el/strings.xml ./matrix-sdk-android/src/main/res/values-el/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-eo/strings.xml ./matrix-sdk-android/src/main/res/values-eo/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-en-rGB/strings.xml ./matrix-sdk-android/src/main/res/values-en-rGB/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-es/strings.xml ./matrix-sdk-android/src/main/res/values-es/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-es-rMX/strings.xml ./matrix-sdk-android/src/main/res/values-es-rMX/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-eu/strings.xml ./matrix-sdk-android/src/main/res/values-eu/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-fa/strings.xml ./matrix-sdk-android/src/main/res/values-fa/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-fi/strings.xml ./matrix-sdk-android/src/main/res/values-fi/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-fr/strings.xml ./matrix-sdk-android/src/main/res/values-fr/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-gl/strings.xml ./matrix-sdk-android/src/main/res/values-gl/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-hu/strings.xml ./matrix-sdk-android/src/main/res/values-hu/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-id/strings.xml ./matrix-sdk-android/src/main/res/values-id/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-in/strings.xml ./matrix-sdk-android/src/main/res/values-in/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-is/strings.xml ./matrix-sdk-android/src/main/res/values-is/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-it/strings.xml ./matrix-sdk-android/src/main/res/values-it/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-ja/strings.xml ./matrix-sdk-android/src/main/res/values-ja/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-ko/strings.xml ./matrix-sdk-android/src/main/res/values-ko/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-lv/strings.xml ./matrix-sdk-android/src/main/res/values-lv/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-nl/strings.xml ./matrix-sdk-android/src/main/res/values-nl/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-nn/strings.xml ./matrix-sdk-android/src/main/res/values-nn/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-pl/strings.xml ./matrix-sdk-android/src/main/res/values-pl/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-pt/strings.xml ./matrix-sdk-android/src/main/res/values-pt/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-pt-rBR/strings.xml ./matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-ru/strings.xml ./matrix-sdk-android/src/main/res/values-ru/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-sk/strings.xml ./matrix-sdk-android/src/main/res/values-sk/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-sq/strings.xml ./matrix-sdk-android/src/main/res/values-sq/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-te/strings.xml ./matrix-sdk-android/src/main/res/values-te/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-th/strings.xml ./matrix-sdk-android/src/main/res/values-th/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-uk/strings.xml ./matrix-sdk-android/src/main/res/values-uk/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-vls/strings.xml ./matrix-sdk-android/src/main/res/values-vls/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-zh-rCN/strings.xml ./matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml
-cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-zh-rTW/strings.xml ./matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml
-
-echo
-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
-cp ../riot-android/vector/src/main/res/values-bs/strings.xml ./vector/src/main/res/values-bs/strings.xml
-cp ../riot-android/vector/src/main/res/values-ca/strings.xml ./vector/src/main/res/values-ca/strings.xml
-cp ../riot-android/vector/src/main/res/values-cs/strings.xml ./vector/src/main/res/values-cs/strings.xml
-cp ../riot-android/vector/src/main/res/values-cy/strings.xml ./vector/src/main/res/values-cy/strings.xml
-cp ../riot-android/vector/src/main/res/values-da/strings.xml ./vector/src/main/res/values-da/strings.xml
-cp ../riot-android/vector/src/main/res/values-de/strings.xml ./vector/src/main/res/values-de/strings.xml
-cp ../riot-android/vector/src/main/res/values-el/strings.xml ./vector/src/main/res/values-el/strings.xml
-cp ../riot-android/vector/src/main/res/values-eo/strings.xml ./vector/src/main/res/values-eo/strings.xml
-cp ../riot-android/vector/src/main/res/values-es/strings.xml ./vector/src/main/res/values-es/strings.xml
-cp ../riot-android/vector/src/main/res/values-es-rMX/strings.xml ./vector/src/main/res/values-es-rMX/strings.xml
-cp ../riot-android/vector/src/main/res/values-eu/strings.xml ./vector/src/main/res/values-eu/strings.xml
-cp ../riot-android/vector/src/main/res/values-fa/strings.xml ./vector/src/main/res/values-fa/strings.xml
-cp ../riot-android/vector/src/main/res/values-fi/strings.xml ./vector/src/main/res/values-fi/strings.xml
-cp ../riot-android/vector/src/main/res/values-fy/strings.xml ./vector/src/main/res/values-fy/strings.xml
-cp ../riot-android/vector/src/main/res/values-fr/strings.xml ./vector/src/main/res/values-fr/strings.xml
-cp ../riot-android/vector/src/main/res/values-fr-rCA/strings.xml ./vector/src/main/res/values-fr-rCA/strings.xml
-cp ../riot-android/vector/src/main/res/values-gl/strings.xml ./vector/src/main/res/values-gl/strings.xml
-cp ../riot-android/vector/src/main/res/values-hu/strings.xml ./vector/src/main/res/values-hu/strings.xml
-cp ../riot-android/vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-id/strings.xml
-cp ../riot-android/vector/src/main/res/values-in/strings.xml ./vector/src/main/res/values-in/strings.xml
-cp ../riot-android/vector/src/main/res/values-is/strings.xml ./vector/src/main/res/values-is/strings.xml
-cp ../riot-android/vector/src/main/res/values-it/strings.xml ./vector/src/main/res/values-it/strings.xml
-cp ../riot-android/vector/src/main/res/values-ja/strings.xml ./vector/src/main/res/values-ja/strings.xml
-cp ../riot-android/vector/src/main/res/values-ko/strings.xml ./vector/src/main/res/values-ko/strings.xml
-cp ../riot-android/vector/src/main/res/values-lv/strings.xml ./vector/src/main/res/values-lv/strings.xml
-cp ../riot-android/vector/src/main/res/values-nb-rNO/strings.xml ./vector/src/main/res/values-nb-rNO/strings.xml
-cp ../riot-android/vector/src/main/res/values-nl/strings.xml ./vector/src/main/res/values-nl/strings.xml
-cp ../riot-android/vector/src/main/res/values-nn/strings.xml ./vector/src/main/res/values-nn/strings.xml
-cp ../riot-android/vector/src/main/res/values-pl/strings.xml ./vector/src/main/res/values-pl/strings.xml
-cp ../riot-android/vector/src/main/res/values-pt/strings.xml ./vector/src/main/res/values-pt/strings.xml
-cp ../riot-android/vector/src/main/res/values-pt-rBR/strings.xml ./vector/src/main/res/values-pt-rBR/strings.xml
-cp ../riot-android/vector/src/main/res/values-ro/strings.xml ./vector/src/main/res/values-ro/strings.xml
-cp ../riot-android/vector/src/main/res/values-ru/strings.xml ./vector/src/main/res/values-ru/strings.xml
-cp ../riot-android/vector/src/main/res/values-sk/strings.xml ./vector/src/main/res/values-sk/strings.xml
-cp ../riot-android/vector/src/main/res/values-sq/strings.xml ./vector/src/main/res/values-sq/strings.xml
-cp ../riot-android/vector/src/main/res/values-sr/strings.xml ./vector/src/main/res/values-sr/strings.xml
-cp ../riot-android/vector/src/main/res/values-te/strings.xml ./vector/src/main/res/values-te/strings.xml
-cp ../riot-android/vector/src/main/res/values-th/strings.xml ./vector/src/main/res/values-th/strings.xml
-cp ../riot-android/vector/src/main/res/values-tlh/strings.xml ./vector/src/main/res/values-tlh/strings.xml
-cp ../riot-android/vector/src/main/res/values-tr/strings.xml ./vector/src/main/res/values-tr/strings.xml
-cp ../riot-android/vector/src/main/res/values-uk/strings.xml ./vector/src/main/res/values-uk/strings.xml
-cp ../riot-android/vector/src/main/res/values-vls/strings.xml ./vector/src/main/res/values-vls/strings.xml
-cp ../riot-android/vector/src/main/res/values-zh-rCN/strings.xml ./vector/src/main/res/values-zh-rCN/strings.xml
-cp ../riot-android/vector/src/main/res/values-zh-rTW/strings.xml ./vector/src/main/res/values-zh-rTW/strings.xml
-
-echo
-echo "Success!"
diff --git a/tools/templates/RiotXFeature/root/src/app_package/ViewModel.kt.ftl b/tools/templates/RiotXFeature/root/src/app_package/ViewModel.kt.ftl
index f4090b40e6..1d2ec0a069 100644
--- a/tools/templates/RiotXFeature/root/src/app_package/ViewModel.kt.ftl
+++ b/tools/templates/RiotXFeature/root/src/app_package/ViewModel.kt.ftl
@@ -6,6 +6,7 @@ import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
+import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.VectorViewModel
<#if createViewEvents>
@@ -38,7 +39,8 @@ class ${viewModelClass} @AssistedInject constructor(@Assisted initialState: ${vi
}
override fun handle(action: ${actionClass}) {
- //TODO
- }
+ when (action) {
+ }.exhaustive
+ }
}
diff --git a/vector/build.gradle b/vector/build.gradle
index 459b297fd6..74fc96a425 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -15,7 +15,7 @@ androidExtensions {
}
ext.versionMajor = 0
-ext.versionMinor = 20
+ext.versionMinor = 21
ext.versionPatch = 0
static def getGitTimestamp() {
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index ae0ffa1f91..7c2939707f 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -161,6 +161,8 @@
android:name=".features.attachments.preview.AttachmentsPreviewActivity"
android:theme="@style/AppTheme.AttachmentsPreview" />
+
+
null
+ is IdentityServiceError -> identityServerError(throwable)
is Failure.NetworkConnection -> {
when {
throwable.ioException is SocketTimeoutException ->
@@ -107,4 +109,16 @@ class DefaultErrorFormatter @Inject constructor(
stringProvider.getQuantityString(R.plurals.login_error_limit_exceeded_retry_after, delaySeconds, delaySeconds)
}
}
+
+ private fun identityServerError(identityServiceError: IdentityServiceError): String {
+ return stringProvider.getString(when (identityServiceError) {
+ IdentityServiceError.OutdatedIdentityServer -> R.string.identity_server_error_outdated_identity_server
+ IdentityServiceError.OutdatedHomeServer -> R.string.identity_server_error_outdated_home_server
+ IdentityServiceError.NoIdentityServerConfigured -> R.string.identity_server_error_no_identity_server_configured
+ IdentityServiceError.TermsNotSignedException -> R.string.identity_server_error_terms_not_signed
+ IdentityServiceError.BulkLookupSha256NotSupported -> R.string.identity_server_error_bulk_sha256_not_supported
+ IdentityServiceError.BindingError -> R.string.identity_server_error_binding_error
+ IdentityServiceError.NoCurrentBindingError -> R.string.identity_server_error_no_current_binding_error
+ })
+ }
}
diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt
index f9f5d3b3d2..b74f143e17 100644
--- a/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt
+++ b/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt
@@ -16,6 +16,7 @@
package im.vector.riotx.core.extensions
+import android.app.Activity
import android.os.Parcelable
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
@@ -59,3 +60,8 @@ fun VectorBaseActivity.addFragmentToBackstack(frameId: Int,
fun VectorBaseActivity.hideKeyboard() {
currentFocus?.hideKeyboard()
}
+
+fun Activity.restart() {
+ startActivity(intent)
+ finish()
+}
diff --git a/vector/src/main/java/im/vector/riotx/core/platform/SimpleFragmentActivity.kt b/vector/src/main/java/im/vector/riotx/core/platform/SimpleFragmentActivity.kt
index e8e8f21259..58ec4b22c6 100644
--- a/vector/src/main/java/im/vector/riotx/core/platform/SimpleFragmentActivity.kt
+++ b/vector/src/main/java/im/vector/riotx/core/platform/SimpleFragmentActivity.kt
@@ -26,7 +26,6 @@ import im.vector.matrix.android.api.session.Session
import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.hideKeyboard
-import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.activity.*
import javax.inject.Inject
@@ -108,15 +107,4 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() {
}
super.onBackPressed()
}
-
- protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
- viewEvents
- .observe()
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- hideWaitingView()
- observer(it)
- }
- .disposeOnDestroy()
- }
}
diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt
index 08cf8e57e1..770a63a3fa 100644
--- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt
+++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt
@@ -70,6 +70,7 @@ import im.vector.riotx.features.settings.VectorPreferences
import im.vector.riotx.features.themes.ActivityOtherThemes
import im.vector.riotx.features.themes.ThemeUtils
import im.vector.riotx.receivers.DebugReceiver
+import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import timber.log.Timber
@@ -94,6 +95,18 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
protected val viewModelProvider
get() = ViewModelProvider(this, viewModelFactory)
+ // TODO Other Activity should use this also
+ protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
+ viewEvents
+ .observe()
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe {
+ hideWaitingView()
+ observer(it)
+ }
+ .disposeOnDestroy()
+ }
+
/* ==========================================================================================
* DATA
* ========================================================================================== */
@@ -179,7 +192,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
}
})
- sessionListener = getVectorComponent().sessionListener()
+ sessionListener = vectorComponent.sessionListener()
sessionListener.globalErrorLiveData.observeEvent(this) {
handleGlobalError(it)
}
@@ -217,8 +230,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
handleInvalidToken(globalError)
is GlobalError.ConsentNotGivenError ->
consentNotGivenHelper.displayDialog(globalError.consentUri,
- activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host
- ?: "")
+ activeSessionHolder.getActiveSession().sessionParams.homeServerHost ?: "")
}
}
diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt
index 6eb316456a..c4dcb0d996 100644
--- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt
@@ -39,6 +39,7 @@ import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRx
import com.bumptech.glide.util.Util.assertMainThread
import com.google.android.material.snackbar.Snackbar
+import com.jakewharton.rxbinding3.view.clicks
import im.vector.riotx.R
import im.vector.riotx.core.di.DaggerScreenComponent
import im.vector.riotx.core.di.HasScreenInjector
@@ -49,6 +50,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import timber.log.Timber
+import java.util.concurrent.TimeUnit
abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
@@ -249,6 +251,18 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
.disposeOnDestroyView()
}
+ /* ==========================================================================================
+ * Views
+ * ========================================================================================== */
+
+ protected fun View.debouncedClicks(onClicked: () -> Unit) {
+ clicks()
+ .throttleFirst(300, TimeUnit.MILLISECONDS)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe { onClicked() }
+ .disposeOnDestroyView()
+ }
+
/* ==========================================================================================
* MENU MANAGEMENT
* ========================================================================================== */
diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt
index e82e8b3856..11cd9c485e 100644
--- a/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt
@@ -30,7 +30,7 @@ import io.reactivex.Single
abstract class VectorViewModel(initialState: S)
: BaseMvRxViewModel(initialState, false) {
- interface Factory {
+ interface Factory {
fun create(state: S): BaseMvRxViewModel
}
diff --git a/vector/src/main/java/im/vector/riotx/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/riotx/core/pushers/PushersManager.kt
index aa1fbaca54..69367e529c 100644
--- a/vector/src/main/java/im/vector/riotx/core/pushers/PushersManager.kt
+++ b/vector/src/main/java/im/vector/riotx/core/pushers/PushersManager.kt
@@ -45,7 +45,7 @@ class PushersManager @Inject constructor(
profileTag,
localeProvider.current().language,
appNameProvider.getAppName(),
- currentSession.sessionParams.credentials.deviceId ?: "MOBILE",
+ currentSession.sessionParams.deviceId ?: "MOBILE",
stringProvider.getString(R.string.pusher_http_url),
append = false,
withEventIdOnly = true
diff --git a/vector/src/main/java/im/vector/riotx/core/resources/UserPreferencesProvider.kt b/vector/src/main/java/im/vector/riotx/core/resources/UserPreferencesProvider.kt
index ac379a8f98..fa4b09ed4c 100644
--- a/vector/src/main/java/im/vector/riotx/core/resources/UserPreferencesProvider.kt
+++ b/vector/src/main/java/im/vector/riotx/core/resources/UserPreferencesProvider.kt
@@ -29,6 +29,10 @@ class UserPreferencesProvider @Inject constructor(private val vectorPreferences:
return vectorPreferences.showReadReceipts()
}
+ fun shouldShowRedactedMessages(): Boolean {
+ return vectorPreferences.showRedactedMessages()
+ }
+
fun shouldShowLongClickOnRoomHelp(): Boolean {
return vectorPreferences.shouldShowLongClickOnRoomHelp()
}
diff --git a/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt
index d82134caf5..9e5af038ef 100644
--- a/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt
+++ b/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt
@@ -33,9 +33,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import im.vector.riotx.R
import im.vector.riotx.features.notifications.NotificationUtils
-import im.vector.riotx.features.settings.VectorLocale
-import timber.log.Timber
-import java.util.Locale
/**
* Tells if the application ignores battery optimizations.
@@ -53,6 +50,10 @@ fun isIgnoringBatteryOptimizations(context: Context): Boolean {
|| (context.getSystemService(Context.POWER_SERVICE) as PowerManager?)?.isIgnoringBatteryOptimizations(context.packageName) == true
}
+fun isAirplaneModeOn(context: Context): Boolean {
+ return Settings.Global.getInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) != 0
+}
+
/**
* display the system dialog for granting this permission. If previously granted, the
* system will not show it (so you should call this method).
@@ -90,24 +91,6 @@ fun copyToClipboard(context: Context, text: CharSequence, showToast: Boolean = t
}
}
-/**
- * Provides the device locale
- *
- * @return the device locale
- */
-fun getDeviceLocale(context: Context): Locale {
- return try {
- val packageManager = context.packageManager
- val resources = packageManager.getResourcesForApplication("android")
- @Suppress("DEPRECATION")
- resources.configuration.locale
- } catch (e: Exception) {
- Timber.e(e, "## getDeviceLocale() failed")
- // Fallback to application locale
- VectorLocale.applicationLocale
- }
-}
-
/**
* Shows notification settings for the current app.
* In android O will directly opens the notification settings, in lower version it will show the App settings
diff --git a/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt b/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt
index 3de555f66e..0361fc9d71 100644
--- a/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt
+++ b/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt
@@ -26,3 +26,14 @@ fun String.isValidUrl(): Boolean {
false
}
}
+
+/**
+ * Ensure string starts with "http". If it is not the case, "https://" is added, only if the String is not empty
+ */
+internal fun String.ensureProtocol(): String {
+ return when {
+ isEmpty() -> this
+ !startsWith("http") -> "https://$this"
+ else -> this
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/configuration/VectorConfiguration.kt b/vector/src/main/java/im/vector/riotx/features/configuration/VectorConfiguration.kt
index a4b7ca263d..2ef69890ed 100644
--- a/vector/src/main/java/im/vector/riotx/features/configuration/VectorConfiguration.kt
+++ b/vector/src/main/java/im/vector/riotx/features/configuration/VectorConfiguration.kt
@@ -30,62 +30,30 @@ import javax.inject.Inject
/**
* Handle locale configuration change, such as theme, font size and locale chosen by the user
*/
-
class VectorConfiguration @Inject constructor(private val context: Context) {
- // TODO Import mLanguageReceiver From Riot?
fun onConfigurationChanged() {
if (Locale.getDefault().toString() != VectorLocale.applicationLocale.toString()) {
Timber.v("## onConfigurationChanged(): the locale has been updated to ${Locale.getDefault()}")
Timber.v("## onConfigurationChanged(): restore the expected value ${VectorLocale.applicationLocale}")
- updateApplicationSettings(VectorLocale.applicationLocale,
- FontScale.getFontScalePrefValue(context),
- ThemeUtils.getApplicationTheme(context))
+ Locale.setDefault(VectorLocale.applicationLocale)
}
}
- private fun updateApplicationSettings(locale: Locale, textSize: String, theme: String) {
- VectorLocale.saveApplicationLocale(context, locale)
- FontScale.saveFontScale(context, textSize)
- Locale.setDefault(locale)
-
- val config = Configuration(context.resources.configuration)
- @Suppress("DEPRECATION")
- config.locale = locale
- config.fontScale = FontScale.getFontScale(context)
- @Suppress("DEPRECATION")
- context.resources.updateConfiguration(config, context.resources.displayMetrics)
-
- ThemeUtils.setApplicationTheme(context, theme)
- // TODO PhoneNumberUtils.onLocaleUpdate()
- }
-
- /**
- * Update the application theme
- *
- * @param theme the new theme
- */
- fun updateApplicationTheme(theme: String) {
- ThemeUtils.setApplicationTheme(context, theme)
- updateApplicationSettings(VectorLocale.applicationLocale,
- FontScale.getFontScalePrefValue(context),
- theme)
- }
-
/**
* Init the configuration from the saved one
*/
fun initConfiguration() {
VectorLocale.init(context)
val locale = VectorLocale.applicationLocale
- val fontScale = FontScale.getFontScale(context)
+ val fontScale = FontScale.getFontScaleValue(context)
val theme = ThemeUtils.getApplicationTheme(context)
Locale.setDefault(locale)
val config = Configuration(context.resources.configuration)
@Suppress("DEPRECATION")
config.locale = locale
- config.fontScale = fontScale
+ config.fontScale = fontScale.scale
@Suppress("DEPRECATION")
context.resources.updateConfiguration(config, context.resources.displayMetrics)
@@ -93,16 +61,6 @@ class VectorConfiguration @Inject constructor(private val context: Context) {
ThemeUtils.setApplicationTheme(context, theme)
}
- /**
- * Update the application locale
- *
- * @param locale
- */
- // TODO Call from LanguagePickerActivity
- fun updateApplicationLocale(locale: Locale) {
- updateApplicationSettings(locale, FontScale.getFontScalePrefValue(context), ThemeUtils.getApplicationTheme(context))
- }
-
/**
* Compute a localised context
*
@@ -115,7 +73,7 @@ class VectorConfiguration @Inject constructor(private val context: Context) {
val resources = context.resources
val locale = VectorLocale.applicationLocale
val configuration = resources.configuration
- configuration.fontScale = FontScale.getFontScale(context)
+ configuration.fontScale = FontScale.getFontScaleValue(context).scale
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.setLocale(locale)
@@ -142,10 +100,9 @@ class VectorConfiguration @Inject constructor(private val context: Context) {
* Compute the locale status value
* @return the local status value
*/
- // TODO Create data class for this
fun getHash(): String {
return (VectorLocale.applicationLocale.toString()
- + "_" + FontScale.getFontScalePrefValue(context)
+ + "_" + FontScale.getFontScaleValue(context).preferenceValue
+ "_" + ThemeUtils.getApplicationTheme(context))
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt
index c8406570d3..faada7ba3e 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt
@@ -51,7 +51,7 @@ class KeysBackupRestoreFromKeyViewModel @Inject constructor(
try {
sharedViewModel.recoverUsingBackupPass(recoveryKey)
} catch (failure: Throwable) {
- recoveryCodeErrorText.value = stringProvider.getString(R.string.keys_backup_recovery_code_error_decrypt)
+ recoveryCodeErrorText.postValue(stringProvider.getString(R.string.keys_backup_recovery_code_error_decrypt))
}
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt
index 1fec404f7d..20046fa115 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt
@@ -197,7 +197,7 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor(private val s
endIconResourceId(R.drawable.e2e_warning)
} else {
if (isSignatureValid) {
- if (session.sessionParams.credentials.deviceId == it.deviceId) {
+ if (session.sessionParams.deviceId == it.deviceId) {
description(stringProvider.getString(R.string.keys_backup_settings_valid_signature_from_this_device))
endIconResourceId(R.drawable.e2e_verified)
} else {
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt
index 3522c5a752..93d6f43763 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt
@@ -164,16 +164,16 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment()
@OnClick(R.id.keys_backup_setup_step2_button)
fun doNext() {
when {
- viewModel.passphrase.value.isNullOrEmpty() -> {
+ viewModel.passphrase.value.isNullOrEmpty() -> {
viewModel.passphraseError.value = context?.getString(R.string.passphrase_empty_error_message)
}
viewModel.passphrase.value != viewModel.confirmPassphrase.value -> {
viewModel.confirmPassphraseError.value = context?.getString(R.string.passphrase_passphrase_does_not_match)
}
- viewModel.passwordStrength.value?.score ?: 0 < 4 -> {
+ viewModel.passwordStrength.value?.score ?: 0 < 4 -> {
viewModel.passphraseError.value = context?.getString(R.string.passphrase_passphrase_too_weak)
}
- else -> {
+ else -> {
viewModel.megolmBackupCreationInfo = null
viewModel.prepareRecoveryKey(activity!!, viewModel.passphrase.value)
@@ -190,7 +190,7 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment()
viewModel.prepareRecoveryKey(activity!!, null)
}
- else -> {
+ else -> {
// User has entered a passphrase but want to skip this step.
viewModel.passphraseError.value = context?.getString(R.string.keys_backup_passphrase_not_empty_error_message)
}
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt
index 9175d6c081..1478b99d3b 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt
@@ -123,7 +123,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
.joinToString(" ")
}
- it.setOnClickListener {
+ it.debouncedClicks {
copyToClipboard(activity!!, recoveryKey)
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStorageKeyFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStorageKeyFragment.kt
index db4c5230fd..848166381e 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStorageKeyFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStorageKeyFragment.kt
@@ -23,7 +23,6 @@ import android.view.View
import android.view.inputmethod.EditorInfo
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
-import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.matrix.android.api.extensions.tryThis
@@ -33,7 +32,6 @@ import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.startImportTextFromFileIntent
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_ssss_access_from_key.*
-import kotlinx.android.synthetic.main.fragment_ssss_access_from_passphrase.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@@ -67,13 +65,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor(
}
.disposeOnDestroyView()
- ssss_key_use_file.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- startImportTextFromFileIntent(this, IMPORT_FILE_REQ)
- }
- .disposeOnDestroyView()
+ ssss_key_use_file.debouncedClicks { startImportTextFromFileIntent(this, IMPORT_FILE_REQ) }
sharedViewModel.observeViewEvents {
when (it) {
@@ -83,13 +75,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor(
}
}
- ssss_key_submit.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- submit()
- }
- .disposeOnDestroyView()
+ ssss_key_submit.debouncedClicks { submit() }
}
fun submit() {
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
index 82d27aea1b..f5eb450fe1 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
@@ -22,7 +22,6 @@ import android.view.inputmethod.EditorInfo
import androidx.core.text.toSpannable
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
-import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.riotx.R
@@ -37,7 +36,7 @@ import javax.inject.Inject
class SharedSecuredStoragePassphraseFragment @Inject constructor(
private val colorProvider: ColorProvider
-): VectorBaseFragment() {
+) : VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_ssss_access_from_passphrase
@@ -83,29 +82,9 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
}
}
- ssss_passphrase_submit.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- submit()
- }
- .disposeOnDestroyView()
-
- ssss_passphrase_use_key.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- sharedViewModel.handle(SharedSecureStorageAction.UseKey)
- }
- .disposeOnDestroyView()
-
- ssss_view_show_password.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- sharedViewModel.handle(SharedSecureStorageAction.TogglePasswordVisibility)
- }
- .disposeOnDestroyView()
+ ssss_passphrase_submit.debouncedClicks { submit() }
+ ssss_passphrase_use_key.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.UseKey) }
+ ssss_view_show_password.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.TogglePasswordVisibility) }
}
fun submit() {
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapAccountPasswordFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapAccountPasswordFragment.kt
index abe6e54092..fcedd2926e 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapAccountPasswordFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapAccountPasswordFragment.kt
@@ -22,7 +22,6 @@ import android.view.inputmethod.EditorInfo
import androidx.core.text.toSpannable
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
-import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.riotx.R
@@ -75,21 +74,8 @@ class BootstrapAccountPasswordFragment @Inject constructor(
}
.disposeOnDestroyView()
- ssss_view_show_password.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility)
- }
- .disposeOnDestroyView()
-
- bootstrapPasswordButton.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- submit()
- }
- .disposeOnDestroyView()
+ ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
+ bootstrapPasswordButton.debouncedClicks { submit() }
withState(sharedViewModel) { state ->
(state.step as? BootstrapStep.AccountPassword)?.failure?.let {
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConclusionFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConclusionFragment.kt
index d84283b14c..fd7ba2dbb7 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConclusionFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConclusionFragment.kt
@@ -21,14 +21,11 @@ import android.view.View
import androidx.core.text.toSpannable
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
-import com.jakewharton.rxbinding3.view.clicks
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.colorizeMatchingText
-import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_bootstrap_conclusion.*
-import java.util.concurrent.TimeUnit
import javax.inject.Inject
class BootstrapConclusionFragment @Inject constructor(
@@ -42,13 +39,7 @@ class BootstrapConclusionFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- bootstrapConclusionContinue.clickableView.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- sharedViewModel.handle(BootstrapActions.Completed)
- }
- .disposeOnDestroyView()
+ bootstrapConclusionContinue.clickableView.debouncedClicks { sharedViewModel.handle(BootstrapActions.Completed) }
}
override fun invalidate() = withState(sharedViewModel) { state ->
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt
index d09eafee58..df4d741bf1 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt
@@ -23,7 +23,6 @@ import androidx.core.text.toSpannable
import androidx.core.view.isGone
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
-import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.riotx.R
@@ -88,21 +87,8 @@ class BootstrapConfirmPassphraseFragment @Inject constructor(
// }
}
- ssss_view_show_password.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility)
- }
- .disposeOnDestroyView()
-
- bootstrapSubmit.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- submit()
- }
- .disposeOnDestroyView()
+ ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
+ bootstrapSubmit.debouncedClicks { submit() }
}
private fun submit() = withState(sharedViewModel) { state ->
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt
index 952b0e5d03..d1eee9ff3f 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt
@@ -22,7 +22,6 @@ import android.view.inputmethod.EditorInfo
import androidx.core.text.toSpannable
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
-import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.riotx.R
@@ -83,21 +82,8 @@ class BootstrapEnterPassphraseFragment @Inject constructor(
// }
}
- ssss_view_show_password.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility)
- }
- .disposeOnDestroyView()
-
- bootstrapSubmit.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- submit()
- }
- .disposeOnDestroyView()
+ ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
+ bootstrapSubmit.debouncedClicks { submit() }
}
private fun submit() = withState(sharedViewModel) { state ->
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt
index f1847e5ab5..caf43721a0 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt
@@ -28,7 +28,6 @@ import androidx.core.text.toSpannable
import androidx.core.view.isVisible
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
-import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.matrix.android.api.extensions.tryThis
@@ -80,37 +79,10 @@ class BootstrapMigrateBackupFragment @Inject constructor(
.disposeOnDestroyView()
// sharedViewModel.observeViewEvents {}
- bootstrapMigrateContinueButton.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- submit()
- }
- .disposeOnDestroyView()
-
- bootstrapMigrateShowPassword.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility)
- }
- .disposeOnDestroyView()
-
- bootstrapMigrateForgotPassphrase.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase)
- }
- .disposeOnDestroyView()
-
- bootstrapMigrateUseFile.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- startImportTextFromFileIntent(this, IMPORT_FILE_REQ)
- }
- .disposeOnDestroyView()
+ bootstrapMigrateContinueButton.debouncedClicks { submit() }
+ bootstrapMigrateShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
+ bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) }
+ bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(this, IMPORT_FILE_REQ) }
}
private fun submit() = withState(sharedViewModel) { state ->
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt
index 05c6f7a53f..4faa4168b0 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt
@@ -25,19 +25,16 @@ import androidx.core.text.toSpannable
import androidx.core.view.isVisible
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
-import com.jakewharton.rxbinding3.view.clicks
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.colorizeMatchingText
import im.vector.riotx.core.utils.startSharePlainTextIntent
import im.vector.riotx.core.utils.toast
-import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_bootstrap_save_key.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
-import java.util.concurrent.TimeUnit
import javax.inject.Inject
class BootstrapSaveRecoveryKeyFragment @Inject constructor(
@@ -51,34 +48,17 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- bootstrapSaveText.text = getString(R.string.bootstrap_save_key_description, getString(R.string.message_key), getString(R.string.recovery_passphrase))
+ val messageKey = getString(R.string.message_key)
+ val recoveryPassphrase = getString(R.string.recovery_passphrase)
+ val color = colorProvider.getColorFromAttribute(R.attr.vctr_toolbar_link_text_color)
+ bootstrapSaveText.text = getString(R.string.bootstrap_save_key_description, messageKey, recoveryPassphrase)
.toSpannable()
- .colorizeMatchingText(getString(R.string.recovery_passphrase), colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
- .colorizeMatchingText(getString(R.string.message_key), colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
+ .colorizeMatchingText(messageKey, color)
+ .colorizeMatchingText(recoveryPassphrase, color)
- recoverySave.clickableView.clicks()
- .debounce(600, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- downloadRecoveryKey()
- }
- .disposeOnDestroyView()
-
- recoveryCopy.clickableView.clicks()
- .debounce(600, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- shareRecoveryKey()
- }
- .disposeOnDestroyView()
-
- recoveryContinue.clickableView.clicks()
- .debounce(300, TimeUnit.MILLISECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- sharedViewModel.handle(BootstrapActions.GoToCompleted)
- }
- .disposeOnDestroyView()
+ recoverySave.clickableView.debouncedClicks { downloadRecoveryKey() }
+ recoveryCopy.clickableView.debouncedClicks { shareRecoveryKey() }
+ recoveryContinue.clickableView.debouncedClicks { sharedViewModel.handle(BootstrapActions.GoToCompleted) }
}
private fun downloadRecoveryKey() = withState(sharedViewModel) { _ ->
diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt
index 7a003c3722..dce33255ce 100644
--- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt
@@ -367,7 +367,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
)
if (trustResult.isVerified()) {
// Sign this device and upload the signature
- session.sessionParams.credentials.deviceId?.let { deviceId ->
+ session.sessionParams.deviceId?.let { deviceId ->
session.cryptoService()
.crossSigningService().trustDevice(deviceId, object : MatrixCallback {
override fun onFailure(failure: Throwable) {
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsAction.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsAction.kt
new file mode 100644
index 0000000000..57b23d26d2
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsAction.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.discovery
+
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.riotx.core.platform.VectorViewModelAction
+
+sealed class DiscoverySettingsAction : VectorViewModelAction {
+ object RetrieveBinding : DiscoverySettingsAction()
+ object Refresh : DiscoverySettingsAction()
+
+ object DisconnectIdentityServer : DiscoverySettingsAction()
+ data class ChangeIdentityServer(val url: String) : DiscoverySettingsAction()
+ data class RevokeThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
+ data class ShareThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
+ data class FinalizeBind3pid(val threePid: ThreePid) : DiscoverySettingsAction()
+ data class SubmitMsisdnToken(val threePid: ThreePid.Msisdn, val code: String) : DiscoverySettingsAction()
+ data class CancelBinding(val threePid: ThreePid) : DiscoverySettingsAction()
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsController.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsController.kt
new file mode 100644
index 0000000000..f92cb1a8bb
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsController.kt
@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery
+
+import android.view.View
+import com.airbnb.epoxy.TypedEpoxyController
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Incomplete
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
+import com.google.i18n.phonenumbers.PhoneNumberUtil
+import im.vector.matrix.android.api.failure.Failure
+import im.vector.matrix.android.api.session.identity.SharedState
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.riotx.R
+import im.vector.riotx.core.epoxy.attributes.ButtonStyle
+import im.vector.riotx.core.epoxy.attributes.ButtonType
+import im.vector.riotx.core.epoxy.attributes.IconMode
+import im.vector.riotx.core.epoxy.loadingItem
+import im.vector.riotx.core.error.ErrorFormatter
+import im.vector.riotx.core.resources.ColorProvider
+import im.vector.riotx.core.resources.StringProvider
+import timber.log.Timber
+import javax.inject.Inject
+import javax.net.ssl.HttpsURLConnection
+
+class DiscoverySettingsController @Inject constructor(
+ private val colorProvider: ColorProvider,
+ private val stringProvider: StringProvider,
+ private val errorFormatter: ErrorFormatter
+) : TypedEpoxyController() {
+
+ var listener: Listener? = null
+
+ private val codes = mutableMapOf()
+
+ override fun buildModels(data: DiscoverySettingsState) {
+ when (data.identityServer) {
+ is Loading -> {
+ loadingItem {
+ id("identityServerLoading")
+ }
+ }
+ is Fail -> {
+ settingsInfoItem {
+ id("identityServerError")
+ helperText(data.identityServer.error.message)
+ }
+ }
+ is Success -> {
+ buildIdentityServerSection(data)
+ val hasIdentityServer = data.identityServer().isNullOrBlank().not()
+ if (hasIdentityServer && !data.termsNotSigned) {
+ buildEmailsSection(data.emailList)
+ buildMsisdnSection(data.phoneNumbersList)
+ }
+ }
+ }
+ }
+
+ private fun buildIdentityServerSection(data: DiscoverySettingsState) {
+ val identityServer = data.identityServer() ?: stringProvider.getString(R.string.none)
+
+ settingsSectionTitleItem {
+ id("idServerTitle")
+ titleResId(R.string.identity_server)
+ }
+
+ settingsItem {
+ id("idServer")
+ title(identityServer)
+ }
+
+ if (data.identityServer() != null && data.termsNotSigned) {
+ settingsInfoItem {
+ id("idServerFooter")
+ helperText(stringProvider.getString(R.string.settings_agree_to_terms, identityServer))
+ showCompoundDrawable(true)
+ itemClickListener(View.OnClickListener { listener?.openIdentityServerTerms() })
+ }
+ settingsButtonItem {
+ id("seeTerms")
+ colorProvider(colorProvider)
+ buttonTitle(stringProvider.getString(R.string.open_terms_of, identityServer))
+ buttonClickListener { listener?.openIdentityServerTerms() }
+ }
+ } else {
+ settingsInfoItem {
+ id("idServerFooter")
+ showCompoundDrawable(false)
+ if (data.identityServer() != null) {
+ helperText(stringProvider.getString(R.string.settings_discovery_identity_server_info, identityServer))
+ } else {
+ helperTextResId(R.string.settings_discovery_identity_server_info_none)
+ }
+ }
+ }
+
+ settingsButtonItem {
+ id("change")
+ colorProvider(colorProvider)
+ if (data.identityServer() == null) {
+ buttonTitleId(R.string.add_identity_server)
+ } else {
+ buttonTitleId(R.string.change_identity_server)
+ }
+ buttonClickListener { listener?.onTapChangeIdentityServer() }
+ }
+
+ if (data.identityServer() != null) {
+ settingsInfoItem {
+ id("removeInfo")
+ helperTextResId(R.string.settings_discovery_disconnect_identity_server_info)
+ }
+ settingsButtonItem {
+ id("remove")
+ colorProvider(colorProvider)
+ buttonTitleId(R.string.disconnect_identity_server)
+ buttonStyle(ButtonStyle.DESTRUCTIVE)
+ buttonClickListener { listener?.onTapDisconnectIdentityServer() }
+ }
+ }
+ }
+
+ private fun buildEmailsSection(emails: Async>) {
+ settingsSectionTitleItem {
+ id("emails")
+ titleResId(R.string.settings_discovery_emails_title)
+ }
+ when (emails) {
+ is Incomplete -> {
+ loadingItem {
+ id("emailsLoading")
+ }
+ }
+ is Fail -> {
+ settingsInfoItem {
+ id("emailsError")
+ helperText(emails.error.message)
+ }
+ }
+ is Success -> {
+ if (emails().isEmpty()) {
+ settingsInfoItem {
+ id("emailsEmpty")
+ helperText(stringProvider.getString(R.string.settings_discovery_no_mails))
+ }
+ } else {
+ emails().forEach { buildEmail(it) }
+ }
+ }
+ }
+ }
+
+ private fun buildEmail(pidInfo: PidInfo) {
+ buildThreePid(pidInfo)
+
+ if (pidInfo.isShared is Fail) {
+ buildSharedFail(pidInfo)
+ } else if (pidInfo.isShared() == SharedState.BINDING_IN_PROGRESS) {
+ when (pidInfo.finalRequest) {
+ is Uninitialized,
+ is Loading ->
+ settingsInformationItem {
+ id("info${pidInfo.threePid.value}")
+ colorProvider(colorProvider)
+ message(stringProvider.getString(R.string.settings_discovery_confirm_mail, pidInfo.threePid.value))
+ }
+ is Fail ->
+ settingsInformationItem {
+ id("info${pidInfo.threePid.value}")
+ colorProvider(colorProvider)
+ message(stringProvider.getString(R.string.settings_discovery_confirm_mail_not_clicked, pidInfo.threePid.value))
+ textColorId(R.color.riotx_destructive_accent)
+ }
+ is Success -> Unit /* Cannot happen */
+ }
+ when (pidInfo.finalRequest) {
+ is Uninitialized,
+ is Fail ->
+ buildContinueCancel(pidInfo.threePid)
+ is Loading ->
+ settingsProgressItem {
+ id("progress${pidInfo.threePid.value}")
+ }
+ is Success -> Unit /* Cannot happen */
+ }
+ }
+ }
+
+ private fun buildMsisdnSection(msisdns: Async>) {
+ settingsSectionTitleItem {
+ id("msisdn")
+ titleResId(R.string.settings_discovery_msisdn_title)
+ }
+
+ when (msisdns) {
+ is Incomplete -> {
+ loadingItem {
+ id("msisdnLoading")
+ }
+ }
+ is Fail -> {
+ settingsInfoItem {
+ id("msisdnListError")
+ helperText(msisdns.error.message)
+ }
+ }
+ is Success -> {
+ if (msisdns().isEmpty()) {
+ settingsInfoItem {
+ id("no_msisdn")
+ helperText(stringProvider.getString(R.string.settings_discovery_no_msisdn))
+ }
+ } else {
+ msisdns().forEach { buildMsisdn(it) }
+ }
+ }
+ }
+ }
+
+ private fun buildMsisdn(pidInfo: PidInfo) {
+ val phoneNumber = try {
+ PhoneNumberUtil.getInstance().parse("+${pidInfo.threePid.value}", null)
+ } catch (t: Throwable) {
+ Timber.e(t, "Unable to parse the phone number")
+ null
+ }
+ ?.let {
+ PhoneNumberUtil.getInstance().format(it, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL)
+ }
+ ?: pidInfo.threePid.value
+
+ buildThreePid(pidInfo, phoneNumber)
+
+ if (pidInfo.isShared is Fail) {
+ buildSharedFail(pidInfo)
+ } else if (pidInfo.isShared() == SharedState.BINDING_IN_PROGRESS) {
+ val errorText = if (pidInfo.finalRequest is Fail) {
+ val error = pidInfo.finalRequest.error
+ // Deal with error 500
+ // Ref: https://github.com/matrix-org/sydent/issues/292
+ if (error is Failure.ServerError
+ && error.httpCode == HttpsURLConnection.HTTP_INTERNAL_ERROR /* 500 */) {
+ stringProvider.getString(R.string.settings_text_message_sent_wrong_code)
+ } else {
+ errorFormatter.toHumanReadable(error)
+ }
+ } else {
+ null
+ }
+ settingsEditTextItem {
+ id("msisdnVerification${pidInfo.threePid.value}")
+ descriptionText(stringProvider.getString(R.string.settings_text_message_sent, phoneNumber))
+ errorText(errorText)
+ inProgress(pidInfo.finalRequest is Loading)
+ interactionListener(object : SettingsEditTextItem.Listener {
+ override fun onValidate() {
+ val code = codes[pidInfo.threePid]
+ if (pidInfo.threePid is ThreePid.Msisdn && code != null) {
+ listener?.sendMsisdnVerificationCode(pidInfo.threePid, code)
+ }
+ }
+
+ override fun onCodeChange(code: String) {
+ codes[pidInfo.threePid] = code
+ }
+ })
+ }
+ buildContinueCancel(pidInfo.threePid)
+ }
+ }
+
+ private fun buildThreePid(pidInfo: PidInfo, title: String = pidInfo.threePid.value) {
+ settingsTextButtonSingleLineItem {
+ id(pidInfo.threePid.value)
+ title(title)
+ colorProvider(colorProvider)
+ stringProvider(stringProvider)
+ when (pidInfo.isShared) {
+ is Loading -> {
+ buttonIndeterminate(true)
+ }
+ is Fail -> {
+ buttonType(ButtonType.NORMAL)
+ buttonStyle(ButtonStyle.DESTRUCTIVE)
+ buttonTitle(stringProvider.getString(R.string.global_retry))
+ iconMode(IconMode.ERROR)
+ buttonClickListener { listener?.onTapRetryToRetrieveBindings() }
+ }
+ is Success -> when (pidInfo.isShared()) {
+ SharedState.SHARED,
+ SharedState.NOT_SHARED -> {
+ buttonType(ButtonType.SWITCH)
+ checked(pidInfo.isShared() == SharedState.SHARED)
+ switchChangeListener { _, checked ->
+ if (checked) {
+ listener?.onTapShare(pidInfo.threePid)
+ } else {
+ listener?.onTapRevoke(pidInfo.threePid)
+ }
+ }
+ }
+ SharedState.BINDING_IN_PROGRESS -> {
+ buttonType(ButtonType.NO_BUTTON)
+ when (pidInfo.finalRequest) {
+ is Incomplete -> iconMode(IconMode.INFO)
+ is Fail -> iconMode(IconMode.ERROR)
+ else -> iconMode(IconMode.NONE)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun buildSharedFail(pidInfo: PidInfo) {
+ settingsInformationItem {
+ id("info${pidInfo.threePid.value}")
+ colorProvider(colorProvider)
+ textColorId(R.color.vector_error_color)
+ message((pidInfo.isShared as? Fail)?.error?.message ?: "")
+ }
+ }
+
+ private fun buildContinueCancel(threePid: ThreePid) {
+ settingsContinueCancelItem {
+ id("bottom${threePid.value}")
+ interactionListener(object : SettingsContinueCancelItem.Listener {
+ override fun onContinue() {
+ when (threePid) {
+ is ThreePid.Email -> {
+ listener?.checkEmailVerification(threePid)
+ }
+ is ThreePid.Msisdn -> {
+ val code = codes[threePid]
+ if (code != null) {
+ listener?.sendMsisdnVerificationCode(threePid, code)
+ }
+ }
+ }
+ }
+
+ override fun onCancel() {
+ listener?.cancelBinding(threePid)
+ }
+ })
+ }
+ }
+
+ interface Listener {
+ fun openIdentityServerTerms()
+ fun onTapRevoke(threePid: ThreePid)
+ fun onTapShare(threePid: ThreePid)
+ fun checkEmailVerification(threePid: ThreePid.Email)
+ fun cancelBinding(threePid: ThreePid)
+ fun sendMsisdnVerificationCode(threePid: ThreePid.Msisdn, code: String)
+ fun onTapChangeIdentityServer()
+ fun onTapDisconnectIdentityServer()
+ fun onTapRetryToRetrieveBindings()
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsFragment.kt
new file mode 100644
index 0000000000..b772db7322
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsFragment.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.AlertDialog
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
+import im.vector.matrix.android.api.session.identity.SharedState
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.matrix.android.api.session.terms.TermsService
+import im.vector.riotx.R
+import im.vector.riotx.core.extensions.cleanup
+import im.vector.riotx.core.extensions.configureWith
+import im.vector.riotx.core.extensions.exhaustive
+import im.vector.riotx.core.extensions.observeEvent
+import im.vector.riotx.core.platform.VectorBaseActivity
+import im.vector.riotx.core.platform.VectorBaseFragment
+import im.vector.riotx.core.utils.ensureProtocol
+import im.vector.riotx.features.discovery.change.SetIdentityServerFragment
+import im.vector.riotx.features.terms.ReviewTermsActivity
+import kotlinx.android.synthetic.main.fragment_generic_recycler.*
+import javax.inject.Inject
+
+class DiscoverySettingsFragment @Inject constructor(
+ private val controller: DiscoverySettingsController,
+ val viewModelFactory: DiscoverySettingsViewModel.Factory
+) : VectorBaseFragment(), DiscoverySettingsController.Listener {
+
+ override fun getLayoutResId() = R.layout.fragment_generic_recycler
+
+ private val viewModel by fragmentViewModel(DiscoverySettingsViewModel::class)
+
+ lateinit var sharedViewModel: DiscoverySharedViewModel
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ sharedViewModel = activityViewModelProvider.get(DiscoverySharedViewModel::class.java)
+
+ controller.listener = this
+ recyclerView.configureWith(controller)
+
+ sharedViewModel.navigateEvent.observeEvent(this) {
+ when (it) {
+ is DiscoverySharedViewModelAction.ChangeIdentityServer ->
+ viewModel.handle(DiscoverySettingsAction.ChangeIdentityServer(it.newUrl))
+ }.exhaustive
+ }
+
+ viewModel.observeViewEvents {
+ when (it) {
+ is DiscoverySettingsViewEvents.Failure -> {
+ displayErrorDialog(it.throwable)
+ }
+ }.exhaustive
+ }
+ }
+
+ override fun onDestroyView() {
+ recyclerView.cleanup()
+ controller.listener = null
+ super.onDestroyView()
+ }
+
+ override fun invalidate() = withState(viewModel) { state ->
+ controller.setData(state)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_discovery_category)
+
+ // If some 3pids are pending, we can try to check if they have been verified here
+ viewModel.handle(DiscoverySettingsAction.Refresh)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) {
+ if (Activity.RESULT_OK == resultCode) {
+ viewModel.handle(DiscoverySettingsAction.RetrieveBinding)
+ } else {
+ // add some error?
+ }
+ }
+
+ super.onActivityResult(requestCode, resultCode, data)
+ }
+
+ override fun openIdentityServerTerms() = withState(viewModel) { state ->
+ if (state.termsNotSigned) {
+ navigator.openTerms(
+ this,
+ TermsService.ServiceType.IdentityService,
+ state.identityServer()?.ensureProtocol() ?: "",
+ null)
+ }
+ }
+
+ override fun onTapRevoke(threePid: ThreePid) {
+ viewModel.handle(DiscoverySettingsAction.RevokeThreePid(threePid))
+ }
+
+ override fun onTapShare(threePid: ThreePid) {
+ viewModel.handle(DiscoverySettingsAction.ShareThreePid(threePid))
+ }
+
+ override fun checkEmailVerification(threePid: ThreePid.Email) {
+ viewModel.handle(DiscoverySettingsAction.FinalizeBind3pid(threePid))
+ }
+
+ override fun sendMsisdnVerificationCode(threePid: ThreePid.Msisdn, code: String) {
+ viewModel.handle(DiscoverySettingsAction.SubmitMsisdnToken(threePid, code))
+ }
+
+ override fun cancelBinding(threePid: ThreePid) {
+ viewModel.handle(DiscoverySettingsAction.CancelBinding(threePid))
+ }
+
+ override fun onTapChangeIdentityServer() = withState(viewModel) { state ->
+ // we should prompt if there are bound items with current is
+ val pidList = state.emailList().orEmpty() + state.phoneNumbersList().orEmpty()
+ val hasBoundIds = pidList.any { it.isShared() == SharedState.SHARED }
+
+ if (hasBoundIds) {
+ // we should prompt
+ AlertDialog.Builder(requireActivity())
+ .setTitle(R.string.change_identity_server)
+ .setMessage(getString(R.string.settings_discovery_disconnect_with_bound_pid, state.identityServer(), state.identityServer()))
+ .setPositiveButton(R.string._continue) { _, _ -> navigateToChangeIdentityServerFragment() }
+ .setNegativeButton(R.string.cancel, null)
+ .show()
+ Unit
+ } else {
+ navigateToChangeIdentityServerFragment()
+ }
+ }
+
+ override fun onTapDisconnectIdentityServer() {
+ // we should prompt if there are bound items with current is
+ withState(viewModel) { state ->
+ val pidList = state.emailList().orEmpty() + state.phoneNumbersList().orEmpty()
+ val hasBoundIds = pidList.any { it.isShared() == SharedState.SHARED }
+
+ val message = if (hasBoundIds) {
+ getString(R.string.settings_discovery_disconnect_with_bound_pid, state.identityServer(), state.identityServer())
+ } else {
+ getString(R.string.disconnect_identity_server_dialog_content, state.identityServer())
+ }
+
+ AlertDialog.Builder(requireActivity())
+ .setTitle(R.string.disconnect_identity_server)
+ .setMessage(message)
+ .setPositiveButton(R.string.disconnect) { _, _ -> viewModel.handle(DiscoverySettingsAction.DisconnectIdentityServer) }
+ .setNegativeButton(R.string.cancel, null)
+ .show()
+ }
+ }
+
+ override fun onTapRetryToRetrieveBindings() {
+ viewModel.handle(DiscoverySettingsAction.RetrieveBinding)
+ }
+
+ private fun navigateToChangeIdentityServerFragment() {
+ parentFragmentManager.beginTransaction()
+ .setCustomAnimations(R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom, R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom)
+ .replace(R.id.vector_settings_page, SetIdentityServerFragment::class.java, null)
+ .addToBackStack(null)
+ .commit()
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsState.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsState.kt
new file mode 100644
index 0000000000..5dc4b2354a
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsState.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.discovery
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MvRxState
+import com.airbnb.mvrx.Uninitialized
+
+data class DiscoverySettingsState(
+ val identityServer: Async = Uninitialized,
+ val emailList: Async> = Uninitialized,
+ val phoneNumbersList: Async> = Uninitialized,
+ // Can be true if terms are updated
+ val termsNotSigned: Boolean = false
+) : MvRxState
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewEvents.kt
new file mode 100644
index 0000000000..6fd45394a2
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewEvents.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.discovery
+
+import im.vector.riotx.core.platform.VectorViewEvents
+
+sealed class DiscoverySettingsViewEvents : VectorViewEvents {
+ data class Failure(val throwable: Throwable) : DiscoverySettingsViewEvents()
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt
new file mode 100644
index 0000000000..7c5086afa7
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt
@@ -0,0 +1,373 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery
+
+import androidx.lifecycle.viewModelScope
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.FragmentViewModelContext
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
+import com.airbnb.mvrx.ViewModelContext
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
+import im.vector.matrix.android.api.session.identity.IdentityServiceListener
+import im.vector.matrix.android.api.session.identity.SharedState
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.matrix.android.internal.util.awaitCallback
+import im.vector.matrix.rx.rx
+import im.vector.riotx.core.extensions.exhaustive
+import im.vector.riotx.core.platform.VectorViewModel
+import kotlinx.coroutines.launch
+
+class DiscoverySettingsViewModel @AssistedInject constructor(
+ @Assisted initialState: DiscoverySettingsState,
+ private val session: Session)
+ : VectorViewModel(initialState) {
+
+ @AssistedInject.Factory
+ interface Factory {
+ fun create(initialState: DiscoverySettingsState): DiscoverySettingsViewModel
+ }
+
+ companion object : MvRxViewModelFactory {
+
+ @JvmStatic
+ override fun create(viewModelContext: ViewModelContext, state: DiscoverySettingsState): DiscoverySettingsViewModel? {
+ val fragment: DiscoverySettingsFragment = (viewModelContext as FragmentViewModelContext).fragment()
+ return fragment.viewModelFactory.create(state)
+ }
+ }
+
+ private val identityService = session.identityService()
+
+ private val identityServerManagerListener = object : IdentityServiceListener {
+ override fun onIdentityServerChange() = withState { state ->
+ val identityServerUrl = identityService.getCurrentIdentityServerUrl()
+ val currentIS = state.identityServer()
+ setState {
+ copy(identityServer = Success(identityServerUrl))
+ }
+ if (currentIS != identityServerUrl) retrieveBinding()
+ }
+ }
+
+ init {
+ setState {
+ copy(identityServer = Success(identityService.getCurrentIdentityServerUrl()))
+ }
+ startListenToIdentityManager()
+ observeThreePids()
+ }
+
+ private fun observeThreePids() {
+ session.rx()
+ .liveThreePIds(true)
+ .subscribe {
+ retrieveBinding(it)
+ }
+ .disposeOnClear()
+ }
+
+ override fun onCleared() {
+ super.onCleared()
+ stopListenToIdentityManager()
+ }
+
+ override fun handle(action: DiscoverySettingsAction) {
+ when (action) {
+ DiscoverySettingsAction.Refresh -> refreshPendingEmailBindings()
+ DiscoverySettingsAction.RetrieveBinding -> retrieveBinding()
+ DiscoverySettingsAction.DisconnectIdentityServer -> disconnectIdentityServer()
+ is DiscoverySettingsAction.ChangeIdentityServer -> changeIdentityServer(action)
+ is DiscoverySettingsAction.RevokeThreePid -> revokeThreePid(action)
+ is DiscoverySettingsAction.ShareThreePid -> shareThreePid(action)
+ is DiscoverySettingsAction.FinalizeBind3pid -> finalizeBind3pid(action, true)
+ is DiscoverySettingsAction.SubmitMsisdnToken -> submitMsisdnToken(action)
+ is DiscoverySettingsAction.CancelBinding -> cancelBinding(action)
+ }.exhaustive
+ }
+
+ private fun disconnectIdentityServer() {
+ setState { copy(identityServer = Loading()) }
+
+ viewModelScope.launch {
+ try {
+ awaitCallback { session.identityService().disconnect(it) }
+ setState { copy(identityServer = Success(null)) }
+ } catch (failure: Throwable) {
+ setState { copy(identityServer = Fail(failure)) }
+ }
+ }
+ }
+
+ private fun changeIdentityServer(action: DiscoverySettingsAction.ChangeIdentityServer) {
+ setState { copy(identityServer = Loading()) }
+
+ viewModelScope.launch {
+ try {
+ val data = awaitCallback {
+ session.identityService().setNewIdentityServer(action.url, it)
+ }
+ setState { copy(identityServer = Success(data)) }
+ retrieveBinding()
+ } catch (failure: Throwable) {
+ setState { copy(identityServer = Fail(failure)) }
+ }
+ }
+ }
+
+ private fun shareThreePid(action: DiscoverySettingsAction.ShareThreePid) = withState { state ->
+ if (state.identityServer() == null) return@withState
+ changeThreePidState(action.threePid, Loading())
+
+ viewModelScope.launch {
+ try {
+ awaitCallback { identityService.startBindThreePid(action.threePid, it) }
+ changeThreePidState(action.threePid, Success(SharedState.BINDING_IN_PROGRESS))
+ } catch (failure: Throwable) {
+ _viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
+ changeThreePidState(action.threePid, Fail(failure))
+ }
+ }
+ }
+
+ private fun changeThreePidState(threePid: ThreePid, state: Async) {
+ setState {
+ val currentMails = emailList() ?: emptyList()
+ val phones = phoneNumbersList() ?: emptyList()
+ copy(
+ emailList = Success(
+ currentMails.map {
+ if (it.threePid == threePid) {
+ it.copy(isShared = state)
+ } else {
+ it
+ }
+ }
+ ),
+ phoneNumbersList = Success(
+ phones.map {
+ if (it.threePid == threePid) {
+ it.copy(isShared = state)
+ } else {
+ it
+ }
+ }
+ )
+ )
+ }
+ }
+
+ private fun changeThreePidSubmitState(threePid: ThreePid, submitState: Async) {
+ setState {
+ val currentMails = emailList() ?: emptyList()
+ val phones = phoneNumbersList() ?: emptyList()
+ copy(
+ emailList = Success(
+ currentMails.map {
+ if (it.threePid == threePid) {
+ it.copy(finalRequest = submitState)
+ } else {
+ it
+ }
+ }
+ ),
+ phoneNumbersList = Success(
+ phones.map {
+ if (it.threePid == threePid) {
+ it.copy(finalRequest = submitState)
+ } else {
+ it
+ }
+ }
+ )
+ )
+ }
+ }
+
+ private fun revokeThreePid(action: DiscoverySettingsAction.RevokeThreePid) {
+ when (action.threePid) {
+ is ThreePid.Email -> revokeEmail(action.threePid)
+ is ThreePid.Msisdn -> revokeMsisdn(action.threePid)
+ }.exhaustive
+ }
+
+ private fun revokeEmail(threePid: ThreePid.Email) = withState { state ->
+ if (state.identityServer() == null) return@withState
+ if (state.emailList() == null) return@withState
+ changeThreePidState(threePid, Loading())
+
+ viewModelScope.launch {
+ try {
+ awaitCallback { identityService.unbindThreePid(threePid, it) }
+ changeThreePidState(threePid, Success(SharedState.NOT_SHARED))
+ } catch (failure: Throwable) {
+ _viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
+ changeThreePidState(threePid, Fail(failure))
+ }
+ }
+ }
+
+ private fun revokeMsisdn(threePid: ThreePid.Msisdn) = withState { state ->
+ if (state.identityServer() == null) return@withState
+ if (state.phoneNumbersList() == null) return@withState
+ changeThreePidState(threePid, Loading())
+
+ viewModelScope.launch {
+ try {
+ awaitCallback { identityService.unbindThreePid(threePid, it) }
+ changeThreePidState(threePid, Success(SharedState.NOT_SHARED))
+ } catch (failure: Throwable) {
+ _viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
+ changeThreePidState(threePid, Fail(failure))
+ }
+ }
+ }
+
+ private fun cancelBinding(action: DiscoverySettingsAction.CancelBinding) {
+ viewModelScope.launch {
+ try {
+ awaitCallback { identityService.cancelBindThreePid(action.threePid, it) }
+ changeThreePidState(action.threePid, Success(SharedState.NOT_SHARED))
+ changeThreePidSubmitState(action.threePid, Uninitialized)
+ } catch (failure: Throwable) {
+ // This could never fail
+ }
+ }
+ }
+
+ private fun startListenToIdentityManager() {
+ identityService.addListener(identityServerManagerListener)
+ }
+
+ private fun stopListenToIdentityManager() {
+ identityService.addListener(identityServerManagerListener)
+ }
+
+ private fun retrieveBinding() {
+ retrieveBinding(session.getThreePids())
+ }
+
+ private fun retrieveBinding(threePids: List) = withState { state ->
+ if (state.identityServer().isNullOrBlank()) return@withState
+
+ val emails = threePids.filterIsInstance()
+ val msisdns = threePids.filterIsInstance()
+
+ setState {
+ copy(
+ emailList = Success(emails.map { PidInfo(it, Loading()) }),
+ phoneNumbersList = Success(msisdns.map { PidInfo(it, Loading()) })
+ )
+ }
+
+ viewModelScope.launch {
+ try {
+ val data = awaitCallback> {
+ identityService.getShareStatus(threePids, it)
+ }
+ setState {
+ copy(
+ emailList = Success(data.filter { it.key is ThreePid.Email }.toPidInfoList()),
+ phoneNumbersList = Success(data.filter { it.key is ThreePid.Msisdn }.toPidInfoList()),
+ termsNotSigned = false
+ )
+ }
+ } catch (failure: Throwable) {
+ if (failure !is IdentityServiceError.TermsNotSignedException) {
+ _viewEvents.post(DiscoverySettingsViewEvents.Failure(failure))
+ }
+
+ setState {
+ copy(
+ emailList = Success(emails.map { PidInfo(it, Fail(failure)) }),
+ phoneNumbersList = Success(msisdns.map { PidInfo(it, Fail(failure)) }),
+ termsNotSigned = failure is IdentityServiceError.TermsNotSignedException
+ )
+ }
+ }
+ }
+ }
+
+ private fun Map.toPidInfoList(): List {
+ return map { threePidStatus ->
+ PidInfo(
+ threePid = threePidStatus.key,
+ isShared = Success(threePidStatus.value)
+ )
+ }
+ }
+
+ private fun submitMsisdnToken(action: DiscoverySettingsAction.SubmitMsisdnToken) = withState { state ->
+ if (state.identityServer().isNullOrBlank()) return@withState
+
+ changeThreePidSubmitState(action.threePid, Loading())
+
+ viewModelScope.launch {
+ try {
+ awaitCallback {
+ identityService.submitValidationToken(action.threePid, action.code, it)
+ }
+ changeThreePidSubmitState(action.threePid, Uninitialized)
+ finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(action.threePid), true)
+ } catch (failure: Throwable) {
+ changeThreePidSubmitState(action.threePid, Fail(failure))
+ }
+ }
+ }
+
+ private fun finalizeBind3pid(action: DiscoverySettingsAction.FinalizeBind3pid, fromUser: Boolean) = withState { state ->
+ val threePid = when (action.threePid) {
+ is ThreePid.Email -> {
+ state.emailList()?.find { it.threePid.value == action.threePid.email }?.threePid ?: return@withState
+ }
+ is ThreePid.Msisdn -> {
+ state.phoneNumbersList()?.find { it.threePid.value == action.threePid.msisdn }?.threePid ?: return@withState
+ }
+ }
+
+ changeThreePidSubmitState(action.threePid, Loading())
+
+ viewModelScope.launch {
+ try {
+ awaitCallback { identityService.finalizeBindThreePid(threePid, it) }
+ changeThreePidSubmitState(action.threePid, Uninitialized)
+ changeThreePidState(action.threePid, Success(SharedState.SHARED))
+ } catch (failure: Throwable) {
+ // If this is not from user (user did not click to "Continue", but this is a refresh when Fragment is resumed), do no display the error
+ if (fromUser) {
+ changeThreePidSubmitState(action.threePid, Fail(failure))
+ } else {
+ changeThreePidSubmitState(action.threePid, Uninitialized)
+ }
+ }
+ }
+ }
+
+ private fun refreshPendingEmailBindings() = withState { state ->
+ state.emailList()?.forEach { info ->
+ when (info.isShared()) {
+ SharedState.BINDING_IN_PROGRESS -> finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(info.threePid), false)
+ else -> Unit
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySharedViewModel.kt
new file mode 100644
index 0000000000..cde326d824
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySharedViewModel.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import im.vector.riotx.core.extensions.postLiveEvent
+import im.vector.riotx.core.utils.LiveEvent
+import javax.inject.Inject
+
+class DiscoverySharedViewModel @Inject constructor() : ViewModel() {
+ var navigateEvent = MutableLiveData>()
+
+ fun requestChangeToIdentityServer(serverUrl: String) {
+ navigateEvent.postLiveEvent(DiscoverySharedViewModelAction.ChangeIdentityServer(serverUrl))
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySharedViewModelAction.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySharedViewModelAction.kt
new file mode 100644
index 0000000000..5889ce4a63
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySharedViewModelAction.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.discovery
+
+sealed class DiscoverySharedViewModelAction {
+ data class ChangeIdentityServer(val newUrl: String) : DiscoverySharedViewModelAction()
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/PidInfo.kt b/vector/src/main/java/im/vector/riotx/features/discovery/PidInfo.kt
new file mode 100644
index 0000000000..67739c48ce
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/PidInfo.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.discovery
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.Uninitialized
+import im.vector.matrix.android.api.session.identity.SharedState
+import im.vector.matrix.android.api.session.identity.ThreePid
+
+data class PidInfo(
+ // Retrieved from the homeserver
+ val threePid: ThreePid,
+ // Retrieved from IdentityServer, or transient state
+ val isShared: Async,
+ // Contains information about a current request to submit the token (for instance SMS code received by SMS)
+ // Or a current binding finalization, for email
+ val finalRequest: Async = Uninitialized
+)
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/SettingsButtonItem.kt b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsButtonItem.kt
new file mode 100644
index 0000000000..11a2737496
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsButtonItem.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery
+
+import android.widget.Button
+import androidx.annotation.StringRes
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import com.airbnb.epoxy.EpoxyModelWithHolder
+import im.vector.riotx.R
+import im.vector.riotx.core.epoxy.ClickListener
+import im.vector.riotx.core.epoxy.VectorEpoxyHolder
+import im.vector.riotx.core.epoxy.attributes.ButtonStyle
+import im.vector.riotx.core.epoxy.onClick
+import im.vector.riotx.core.extensions.setTextOrHide
+import im.vector.riotx.core.resources.ColorProvider
+
+@EpoxyModelClass(layout = R.layout.item_settings_button)
+abstract class SettingsButtonItem : EpoxyModelWithHolder() {
+
+ @EpoxyAttribute
+ lateinit var colorProvider: ColorProvider
+
+ @EpoxyAttribute
+ var buttonTitle: String? = null
+
+ @EpoxyAttribute
+ @StringRes
+ var buttonTitleId: Int? = null
+
+ @EpoxyAttribute
+ var buttonStyle: ButtonStyle = ButtonStyle.POSITIVE
+
+ @EpoxyAttribute
+ var buttonClickListener: ClickListener? = null
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+ if (buttonTitleId != null) {
+ holder.button.setText(buttonTitleId!!)
+ } else {
+ holder.button.setTextOrHide(buttonTitle)
+ }
+
+ when (buttonStyle) {
+ ButtonStyle.POSITIVE -> {
+ holder.button.setTextColor(colorProvider.getColor(R.color.riotx_accent))
+ }
+ ButtonStyle.DESTRUCTIVE -> {
+ holder.button.setTextColor(colorProvider.getColor(R.color.riotx_destructive_accent))
+ }
+ }
+
+ holder.button.onClick(buttonClickListener)
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val button by bind(R.id.settings_item_button)
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/SettingsContinueCancelItem.kt b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsContinueCancelItem.kt
new file mode 100644
index 0000000000..0e424de540
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsContinueCancelItem.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery
+
+import android.widget.Button
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import com.airbnb.epoxy.EpoxyModelWithHolder
+import im.vector.riotx.R
+import im.vector.riotx.core.epoxy.VectorEpoxyHolder
+
+@EpoxyModelClass(layout = R.layout.item_settings_continue_cancel)
+abstract class SettingsContinueCancelItem : EpoxyModelWithHolder() {
+
+ @EpoxyAttribute
+ var interactionListener: Listener? = null
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+
+ holder.cancelButton.setOnClickListener {
+ interactionListener?.onCancel()
+ }
+
+ holder.continueButton.setOnClickListener {
+ interactionListener?.onContinue()
+ }
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val cancelButton by bind(R.id.settings_item_cancel_button)
+ val continueButton by bind(R.id.settings_item_continue_button)
+ }
+
+ interface Listener {
+ fun onContinue()
+ fun onCancel()
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/SettingsEditTextItem.kt b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsEditTextItem.kt
new file mode 100644
index 0000000000..aaa4435f3a
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsEditTextItem.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery
+
+import android.view.KeyEvent
+import android.view.inputmethod.EditorInfo
+import android.widget.EditText
+import android.widget.TextView
+import androidx.core.widget.doOnTextChanged
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import com.airbnb.epoxy.EpoxyModelWithHolder
+import com.google.android.material.textfield.TextInputLayout
+import im.vector.riotx.R
+import im.vector.riotx.core.epoxy.VectorEpoxyHolder
+import im.vector.riotx.core.extensions.setTextOrHide
+
+@EpoxyModelClass(layout = R.layout.item_settings_edit_text)
+abstract class SettingsEditTextItem : EpoxyModelWithHolder() {
+
+ @EpoxyAttribute var descriptionText: String? = null
+ @EpoxyAttribute var errorText: String? = null
+ @EpoxyAttribute var inProgress: Boolean = false
+
+ @EpoxyAttribute
+ var interactionListener: Listener? = null
+
+ private val textChangeListener: (text: CharSequence?, start: Int, count: Int, after: Int) -> Unit = { code, _, _, _ ->
+ code?.let { interactionListener?.onCodeChange(it.toString()) }
+ }
+
+ private val editorActionListener = object : TextView.OnEditorActionListener {
+ override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ interactionListener?.onValidate()
+ return true
+ }
+ return false
+ }
+ }
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+ holder.textView.setTextOrHide(descriptionText)
+
+ holder.editText.isEnabled = !inProgress
+
+ if (errorText.isNullOrBlank()) {
+ holder.textInputLayout.error = null
+ } else {
+ holder.textInputLayout.error = errorText
+ }
+
+ holder.editText.doOnTextChanged(textChangeListener)
+ holder.editText.setOnEditorActionListener(editorActionListener)
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val textView by bind(R.id.settings_item_edit_text_description)
+ val editText by bind(R.id.settings_item_edit_text)
+ val textInputLayout by bind(R.id.settings_item_edit_text_til)
+ }
+
+ interface Listener {
+ fun onValidate()
+ fun onCodeChange(code: String)
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/SettingsInfoItem.kt b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsInfoItem.kt
new file mode 100644
index 0000000000..c28b58340b
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsInfoItem.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery
+
+import android.view.View
+import android.widget.TextView
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import com.airbnb.epoxy.EpoxyModelWithHolder
+import im.vector.riotx.R
+import im.vector.riotx.core.epoxy.VectorEpoxyHolder
+import im.vector.riotx.core.extensions.setTextOrHide
+
+@EpoxyModelClass(layout = R.layout.item_settings_helper_info)
+abstract class SettingsInfoItem : EpoxyModelWithHolder() {
+
+ @EpoxyAttribute
+ var helperText: String? = null
+
+ @EpoxyAttribute
+ @StringRes
+ var helperTextResId: Int? = null
+
+ @EpoxyAttribute
+ var itemClickListener: View.OnClickListener? = null
+
+ @EpoxyAttribute
+ @DrawableRes
+ var compoundDrawable: Int = R.drawable.vector_warning_red
+
+ @EpoxyAttribute
+ var showCompoundDrawable: Boolean = false
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+
+ if (helperTextResId != null) {
+ holder.text.setText(helperTextResId!!)
+ } else {
+ holder.text.setTextOrHide(helperText)
+ }
+
+ holder.view.setOnClickListener(itemClickListener)
+
+ if (showCompoundDrawable) {
+ holder.text.setCompoundDrawablesWithIntrinsicBounds(compoundDrawable, 0, 0, 0)
+ } else {
+ holder.text.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
+ }
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val text by bind(R.id.settings_helper_text)
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/SettingsInformationItem.kt b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsInformationItem.kt
new file mode 100644
index 0000000000..ad3c445ac3
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsInformationItem.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery
+
+import android.widget.TextView
+import androidx.annotation.ColorRes
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import com.airbnb.epoxy.EpoxyModelWithHolder
+import im.vector.riotx.R
+import im.vector.riotx.core.epoxy.VectorEpoxyHolder
+import im.vector.riotx.core.resources.ColorProvider
+
+@EpoxyModelClass(layout = R.layout.item_settings_information)
+abstract class SettingsInformationItem : EpoxyModelWithHolder() {
+
+ @EpoxyAttribute
+ lateinit var colorProvider: ColorProvider
+
+ @EpoxyAttribute
+ lateinit var message: String
+
+ @EpoxyAttribute
+ @ColorRes
+ var textColorId: Int = R.color.vector_info_color
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+
+ holder.textView.text = message
+ holder.textView.setTextColor(colorProvider.getColor(textColorId))
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val textView by bind(R.id.settings_item_information)
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/SettingsItem.kt b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsItem.kt
new file mode 100644
index 0000000000..4b6e4b3edf
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsItem.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery
+
+import android.view.View
+import android.widget.Switch
+import android.widget.TextView
+import androidx.annotation.StringRes
+import androidx.core.view.isVisible
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import com.airbnb.epoxy.EpoxyModelWithHolder
+import im.vector.riotx.R
+import im.vector.riotx.core.epoxy.VectorEpoxyHolder
+import im.vector.riotx.core.extensions.setTextOrHide
+
+@EpoxyModelClass(layout = R.layout.item_settings_simple_item)
+abstract class SettingsItem : EpoxyModelWithHolder() {
+
+ @EpoxyAttribute
+ var title: String? = null
+
+ @EpoxyAttribute
+ @StringRes
+ var titleResId: Int? = null
+
+ @EpoxyAttribute
+ @StringRes
+ var descriptionResId: Int? = null
+
+ @EpoxyAttribute
+ var description: CharSequence? = null
+
+ @EpoxyAttribute
+ var itemClickListener: View.OnClickListener? = null
+
+ override fun bind(holder: Holder) {
+ if (titleResId != null) {
+ holder.titleText.setText(titleResId!!)
+ } else {
+ holder.titleText.setTextOrHide(title)
+ }
+
+ if (descriptionResId != null) {
+ holder.descriptionText.setText(descriptionResId!!)
+ } else {
+ holder.descriptionText.setTextOrHide(description)
+ }
+
+ holder.switchButton.isVisible = false
+
+ holder.view.setOnClickListener(itemClickListener)
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val titleText by bind(R.id.settings_item_title)
+ val descriptionText by bind(R.id.settings_item_description)
+ val switchButton by bind(R.id.settings_item_switch)
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/SettingsProgressItem.kt b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsProgressItem.kt
new file mode 100644
index 0000000000..b48fc3b7f1
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsProgressItem.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery
+
+import com.airbnb.epoxy.EpoxyModelClass
+import com.airbnb.epoxy.EpoxyModelWithHolder
+import im.vector.riotx.R
+import im.vector.riotx.core.epoxy.VectorEpoxyHolder
+
+@EpoxyModelClass(layout = R.layout.item_settings_progress)
+abstract class SettingsProgressItem : EpoxyModelWithHolder() {
+
+ class Holder : VectorEpoxyHolder()
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/SettingsSectionTitleItem.kt b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsSectionTitleItem.kt
new file mode 100644
index 0000000000..d8b6eb7daa
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsSectionTitleItem.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery
+
+import android.widget.TextView
+import androidx.annotation.StringRes
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import com.airbnb.epoxy.EpoxyModelWithHolder
+import im.vector.riotx.R
+import im.vector.riotx.core.epoxy.VectorEpoxyHolder
+import im.vector.riotx.core.extensions.setTextOrHide
+
+@EpoxyModelClass(layout = R.layout.item_settings_section_title)
+abstract class SettingsSectionTitleItem : EpoxyModelWithHolder() {
+
+ @EpoxyAttribute
+ var title: String? = null
+
+ @EpoxyAttribute
+ @StringRes
+ var titleResId: Int? = null
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+
+ if (titleResId != null) {
+ holder.textView.setText(titleResId!!)
+ } else {
+ holder.textView.setTextOrHide(title)
+ }
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val textView by bind(R.id.settings_section_title_text)
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/SettingsTextButtonSingleLineItem.kt b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsTextButtonSingleLineItem.kt
new file mode 100644
index 0000000000..90a1a9ef99
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/SettingsTextButtonSingleLineItem.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery
+
+import android.widget.Button
+import android.widget.CompoundButton
+import android.widget.ProgressBar
+import android.widget.Switch
+import android.widget.TextView
+import androidx.annotation.StringRes
+import androidx.core.content.ContextCompat
+import androidx.core.view.isInvisible
+import androidx.core.view.isVisible
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import com.airbnb.epoxy.EpoxyModelWithHolder
+import im.vector.riotx.R
+import im.vector.riotx.core.epoxy.ClickListener
+import im.vector.riotx.core.epoxy.VectorEpoxyHolder
+import im.vector.riotx.core.epoxy.attributes.ButtonStyle
+import im.vector.riotx.core.epoxy.attributes.ButtonType
+import im.vector.riotx.core.epoxy.attributes.IconMode
+import im.vector.riotx.core.epoxy.onClick
+import im.vector.riotx.core.extensions.exhaustive
+import im.vector.riotx.core.extensions.setTextOrHide
+import im.vector.riotx.core.resources.ColorProvider
+import im.vector.riotx.core.resources.StringProvider
+import im.vector.riotx.features.themes.ThemeUtils
+
+@EpoxyModelClass(layout = R.layout.item_settings_button_single_line)
+abstract class SettingsTextButtonSingleLineItem : EpoxyModelWithHolder() {
+
+ @EpoxyAttribute
+ lateinit var colorProvider: ColorProvider
+
+ @EpoxyAttribute
+ lateinit var stringProvider: StringProvider
+
+ @EpoxyAttribute
+ var title: String? = null
+
+ @EpoxyAttribute
+ @StringRes
+ var titleResId: Int? = null
+
+ @EpoxyAttribute
+ var iconMode: IconMode = IconMode.NONE
+
+ @EpoxyAttribute
+ var buttonTitle: String? = null
+
+ @EpoxyAttribute
+ @StringRes
+ var buttonTitleId: Int? = null
+
+ @EpoxyAttribute
+ var buttonStyle: ButtonStyle = ButtonStyle.POSITIVE
+
+ @EpoxyAttribute
+ var buttonType: ButtonType = ButtonType.NORMAL
+
+ @EpoxyAttribute
+ var buttonIndeterminate: Boolean = false
+
+ @EpoxyAttribute
+ var checked: Boolean? = null
+
+ @EpoxyAttribute
+ var buttonClickListener: ClickListener? = null
+
+ @EpoxyAttribute
+ var switchChangeListener: CompoundButton.OnCheckedChangeListener? = null
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+
+ if (titleResId != null) {
+ holder.textView.setText(titleResId!!)
+ } else {
+ holder.textView.setTextOrHide(title, hideWhenBlank = false)
+ }
+
+ if (buttonTitleId != null) {
+ holder.mainButton.setText(buttonTitleId!!)
+ } else {
+ holder.mainButton.setTextOrHide(buttonTitle)
+ }
+
+ if (buttonIndeterminate) {
+ holder.progress.isVisible = true
+ holder.mainButton.isInvisible = true
+ holder.switchButton.isInvisible = true
+ holder.switchButton.setOnCheckedChangeListener(null)
+ holder.mainButton.setOnClickListener(null)
+ } else {
+ holder.progress.isVisible = false
+ when (buttonType) {
+ ButtonType.NO_BUTTON -> {
+ holder.mainButton.isVisible = false
+ holder.switchButton.isVisible = false
+ }
+ ButtonType.NORMAL -> {
+ holder.mainButton.isVisible = true
+ holder.switchButton.isVisible = false
+ when (buttonStyle) {
+ ButtonStyle.POSITIVE -> {
+ holder.mainButton.setTextColor(colorProvider.getColorFromAttribute(R.attr.colorAccent))
+ }
+ ButtonStyle.DESTRUCTIVE -> {
+ holder.mainButton.setTextColor(colorProvider.getColor(R.color.vector_error_color))
+ }
+ }.exhaustive
+ holder.mainButton.onClick(buttonClickListener)
+ }
+ ButtonType.SWITCH -> {
+ holder.mainButton.isVisible = false
+ holder.switchButton.isVisible = true
+ // set to null before changing the state
+ holder.switchButton.setOnCheckedChangeListener(null)
+ checked?.let { holder.switchButton.isChecked = it }
+ holder.switchButton.setOnCheckedChangeListener(switchChangeListener)
+ }
+ }.exhaustive
+ }
+
+ when (iconMode) {
+ IconMode.NONE -> {
+ holder.textView.setCompoundDrawables(null, null, null, null)
+ }
+ IconMode.INFO -> {
+ val errorColor = colorProvider.getColor(R.color.notification_accent_color)
+ ContextCompat.getDrawable(holder.view.context, R.drawable.ic_notification_privacy_warning)?.apply {
+ ThemeUtils.tintDrawableWithColor(this, errorColor)
+ holder.textView.setCompoundDrawablesWithIntrinsicBounds(this, null, null, null)
+ }
+ }
+ IconMode.ERROR -> {
+ val errorColor = colorProvider.getColor(R.color.vector_error_color)
+ ContextCompat.getDrawable(holder.view.context, R.drawable.ic_notification_privacy_warning)?.apply {
+ ThemeUtils.tintDrawableWithColor(this, errorColor)
+ holder.textView.setCompoundDrawablesWithIntrinsicBounds(this, null, null, null)
+ }
+ }
+ }
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val textView by bind(R.id.settings_item_text)
+ val mainButton by bind(R.id.settings_item_button)
+ val switchButton by bind(R.id.settings_item_switch)
+ val progress by bind(R.id.settings_item_progress)
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerAction.kt b/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerAction.kt
new file mode 100644
index 0000000000..14f149c282
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerAction.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.discovery.change
+
+import im.vector.riotx.core.platform.VectorViewModelAction
+
+sealed class SetIdentityServerAction : VectorViewModelAction {
+ object UseDefaultIdentityServer : SetIdentityServerAction()
+
+ data class UseCustomIdentityServer(val url: String) : SetIdentityServerAction()
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerFragment.kt b/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerFragment.kt
new file mode 100644
index 0000000000..701c187cef
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerFragment.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery.change
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.view.inputmethod.EditorInfo
+import androidx.appcompat.app.AlertDialog
+import androidx.core.text.toSpannable
+import androidx.core.view.isVisible
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
+import com.jakewharton.rxbinding3.widget.textChanges
+import im.vector.matrix.android.api.session.terms.TermsService
+import im.vector.riotx.R
+import im.vector.riotx.core.extensions.exhaustive
+import im.vector.riotx.core.extensions.toReducedUrl
+import im.vector.riotx.core.platform.VectorBaseActivity
+import im.vector.riotx.core.platform.VectorBaseFragment
+import im.vector.riotx.core.resources.ColorProvider
+import im.vector.riotx.core.utils.colorizeMatchingText
+import im.vector.riotx.features.discovery.DiscoverySharedViewModel
+import im.vector.riotx.features.terms.ReviewTermsActivity
+import kotlinx.android.synthetic.main.fragment_set_identity_server.*
+import javax.inject.Inject
+
+class SetIdentityServerFragment @Inject constructor(
+ val viewModelFactory: SetIdentityServerViewModel.Factory,
+ val colorProvider: ColorProvider
+) : VectorBaseFragment() {
+
+ override fun getLayoutResId() = R.layout.fragment_set_identity_server
+
+ private val viewModel by fragmentViewModel(SetIdentityServerViewModel::class)
+
+ lateinit var sharedViewModel: DiscoverySharedViewModel
+
+ override fun invalidate() = withState(viewModel) { state ->
+ if (state.defaultIdentityServerUrl.isNullOrEmpty()) {
+ // No default
+ identityServerSetDefaultNotice.isVisible = false
+ identityServerSetDefaultSubmit.isVisible = false
+ identityServerSetDefaultAlternative.setText(R.string.identity_server_set_alternative_notice_no_default)
+ } else {
+ identityServerSetDefaultNotice.text = getString(
+ R.string.identity_server_set_default_notice,
+ state.homeServerUrl.toReducedUrl(),
+ state.defaultIdentityServerUrl.toReducedUrl()
+ )
+ .toSpannable()
+ .colorizeMatchingText(state.defaultIdentityServerUrl.toReducedUrl(),
+ colorProvider.getColorFromAttribute(R.attr.riotx_text_primary_body_contrast))
+
+ identityServerSetDefaultNotice.isVisible = true
+ identityServerSetDefaultSubmit.isVisible = true
+ identityServerSetDefaultSubmit.text = getString(R.string.identity_server_set_default_submit, state.defaultIdentityServerUrl.toReducedUrl())
+ identityServerSetDefaultAlternative.setText(R.string.identity_server_set_alternative_notice)
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ sharedViewModel = activityViewModelProvider.get(DiscoverySharedViewModel::class.java)
+
+ identityServerSetDefaultAlternativeTextInput.setOnEditorActionListener { _, actionId, _ ->
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ viewModel.handle(SetIdentityServerAction.UseCustomIdentityServer(identityServerSetDefaultAlternativeTextInput.text.toString()))
+ return@setOnEditorActionListener true
+ }
+ return@setOnEditorActionListener false
+ }
+
+ identityServerSetDefaultAlternativeTextInput
+ .textChanges()
+ .subscribe {
+ identityServerSetDefaultAlternativeTil.error = null
+ identityServerSetDefaultAlternativeSubmit.isEnabled = it.isNotEmpty()
+ }
+ .disposeOnDestroyView()
+
+ identityServerSetDefaultSubmit.debouncedClicks {
+ viewModel.handle(SetIdentityServerAction.UseDefaultIdentityServer)
+ }
+
+ identityServerSetDefaultAlternativeSubmit.debouncedClicks {
+ viewModel.handle(SetIdentityServerAction.UseCustomIdentityServer(identityServerSetDefaultAlternativeTextInput.text.toString()))
+ }
+
+ viewModel.observeViewEvents {
+ when (it) {
+ is SetIdentityServerViewEvents.Loading -> showLoading(it.message)
+ is SetIdentityServerViewEvents.Failure -> handleFailure(it)
+ is SetIdentityServerViewEvents.OtherFailure -> showFailure(it.failure)
+ is SetIdentityServerViewEvents.NoTerms -> {
+ AlertDialog.Builder(requireActivity())
+ .setTitle(R.string.settings_discovery_no_terms_title)
+ .setMessage(R.string.settings_discovery_no_terms)
+ .setPositiveButton(R.string._continue) { _, _ ->
+ processIdentityServerChange()
+ }
+ .setNegativeButton(R.string.cancel, null)
+ .show()
+ Unit
+ }
+ is SetIdentityServerViewEvents.TermsAccepted -> processIdentityServerChange()
+ is SetIdentityServerViewEvents.ShowTerms -> {
+ navigator.openTerms(
+ this,
+ TermsService.ServiceType.IdentityService,
+ it.identityServerUrl,
+ null)
+ }
+ }.exhaustive
+ }
+ }
+
+ private fun handleFailure(failure: SetIdentityServerViewEvents.Failure) {
+ val message = getString(failure.errorMessageId)
+ if (failure.forDefault) {
+ // Display the error in a dialog
+ AlertDialog.Builder(requireActivity())
+ .setTitle(R.string.dialog_title_error)
+ .setMessage(message)
+ .setPositiveButton(R.string.ok, null)
+ .show()
+ } else {
+ // Display the error inlined
+ identityServerSetDefaultAlternativeTil.error = message
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.identity_server)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) {
+ if (Activity.RESULT_OK == resultCode) {
+ processIdentityServerChange()
+ } else {
+ // add some error?
+ }
+ }
+ super.onActivityResult(requestCode, resultCode, data)
+ }
+
+ private fun processIdentityServerChange() {
+ viewModel.currentWantedUrl?.let {
+ sharedViewModel.requestChangeToIdentityServer(it)
+ parentFragmentManager.popBackStack()
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerState.kt b/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerState.kt
new file mode 100644
index 0000000000..2b76d21ce5
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerState.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.discovery.change
+
+import com.airbnb.mvrx.MvRxState
+
+data class SetIdentityServerState(
+ val homeServerUrl: String = "",
+ // Will contain the default identity server url if any
+ val defaultIdentityServerUrl: String? = null
+) : MvRxState
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerViewEvents.kt
new file mode 100644
index 0000000000..b840afac11
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerViewEvents.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.discovery.change
+
+import androidx.annotation.StringRes
+import im.vector.riotx.core.platform.VectorViewEvents
+
+sealed class SetIdentityServerViewEvents : VectorViewEvents {
+ data class Loading(val message: CharSequence? = null) : SetIdentityServerViewEvents()
+ data class Failure(@StringRes val errorMessageId: Int, val forDefault: Boolean) : SetIdentityServerViewEvents()
+ data class OtherFailure(val failure: Throwable) : SetIdentityServerViewEvents()
+
+ data class ShowTerms(val identityServerUrl: String) : SetIdentityServerViewEvents()
+
+ object NoTerms : SetIdentityServerViewEvents()
+ object TermsAccepted : SetIdentityServerViewEvents()
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerViewModel.kt b/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerViewModel.kt
new file mode 100644
index 0000000000..9bec24548e
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/discovery/change/SetIdentityServerViewModel.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.riotx.features.discovery.change
+
+import androidx.lifecycle.viewModelScope
+import com.airbnb.mvrx.FragmentViewModelContext
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.ViewModelContext
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import im.vector.matrix.android.api.failure.Failure
+import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
+import im.vector.matrix.android.api.session.terms.GetTermsResponse
+import im.vector.matrix.android.api.session.terms.TermsService
+import im.vector.matrix.android.internal.util.awaitCallback
+import im.vector.riotx.R
+import im.vector.riotx.core.di.HasScreenInjector
+import im.vector.riotx.core.extensions.exhaustive
+import im.vector.riotx.core.platform.VectorViewModel
+import im.vector.riotx.core.resources.StringProvider
+import im.vector.riotx.core.utils.ensureProtocol
+import kotlinx.coroutines.launch
+import java.net.UnknownHostException
+
+class SetIdentityServerViewModel @AssistedInject constructor(
+ @Assisted initialState: SetIdentityServerState,
+ private val mxSession: Session,
+ stringProvider: StringProvider)
+ : VectorViewModel(initialState) {
+
+ @AssistedInject.Factory
+ interface Factory {
+ fun create(initialState: SetIdentityServerState): SetIdentityServerViewModel
+ }
+
+ companion object : MvRxViewModelFactory {
+
+ override fun initialState(viewModelContext: ViewModelContext): SetIdentityServerState? {
+ val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession()
+
+ return SetIdentityServerState(
+ homeServerUrl = session.sessionParams.homeServerUrl,
+ defaultIdentityServerUrl = session.identityService().getDefaultIdentityServer()
+ )
+ }
+
+ @JvmStatic
+ override fun create(viewModelContext: ViewModelContext, state: SetIdentityServerState): SetIdentityServerViewModel? {
+ val fragment: SetIdentityServerFragment = (viewModelContext as FragmentViewModelContext).fragment()
+ return fragment.viewModelFactory.create(state)
+ }
+ }
+
+ var currentWantedUrl: String? = null
+ private set
+
+ private val userLanguage = stringProvider.getString(R.string.resources_language)
+
+ override fun handle(action: SetIdentityServerAction) {
+ when (action) {
+ SetIdentityServerAction.UseDefaultIdentityServer -> useDefault()
+ is SetIdentityServerAction.UseCustomIdentityServer -> usedCustomIdentityServerUrl(action)
+ }.exhaustive
+ }
+
+ private fun useDefault() = withState { state ->
+ state.defaultIdentityServerUrl?.let { doChangeIdentityServerUrl(it, true) }
+ }
+
+ private fun usedCustomIdentityServerUrl(action: SetIdentityServerAction.UseCustomIdentityServer) {
+ doChangeIdentityServerUrl(action.url, false)
+ }
+
+ private fun doChangeIdentityServerUrl(url: String, isDefault: Boolean) {
+ if (url.isEmpty()) {
+ _viewEvents.post(SetIdentityServerViewEvents.Failure(R.string.settings_discovery_please_enter_server, isDefault))
+ return
+ }
+ val baseUrl = url.ensureProtocol().also { currentWantedUrl = it }
+
+ _viewEvents.post(SetIdentityServerViewEvents.Loading())
+
+ viewModelScope.launch {
+ try {
+ // First ping the identity server v2 API
+ awaitCallback {
+ mxSession.identityService().isValidIdentityServer(baseUrl, it)
+ }
+ // Ok, next step
+ checkTerms(baseUrl)
+ } catch (failure: Throwable) {
+ when {
+ failure is IdentityServiceError.OutdatedIdentityServer ->
+ _viewEvents.post(SetIdentityServerViewEvents.Failure(R.string.identity_server_error_outdated_identity_server, isDefault))
+ failure is Failure.NetworkConnection && failure.ioException is UnknownHostException ->
+ _viewEvents.post(SetIdentityServerViewEvents.Failure(R.string.settings_discovery_bad_identity_server, isDefault))
+ else ->
+ _viewEvents.post(SetIdentityServerViewEvents.OtherFailure(failure))
+ }
+ }
+ }
+ }
+
+ private suspend fun checkTerms(baseUrl: String) {
+ try {
+ val data = awaitCallback {
+ mxSession.getTerms(TermsService.ServiceType.IdentityService, baseUrl, it)
+ }
+
+ // has all been accepted?
+ val resp = data.serverResponse
+ val tos = resp.getLocalizedTerms(userLanguage)
+ if (tos.isEmpty()) {
+ // prompt do not define policy
+ _viewEvents.post(SetIdentityServerViewEvents.NoTerms)
+ } else {
+ val shouldPrompt = tos.any { !data.alreadyAcceptedTermUrls.contains(it.localizedUrl) }
+ if (shouldPrompt) {
+ _viewEvents.post(SetIdentityServerViewEvents.ShowTerms(baseUrl))
+ } else {
+ _viewEvents.post(SetIdentityServerViewEvents.TermsAccepted)
+ }
+ }
+ } catch (failure: Throwable) {
+ if (failure is Failure.OtherServerError && failure.httpCode == 404) {
+ // 404: Same as NoTerms
+ _viewEvents.post(SetIdentityServerViewEvents.NoTerms)
+ } else {
+ _viewEvents.post(SetIdentityServerViewEvents.OtherFailure(failure))
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt
index 1357b30413..b17fb87f50 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt
@@ -209,7 +209,7 @@ class HomeDetailFragment @Inject constructor(
parentActivity.configure(groupToolbar)
}
groupToolbar.title = ""
- groupToolbarAvatarImageView.setOnClickListener {
+ groupToolbarAvatarImageView.debouncedClicks {
sharedActionViewModel.post(HomeActivitySharedAction.OpenDrawer)
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDrawerFragment.kt
index a8373797c6..3439b1793c 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/HomeDrawerFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDrawerFragment.kt
@@ -53,13 +53,13 @@ class HomeDrawerFragment @Inject constructor(
homeDrawerUserIdView.text = user.userId
}
}
- homeDrawerHeaderSettingsView.setOnClickListener {
+ homeDrawerHeaderSettingsView.debouncedClicks {
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
navigator.openSettings(requireActivity())
}
// Debug menu
- homeDrawerHeaderDebugView.setOnClickListener {
+ homeDrawerHeaderDebugView.debouncedClicks {
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
navigator.openDebug(requireActivity())
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/UnknownDeviceDetectorSharedViewModel.kt
index a05e9ee985..1a0d9baf15 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/UnknownDeviceDetectorSharedViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/UnknownDeviceDetectorSharedViewModel.kt
@@ -67,9 +67,10 @@ class UnknownDeviceDetectorSharedViewModel(
init {
- val currentSessionTs = session.cryptoService().getCryptoDeviceInfo(session.myUserId).firstOrNull {
- it.deviceId == session.sessionParams.credentials.deviceId
- }?.firstTimeSeenLocalTs ?: System.currentTimeMillis()
+ val currentSessionTs = session.cryptoService().getCryptoDeviceInfo(session.myUserId)
+ .firstOrNull { it.deviceId == session.sessionParams.deviceId }
+ ?.firstTimeSeenLocalTs
+ ?: System.currentTimeMillis()
Timber.v("## Detector - Current Session first time seen $currentSessionTs")
ignoredDeviceList.addAll(
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 f348e0612a..079a2927d7 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
@@ -264,7 +264,7 @@ class RoomDetailFragment @Inject constructor(
setupNotificationView()
setupJumpToReadMarkerView()
setupJumpToBottomView()
- roomToolbarContentView.setOnClickListener {
+ roomToolbarContentView.debouncedClicks {
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
}
roomDetailViewModel.subscribe { renderState(it) }
@@ -348,7 +348,7 @@ class RoomDetailFragment @Inject constructor(
private fun setupJumpToBottomView() {
jumpToBottomView.visibility = View.INVISIBLE
- jumpToBottomView.setOnClickListener {
+ jumpToBottomView.debouncedClicks {
roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
jumpToBottomView.visibility = View.INVISIBLE
if (!roomDetailViewModel.timeline.isLive) {
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 727b33ee37..20748791aa 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
@@ -99,11 +99,13 @@ class RoomDetailViewModel @AssistedInject constructor(
private val timelineSettings = if (userPreferencesProvider.shouldShowHiddenEvents()) {
TimelineSettings(30,
filterEdits = false,
+ filterRedacted = userPreferencesProvider.shouldShowRedactedMessages().not(),
filterTypes = false,
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
} else {
TimelineSettings(30,
filterEdits = true,
+ filterRedacted = userPreferencesProvider.shouldShowRedactedMessages().not(),
filterTypes = true,
allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES,
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt
index 9c606de3b4..1484e8009b 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt
@@ -142,10 +142,10 @@ class RoomListFragment @Inject constructor(
else -> Unit // No button in this mode
}
- createChatRoomButton.setOnClickListener {
+ createChatRoomButton.debouncedClicks {
createDirectChat()
}
- createGroupRoomButton.setOnClickListener {
+ createGroupRoomButton.debouncedClicks {
openRoomDirectory()
}
diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt
index 07c0cdbc7d..fc2f34b7a0 100644
--- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt
@@ -66,13 +66,13 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted
{
val successMessage = when (selectedUsers.size) {
1 -> stringProvider.getString(R.string.invitation_sent_to_one_user,
- selectedUsers.first().displayName)
+ selectedUsers.first().getBestName())
2 -> stringProvider.getString(R.string.invitations_sent_to_two_users,
- selectedUsers.first().displayName,
- selectedUsers.last().displayName)
+ selectedUsers.first().getBestName(),
+ selectedUsers.last().getBestName())
else -> stringProvider.getQuantityString(R.plurals.invitations_sent_to_one_and_more_users,
selectedUsers.size - 1,
- selectedUsers.first().displayName,
+ selectedUsers.first().getBestName(),
selectedUsers.size - 1)
}
_viewEvents.post(InviteUsersToRoomViewEvents.Success(successMessage))
diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt
index 92dcfcc8aa..6f95847b20 100644
--- a/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt
@@ -25,6 +25,7 @@ import butterknife.OnClick
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.riotx.R
import im.vector.riotx.core.extensions.hideKeyboard
+import im.vector.riotx.core.utils.ensureProtocol
import im.vector.riotx.core.utils.openUrlInExternalBrowser
import kotlinx.android.synthetic.main.fragment_login_server_url_form.*
import javax.inject.Inject
@@ -96,16 +97,13 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
cleanupUi()
// Static check of homeserver url, empty, malformed, etc.
- var serverUrl = loginServerUrlFormHomeServerUrl.text.toString().trim()
+ val serverUrl = loginServerUrlFormHomeServerUrl.text.toString().trim().ensureProtocol()
when {
serverUrl.isBlank() -> {
loginServerUrlFormHomeServerUrlTil.error = getString(R.string.login_error_invalid_home_server)
}
else -> {
- if (serverUrl.startsWith("http").not()) {
- serverUrl = "https://$serverUrl"
- }
loginServerUrlFormHomeServerUrl.setText(serverUrl)
loginViewModel.handle(LoginAction.UpdateHomeServer(serverUrl))
}
diff --git a/vector/src/main/java/im/vector/riotx/features/login/terms/LocalizedFlowDataLoginTermsChecked.kt b/vector/src/main/java/im/vector/riotx/features/login/terms/LocalizedFlowDataLoginTermsChecked.kt
index 52aaa9d4a4..6659d65dd6 100644
--- a/vector/src/main/java/im/vector/riotx/features/login/terms/LocalizedFlowDataLoginTermsChecked.kt
+++ b/vector/src/main/java/im/vector/riotx/features/login/terms/LocalizedFlowDataLoginTermsChecked.kt
@@ -16,7 +16,7 @@
package im.vector.riotx.features.login.terms
-import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
+import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
data class LocalizedFlowDataLoginTermsChecked(val localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms,
var checked: Boolean = false)
diff --git a/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt
index 09746adc87..88f4fc2f5f 100755
--- a/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt
@@ -31,7 +31,7 @@ import im.vector.riotx.features.login.LoginAction
import im.vector.riotx.features.login.LoginViewState
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_login_terms.*
-import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
+import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
import javax.inject.Inject
@Parcelize
diff --git a/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsViewState.kt b/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsViewState.kt
index 104ea88daa..77293fbef6 100644
--- a/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsViewState.kt
+++ b/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsViewState.kt
@@ -17,7 +17,7 @@
package im.vector.riotx.features.login.terms
import com.airbnb.mvrx.MvRxState
-import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
+import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
data class LoginTermsViewState(
val localizedFlowDataLoginTermsChecked: List
diff --git a/vector/src/main/java/im/vector/riotx/features/login/terms/PolicyController.kt b/vector/src/main/java/im/vector/riotx/features/login/terms/PolicyController.kt
index c301463c2a..42ed87a280 100644
--- a/vector/src/main/java/im/vector/riotx/features/login/terms/PolicyController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/login/terms/PolicyController.kt
@@ -18,7 +18,7 @@ package im.vector.riotx.features.login.terms
import android.view.View
import com.airbnb.epoxy.TypedEpoxyController
-import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
+import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
import javax.inject.Inject
class PolicyController @Inject constructor() : TypedEpoxyController>() {
diff --git a/vector/src/main/java/im/vector/riotx/features/login/terms/converter.kt b/vector/src/main/java/im/vector/riotx/features/login/terms/converter.kt
index c9e6dcf3fd..d28d12eee8 100644
--- a/vector/src/main/java/im/vector/riotx/features/login/terms/converter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/login/terms/converter.kt
@@ -17,7 +17,7 @@
package im.vector.riotx.features.login.terms
import im.vector.matrix.android.api.auth.registration.TermPolicies
-import org.matrix.androidsdk.rest.model.login.LocalizedFlowDataLoginTerms
+import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
/**
* This method extract the policies from the login terms parameter, regarding the user language.
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 ac725eb850..b2213eb223 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
@@ -23,8 +23,10 @@ import android.view.View
import androidx.core.app.ActivityOptionsCompat
import androidx.core.app.TaskStackBuilder
import androidx.core.view.ViewCompat
+import androidx.fragment.app.Fragment
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
+import im.vector.matrix.android.api.session.terms.TermsService
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder
@@ -52,6 +54,7 @@ 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 im.vector.riotx.features.terms.ReviewTermsActivity
import javax.inject.Inject
import javax.inject.Singleton
@@ -207,6 +210,11 @@ class DefaultNavigator @Inject constructor(
}
}
+ override fun openTerms(fragment: Fragment, serviceType: TermsService.ServiceType, baseUrl: String, token: String?, requestCode: Int) {
+ val intent = ReviewTermsActivity.intent(fragment.requireContext(), serviceType, baseUrl, token)
+ fragment.startActivityForResult(intent, requestCode)
+ }
+
private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) {
if (buildTask) {
val stackBuilder = TaskStackBuilder.create(context)
diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt
index cc8e7cac34..07ec0e4ca2 100644
--- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt
+++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt
@@ -19,10 +19,13 @@ package im.vector.riotx.features.navigation
import android.app.Activity
import android.content.Context
import android.view.View
+import androidx.fragment.app.Fragment
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
+import im.vector.matrix.android.api.session.terms.TermsService
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.features.settings.VectorSettingsActivity
import im.vector.riotx.features.share.SharedData
+import im.vector.riotx.features.terms.ReviewTermsActivity
interface Navigator {
@@ -67,4 +70,10 @@ interface Navigator {
fun openRoomProfile(context: Context, roomId: String)
fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem)
+
+ fun openTerms(fragment: Fragment,
+ serviceType: TermsService.ServiceType,
+ baseUrl: String,
+ token: String?,
+ requestCode: Int = ReviewTermsActivity.TERMS_REQUEST_CODE)
}
diff --git a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt
index 7d58c4aacc..a001567635 100755
--- a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt
@@ -31,9 +31,9 @@ import im.vector.riotx.BuildConfig
import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.extensions.toOnOff
-import im.vector.riotx.core.utils.getDeviceLocale
import im.vector.riotx.features.settings.VectorLocale
import im.vector.riotx.features.settings.VectorPreferences
+import im.vector.riotx.features.settings.locale.SystemLocaleProvider
import im.vector.riotx.features.themes.ThemeUtils
import im.vector.riotx.features.version.VersionProvider
import okhttp3.Call
@@ -58,10 +58,13 @@ import javax.inject.Singleton
* BugReporter creates and sends the bug reports.
*/
@Singleton
-class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
- private val versionProvider: VersionProvider,
- private val vectorPreferences: VectorPreferences,
- private val vectorFileLogger: VectorFileLogger) {
+class BugReporter @Inject constructor(
+ private val activeSessionHolder: ActiveSessionHolder,
+ private val versionProvider: VersionProvider,
+ private val vectorPreferences: VectorPreferences,
+ private val vectorFileLogger: VectorFileLogger,
+ private val systemLocaleProvider: SystemLocaleProvider
+) {
var inMultiWindowMode = false
companion object {
@@ -209,7 +212,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
activeSessionHolder.getSafeActiveSession()?.let { session ->
userId = session.myUserId
- deviceId = session.sessionParams.credentials.deviceId ?: "undefined"
+ deviceId = session.sessionParams.deviceId ?: "undefined"
olmVersion = session.cryptoService().getCryptoVersion(context, true)
}
@@ -240,7 +243,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
+ Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME)
.addFormDataPart("locale", Locale.getDefault().toString())
.addFormDataPart("app_language", VectorLocale.applicationLocale.toString())
- .addFormDataPart("default_app_language", getDeviceLocale(context).toString())
+ .addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString())
.addFormDataPart("theme", ThemeUtils.getApplicationTheme(context))
val buildNumber = context.getString(R.string.build_number)
diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt
index e466c2311f..a75479275b 100644
--- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt
@@ -71,7 +71,7 @@ class PublicRoomsFragment @Inject constructor(
}
.disposeOnDestroyView()
- publicRoomsCreateNewRoom.setOnClickListener {
+ publicRoomsCreateNewRoom.debouncedClicks {
sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom)
}
diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt
index 827db96783..7956f2fd9e 100644
--- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt
@@ -46,7 +46,7 @@ class CreateRoomFragment @Inject constructor(private val createRoomController: C
vectorBaseActivity.setSupportActionBar(createRoomToolbar)
sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
setupRecyclerView()
- createRoomClose.setOnClickListener {
+ createRoomClose.debouncedClicks {
sharedActionViewModel.post(RoomDirectorySharedAction.Back)
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/FontScale.kt b/vector/src/main/java/im/vector/riotx/features/settings/FontScale.kt
index a9e797ba7a..47c438695c 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/FontScale.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/FontScale.kt
@@ -17,7 +17,7 @@
package im.vector.riotx.features.settings
import android.content.Context
-import android.content.res.Configuration
+import androidx.annotation.StringRes
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import im.vector.riotx.R
@@ -29,124 +29,59 @@ object FontScale {
// Key for the SharedPrefs
private const val APPLICATION_FONT_SCALE_KEY = "APPLICATION_FONT_SCALE_KEY"
- // Possible values for the SharedPrefs
- private const val FONT_SCALE_TINY = "FONT_SCALE_TINY"
- private const val FONT_SCALE_SMALL = "FONT_SCALE_SMALL"
- private const val FONT_SCALE_NORMAL = "FONT_SCALE_NORMAL"
- private const val FONT_SCALE_LARGE = "FONT_SCALE_LARGE"
- private const val FONT_SCALE_LARGER = "FONT_SCALE_LARGER"
- private const val FONT_SCALE_LARGEST = "FONT_SCALE_LARGEST"
- private const val FONT_SCALE_HUGE = "FONT_SCALE_HUGE"
-
- private val fontScaleToPrefValue = mapOf(
- 0.70f to FONT_SCALE_TINY,
- 0.85f to FONT_SCALE_SMALL,
- 1.00f to FONT_SCALE_NORMAL,
- 1.15f to FONT_SCALE_LARGE,
- 1.30f to FONT_SCALE_LARGER,
- 1.45f to FONT_SCALE_LARGEST,
- 1.60f to FONT_SCALE_HUGE
+ data class FontScaleValue(
+ val index: Int,
+ // Possible values for the SharedPrefs
+ val preferenceValue: String,
+ val scale: Float,
+ @StringRes
+ val nameResId: Int
)
- private val prefValueToNameResId = mapOf(
- FONT_SCALE_TINY to R.string.tiny,
- FONT_SCALE_SMALL to R.string.small,
- FONT_SCALE_NORMAL to R.string.normal,
- FONT_SCALE_LARGE to R.string.large,
- FONT_SCALE_LARGER to R.string.larger,
- FONT_SCALE_LARGEST to R.string.largest,
- FONT_SCALE_HUGE to R.string.huge
+ private val fontScaleValues = listOf(
+ FontScaleValue(0, "FONT_SCALE_TINY", 0.70f, R.string.tiny),
+ FontScaleValue(1, "FONT_SCALE_SMALL", 0.85f, R.string.small),
+ FontScaleValue(2, "FONT_SCALE_NORMAL", 1.00f, R.string.normal),
+ FontScaleValue(3, "FONT_SCALE_LARGE", 1.15f, R.string.large),
+ FontScaleValue(4, "FONT_SCALE_LARGER", 1.30f, R.string.larger),
+ FontScaleValue(5, "FONT_SCALE_LARGEST", 1.45f, R.string.largest),
+ FontScaleValue(6, "FONT_SCALE_HUGE", 1.60f, R.string.huge)
)
+ private val normalFontScaleValue = fontScaleValues[2]
+
/**
* Get the font scale value from SharedPrefs. Init the SharedPrefs if necessary
*
- * @return the font scale
+ * @return the font scale value
*/
- fun getFontScalePrefValue(context: Context): String {
+ fun getFontScaleValue(context: Context): FontScaleValue {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
- var scalePreferenceValue: String
- if (APPLICATION_FONT_SCALE_KEY !in preferences) {
+ return if (APPLICATION_FONT_SCALE_KEY !in preferences) {
val fontScale = context.resources.configuration.fontScale
- scalePreferenceValue = FONT_SCALE_NORMAL
-
- if (fontScaleToPrefValue.containsKey(fontScale)) {
- scalePreferenceValue = fontScaleToPrefValue[fontScale] as String
- }
-
- preferences.edit {
- putString(APPLICATION_FONT_SCALE_KEY, scalePreferenceValue)
- }
+ (fontScaleValues.firstOrNull { it.scale == fontScale } ?: normalFontScaleValue)
+ .also { preferences.edit { putString(APPLICATION_FONT_SCALE_KEY, it.preferenceValue) } }
} else {
- scalePreferenceValue = preferences.getString(APPLICATION_FONT_SCALE_KEY, FONT_SCALE_NORMAL)!!
+ val pref = preferences.getString(APPLICATION_FONT_SCALE_KEY, null)
+ fontScaleValues.firstOrNull { it.preferenceValue == pref } ?: normalFontScaleValue
}
+ }
- return scalePreferenceValue
+ fun updateFontScale(context: Context, index: Int) {
+ fontScaleValues.getOrNull(index)?.let {
+ saveFontScaleValue(context, it)
+ }
}
/**
- * Provides the font scale value
+ * Store the font scale vale
*
- * @return the font scale
+ * @param fontScaleValue the font scale value to store
*/
- fun getFontScale(context: Context): Float {
- val fontScale = getFontScalePrefValue(context)
-
- if (fontScaleToPrefValue.containsValue(fontScale)) {
- for ((key, value) in fontScaleToPrefValue) {
- if (value == fontScale) {
- return key
- }
- }
- }
-
- return 1.0f
- }
-
- /**
- * Provides the font scale description
- *
- * @return the font description
- */
- fun getFontScaleDescription(context: Context): String {
- val fontScale = getFontScalePrefValue(context)
-
- return if (prefValueToNameResId.containsKey(fontScale)) {
- context.getString(prefValueToNameResId[fontScale] as Int)
- } else context.getString(R.string.normal)
- }
-
- /**
- * Update the font size from the locale description.
- *
- * @param fontScaleDescription the font scale description
- */
- fun updateFontScale(context: Context, fontScaleDescription: String) {
- for ((key, value) in prefValueToNameResId) {
- if (context.getString(value) == fontScaleDescription) {
- saveFontScale(context, key)
- }
- }
-
- val config = Configuration(context.resources.configuration)
- config.fontScale = getFontScale(context)
- @Suppress("DEPRECATION")
- context.resources.updateConfiguration(config, context.resources.displayMetrics)
- }
-
- /**
- * Save the new font scale
- *
- * @param scaleValue the text scale
- */
- fun saveFontScale(context: Context, scaleValue: String) {
- if (scaleValue.isNotEmpty()) {
- PreferenceManager.getDefaultSharedPreferences(context)
- .edit {
- putString(APPLICATION_FONT_SCALE_KEY, scaleValue)
- }
- }
+ private fun saveFontScaleValue(context: Context, fontScaleValue: FontScaleValue) {
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit { putString(APPLICATION_FONT_SCALE_KEY, fontScaleValue.preferenceValue) }
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorLocale.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorLocale.kt
index e1a89ab3c4..a4ccfdba47 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorLocale.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorLocale.kt
@@ -19,13 +19,11 @@ package im.vector.riotx.features.settings
import android.content.Context
import android.content.res.Configuration
import android.os.Build
-import androidx.preference.PreferenceManager
import androidx.core.content.edit
-import im.vector.riotx.BuildConfig
+import androidx.preference.PreferenceManager
import im.vector.riotx.R
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.Locale
@@ -41,10 +39,9 @@ object VectorLocale {
private val defaultLocale = Locale("en", "US")
/**
- * The supported application languages
+ * The cache of supported application languages
*/
- var supportedLocales = ArrayList()
- private set
+ private val supportedLocales = mutableListOf()
/**
* Provides the current application locale
@@ -52,10 +49,13 @@ object VectorLocale {
var applicationLocale = defaultLocale
private set
+ lateinit var context: Context
+
/**
* Init this object
*/
fun init(context: Context) {
+ this.context = context
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
if (preferences.contains(APPLICATION_LOCALE_LANGUAGE_KEY)) {
@@ -72,19 +72,14 @@ object VectorLocale {
applicationLocale = defaultLocale
}
- saveApplicationLocale(context, applicationLocale)
- }
-
- // init the known locales in background, using kotlin coroutines
- GlobalScope.launch(Dispatchers.IO) {
- initApplicationLocales(context)
+ saveApplicationLocale(applicationLocale)
}
}
/**
* Save the new application locale.
*/
- fun saveApplicationLocale(context: Context, locale: Locale) {
+ fun saveApplicationLocale(locale: Locale) {
applicationLocale = locale
PreferenceManager.getDefaultSharedPreferences(context).edit {
@@ -144,6 +139,7 @@ object VectorLocale {
} else {
val resources = context.resources
val conf = resources.configuration
+
@Suppress("DEPRECATION")
val savedLocale = conf.locale
@Suppress("DEPRECATION")
@@ -165,11 +161,9 @@ object VectorLocale {
}
/**
- * Provides the supported application locales list
- *
- * @param context the context
+ * Init the supported application locales list
*/
- private fun initApplicationLocales(context: Context) {
+ private fun initApplicationLocales() {
val knownLocalesSet = HashSet>()
try {
@@ -195,9 +189,7 @@ object VectorLocale {
)
}
- supportedLocales.clear()
-
- knownLocalesSet.mapTo(supportedLocales) { (language, country, script) ->
+ val list = knownLocalesSet.map { (language, country, script) ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Locale.Builder()
.setLanguage(language)
@@ -208,9 +200,11 @@ object VectorLocale {
Locale(language, country)
}
}
+ // sort by human display names
+ .sortedBy { localeToLocalisedString(it).toLowerCase(it) }
- // sort by human display names
- supportedLocales.sortWith(Comparator { lhs, rhs -> localeToLocalisedString(lhs).compareTo(localeToLocalisedString(rhs)) })
+ supportedLocales.clear()
+ supportedLocales.addAll(list)
}
/**
@@ -235,22 +229,39 @@ object VectorLocale {
append(locale.getDisplayCountry(locale))
append(")")
}
-
- // In debug mode, also display information about the locale in the current locale.
- if (BuildConfig.DEBUG) {
- append("\n[")
- append(locale.displayLanguage)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && locale.script != "Latn") {
- append(" - ")
- append(locale.displayScript)
- }
- if (locale.displayCountry.isNotEmpty()) {
- append(" (")
- append(locale.displayCountry)
- append(")")
- }
- append("]")
- }
}
}
+
+ /**
+ * Information about the locale in the current locale
+ *
+ * @param locale the locale to get info from
+ * @return the string
+ */
+ fun localeToLocalisedStringInfo(locale: Locale): String {
+ return buildString {
+ append("[")
+ append(locale.displayLanguage)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && locale.script != "Latn") {
+ append(" - ")
+ append(locale.displayScript)
+ }
+ if (locale.displayCountry.isNotEmpty()) {
+ append(" (")
+ append(locale.displayCountry)
+ append(")")
+ }
+ append("]")
+ }
+ }
+
+ suspend fun getSupportedLocales(): List {
+ if (supportedLocales.isEmpty()) {
+ // init the known locales in background
+ withContext(Dispatchers.IO) {
+ initApplicationLocales()
+ }
+ }
+ return supportedLocales
+ }
}
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 c995c4d986..1455e2f8d8 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
@@ -88,6 +88,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
private const val SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY = "SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY"
private const val SETTINGS_12_24_TIMESTAMPS_KEY = "SETTINGS_12_24_TIMESTAMPS_KEY"
private const val SETTINGS_SHOW_READ_RECEIPTS_KEY = "SETTINGS_SHOW_READ_RECEIPTS_KEY"
+ private const val SETTINGS_SHOW_REDACTED_KEY = "SETTINGS_SHOW_REDACTED_KEY"
private const val SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY = "SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY"
private const val SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY = "SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY"
private const val SETTINGS_VIBRATE_ON_MENTION_KEY = "SETTINGS_VIBRATE_ON_MENTION_KEY"
@@ -625,6 +626,15 @@ class VectorPreferences @Inject constructor(private val context: Context) {
return defaultPrefs.getBoolean(SETTINGS_SHOW_READ_RECEIPTS_KEY, true)
}
+ /**
+ * Tells if the redacted message should be shown
+ *
+ * @return true if the redacted should be shown
+ */
+ fun showRedactedMessages(): Boolean {
+ return defaultPrefs.getBoolean(SETTINGS_SHOW_REDACTED_KEY, true)
+ }
+
/**
* Tells if the help on room list should be shown
*
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt
index 802cf7b33f..dfa88d9b87 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt
@@ -79,6 +79,9 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
private val mPasswordPreference by lazy {
findPreference(VectorPreferences.SETTINGS_CHANGE_PASSWORD_PREFERENCE_KEY)!!
}
+ private val mIdentityServerPreference by lazy {
+ findPreference(VectorPreferences.SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY)!!
+ }
// Local contacts
private val mContactSettingsCategory by lazy {
@@ -130,6 +133,10 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
// Unfortunately, this is not supported in lib v7
// it.editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+ it.setOnPreferenceClickListener {
+ notImplemented()
+ true
+ }
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
notImplemented()
@@ -162,11 +169,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
// home server
findPreference