1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 00:02:48 +02:00

Compare commits

...

50 Commits

Author SHA1 Message Date
Benoit Marty
bb33a92d27 Merge branch 'release/1.1.0' 2021-02-19 19:30:56 +01:00
Benoit Marty
809c0e720e Prepare release 1.1.0 2021-02-19 19:30:38 +01:00
Benoit Marty
371251c994 Merge pull request #2849 from vector-im/feature/bma/fileLogger
Cleanup VectorFileLogger.kt
2021-02-19 19:24:24 +01:00
Benoit Marty
616b02cea8 Cleanup VectorFileLogger.kt 2021-02-19 18:17:53 +01:00
Benoit Marty
e12cbf92c3 Merge pull request #2836 from vector-im/feature/bma/fix_redirection
Fix redirection
2021-02-19 14:43:14 +01:00
Benoit Marty
1294d211d6 Merge branch 'develop' into feature/bma/fix_redirection 2021-02-19 14:43:01 +01:00
Benoit Marty
e8026c6d3f Naming convention 2021-02-19 13:53:38 +01:00
Benoit Marty
9614d55612 Merge pull request #2844 from vector-im/feature/bma/fix_account_deactivation
Fix account deactivation crash
2021-02-19 13:47:30 +01:00
Benoit Marty
8c1b99586b Use resumeWithException() facility (no other change) 2021-02-18 20:44:28 +01:00
Benoit Marty
544c1e4a6a Also fix the problem of cleanup too early (or done several times in case of bad password) for DefaultFinalizeAddingThreePidTask 2021-02-18 19:23:31 +01:00
Benoit Marty
2271ddacf8 Remove unused task and API 2021-02-18 19:05:42 +01:00
Benoit Marty
9443b80811 Fix test: ensure the session is open 2021-02-18 18:46:23 +01:00
Benoit Marty
5afdc81ce0 Moar cleanup 2021-02-18 18:43:48 +01:00
Benoit Marty
2df9b43abc Make the code a bit easier to understand (no change) 2021-02-18 18:42:55 +01:00
Benoit Marty
73e93e7d3d Doc and method signature 2021-02-18 17:56:05 +01:00
Benoit Marty
c76ced68e0 Make the code a bit easier to understand (no other change) 2021-02-18 17:47:11 +01:00
Benoit Marty
b9f5863b53 Avoid long lines 2021-02-18 16:14:02 +01:00
Benoit Marty
dea76fd81b Fix test compilation 2021-02-18 15:36:51 +01:00
Benoit Marty
fd94536118 Merge pull request #2830 from vector-im/feature/fga/fix_some_voip_issues
Feature/fga/fix some voip issues
2021-02-17 19:01:08 +01:00
Benoit Marty
61ea78a9d9 Merge branch 'feature/bma/improve_script' into develop 2021-02-17 19:00:14 +01:00
Benoit Marty
a4b5f79e8f Add capability to download only one artifact 2021-02-17 18:59:40 +01:00
Benoit Marty
fc1c2bc2c0 Merge pull request #2828 from vector-im/feature/bma/color_step_1
First step about color cleanup
2021-02-17 18:27:44 +01:00
Benoit Marty
51fd45d317 typo and doc 2021-02-17 18:18:36 +01:00
Benoit Marty
fcee1f1150 Fix glitch on screen transition 2021-02-17 18:18:36 +01:00
Benoit Marty
0e322630f1 Ignore url override from credential if it is not valid (#2822) 2021-02-17 18:18:36 +01:00
Benoit Marty
d19cedef88 Less code 2021-02-17 18:18:36 +01:00
Benoit Marty
6aa5dc992d Migrate AuthenticationService API to coroutines (#2449) 2021-02-17 18:18:36 +01:00
Benoit Marty
c787de75f5 Cleaup 2021-02-17 10:36:33 +01:00
Benoit Marty
a34c072c48 Rename interface 2021-02-17 10:28:49 +01:00
Benoit Marty
373586c23e Cleanup 2021-02-17 10:25:06 +01:00
ganfra
754dec949b Remove useless log 2021-02-16 20:45:22 +01:00
ganfra
776ebce497 Merge branch 'develop' into feature/fga/fix_some_voip_issues 2021-02-16 16:06:12 +01:00
ganfra
79acf1cc42 Fix copyright 2021-02-16 15:57:23 +01:00
Benoit Marty
b0ea7cecb5 Move all colors to the same file and rename it 2021-02-16 10:37:17 +01:00
Benoit Marty
d351c2cabb No need for VectorStyles 2021-02-16 10:34:06 +01:00
Benoit Marty
aab70e14fc Cleanup logs (fix lint) 2021-02-16 10:33:52 +01:00
ganfra
3170d4428c VoIP: extract the PSTNProtocoltChecker to the SDK 2021-02-15 19:34:37 +01:00
ganfra
5e3e5d2648 Clean code 2021-02-15 15:35:11 +01:00
ganfra
96b02d3154 VoIP: PSTN support was done too early 2021-02-15 14:49:45 +01:00
ganfra
109a9e816b Timeline: fix crash on mutable iterator 2021-02-15 12:03:50 +01:00
ganfra
61373b8b51 Timeline: start refactoring the Interceptor mechanism 2021-02-12 19:13:45 +01:00
Benoit Marty
fa710ff601 Merge branch 'release/1.0.17' 2021-02-09 21:07:35 +01:00
Benoit Marty
391ddf1925 Merge branch 'release/1.0.16' 2021-02-04 13:25:23 +01:00
Benoit Marty
ce6d4c4a64 Merge branch 'release/1.0.15' 2021-02-03 17:08:27 +01:00
Benoit Marty
7c013de7b9 Merge branch 'release/1.0.14' 2021-01-15 11:13:49 +01:00
Benoit Marty
0412b87ad1 Merge branch 'release/1.0.13' 2020-12-18 10:59:54 +01:00
Benoit Marty
1946058c8e Merge branch 'release/1.0.12' 2020-12-15 14:16:17 +01:00
Benoit Marty
dffdcfe1e4 Merge branch 'release/1.0.11' 2020-11-27 20:40:03 +01:00
Benoit Marty
bba167d4ea Merge branch 'release/1.0.10' 2020-11-04 15:58:45 +01:00
Benoit Marty
9a5e71f391 Merge branch 'hotfix/bma/versionCode' 2020-10-19 12:31:35 +02:00
63 changed files with 1458 additions and 1392 deletions

View File

@@ -26,6 +26,7 @@
<w>pkcs</w>
<w>previewable</w>
<w>previewables</w>
<w>pstn</w>
<w>riotx</w>
<w>signin</w>
<w>signout</w>

View File

@@ -1,4 +1,4 @@
Changes in Element 1.0.18 (2021-XX-XX)
Changes in Element 1.1.0 (2021-02-19)
===================================================
Features ✨:
@@ -14,22 +14,16 @@ Bugfix 🐛:
- VoIP : fix audio devices output
- Fix crash after initial sync on Dendrite
- Fix crash reported by PlayStore (#2707)
Translations 🗣:
-
- Ignore url override from credential if it is not valid (#2822)
- Fix crash when deactivating an account
SDK API changes ⚠️:
-
Build 🧱:
-
Test:
-
- Migrate AuthenticationService API to coroutines (#2449)
Other changes:
- New Dev Tools panel for developers
- Fix typos in CHANGES.md (#2811)
- Colors rework: first step: merge file `colors_riot.xml` to file `colors_riotx.xml` and rename the file to `colors.xml`
Changes in Element 1.0.17 (2021-02-09)
===================================================

View File

@@ -0,0 +1,2 @@
Main changes in this version: VoIP (audio and video calls in DM) improvement and bug fixes!
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.0

View File

@@ -26,15 +26,12 @@ import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.TestConstants
import org.matrix.android.sdk.common.TestMatrixCallback
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
@@ -46,12 +43,13 @@ class DeactivateAccountTest : InstrumentedTest {
@Test
fun deactivateAccountTest() {
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false))
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
// Deactivate the account
commonTestHelper.runBlockingTest {
session.deactivateAccount(
object : UserInteractiveAuthInterceptor {
eraseAllData = false,
userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(
UserPasswordAuth(
@@ -61,7 +59,8 @@ class DeactivateAccountTest : InstrumentedTest {
)
)
}
}, false)
}
)
}
// Try to login on the previous account, it will fail (M_USER_DEACTIVATED)
@@ -75,23 +74,23 @@ class DeactivateAccountTest : InstrumentedTest {
// Try to create an account with the deactivate account user id, it will fail (M_USER_IN_USE)
val hs = commonTestHelper.createHomeServerConfig()
commonTestHelper.doSync<LoginFlowResult> {
commonTestHelper.matrix.authenticationService.getLoginFlow(hs, it)
commonTestHelper.runBlockingTest {
commonTestHelper.matrix.authenticationService.getLoginFlow(hs)
}
var accountCreationError: Throwable? = null
commonTestHelper.waitWithLatch {
commonTestHelper.matrix.authenticationService
.getRegistrationWizard()
.createAccount(session.myUserId.substringAfter("@").substringBefore(":"),
TestConstants.PASSWORD,
null,
object : TestMatrixCallback<RegistrationResult>(it, false) {
override fun onFailure(failure: Throwable) {
accountCreationError = failure
super.onFailure(failure)
}
})
commonTestHelper.runBlockingTest {
try {
commonTestHelper.matrix.authenticationService
.getRegistrationWizard()
.createAccount(
session.myUserId.substringAfter("@").substringBefore(":"),
TestConstants.PASSWORD,
null
)
} catch (failure: Throwable) {
accountCreationError = failure
}
}
// Test the error

View File

@@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.EventType
@@ -210,22 +209,21 @@ class CommonTestHelper(context: Context) {
sessionTestParams: SessionTestParams): Session {
val hs = createHomeServerConfig()
doSync<LoginFlowResult> {
matrix.authenticationService
.getLoginFlow(hs, it)
runBlockingTest {
matrix.authenticationService.getLoginFlow(hs)
}
doSync<RegistrationResult>(timeout = 60_000) {
runBlockingTest(timeout = 60_000) {
matrix.authenticationService
.getRegistrationWizard()
.createAccount(userName, password, null, it)
.createAccount(userName, password, null)
}
// Perform dummy step
val registrationResult = doSync<RegistrationResult>(timeout = 60_000) {
val registrationResult = runBlockingTest(timeout = 60_000) {
matrix.authenticationService
.getRegistrationWizard()
.dummy(it)
.dummy()
}
assertTrue(registrationResult is RegistrationResult.Success)
@@ -249,15 +247,14 @@ class CommonTestHelper(context: Context) {
sessionTestParams: SessionTestParams): Session {
val hs = createHomeServerConfig()
doSync<LoginFlowResult> {
matrix.authenticationService
.getLoginFlow(hs, it)
runBlockingTest {
matrix.authenticationService.getLoginFlow(hs)
}
val session = doSync<Session> {
val session = runBlockingTest {
matrix.authenticationService
.getLoginWizard()
.login(userName, password, "myDevice", it)
.login(userName, password, "myDevice")
}
if (sessionTestParams.withInitialSync) {
@@ -277,21 +274,19 @@ class CommonTestHelper(context: Context) {
password: String): Throwable {
val hs = createHomeServerConfig()
doSync<LoginFlowResult> {
matrix.authenticationService
.getLoginFlow(hs, it)
runBlockingTest {
matrix.authenticationService.getLoginFlow(hs)
}
var requestFailure: Throwable? = null
waitWithLatch { latch ->
matrix.authenticationService
.getLoginWizard()
.login(userName, password, "myDevice", object : TestMatrixCallback<Session>(latch, onlySuccessful = false) {
override fun onFailure(failure: Throwable) {
requestFailure = failure
super.onFailure(failure)
}
})
runBlockingTest {
try {
matrix.authenticationService
.getLoginWizard()
.login(userName, password, "myDevice")
} catch (failure: Throwable) {
requestFailure = failure
}
}
assertNotNull(requestFailure)

View File

@@ -61,7 +61,7 @@ class SearchMessagesTest : InstrumentedTest {
2)
run {
var lock = CountDownLatch(1)
val lock = CountDownLatch(1)
val eventListener = commonTestHelper.createEventListener(lock) { snapshot ->
snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2
@@ -70,7 +70,6 @@ class SearchMessagesTest : InstrumentedTest {
aliceTimeline.addListener(eventListener)
commonTestHelper.await(lock)
lock = CountDownLatch(1)
val data = commonTestHelper.runBlockingTest {
aliceSession
.searchService()

View File

@@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.auth
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
@@ -24,7 +23,6 @@ import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
/**
* This interface defines methods to authenticate or to create an account to a matrix server.
@@ -32,14 +30,14 @@ import org.matrix.android.sdk.api.util.Cancelable
interface AuthenticationService {
/**
* Request the supported login flows for this homeserver.
* This is the first method to call to be able to get a wizard to login or the create an account
* This is the first method to call to be able to get a wizard to login or to create an account
*/
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable
suspend fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult
/**
* Request the supported login flows for the corresponding sessionId.
*/
fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable
suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult
/**
* Get a SSO url
@@ -69,12 +67,12 @@ interface AuthenticationService {
/**
* Cancel pending login or pending registration
*/
fun cancelPendingLoginOrRegistration()
suspend fun cancelPendingLoginOrRegistration()
/**
* Reset all pending settings, including current HomeServerConnectionConfig
*/
fun reset()
suspend fun reset()
/**
* Check if there is an authenticated [Session].
@@ -91,24 +89,21 @@ interface AuthenticationService {
/**
* Create a session after a SSO successful login
*/
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials,
callback: MatrixCallback<Session>): Cancelable
suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials): Session
/**
* Perform a wellknown request, using the domain from the matrixId
*/
fun getWellKnownData(matrixId: String,
homeServerConnectionConfig: HomeServerConnectionConfig?,
callback: MatrixCallback<WellknownResult>): Cancelable
suspend fun getWellKnownData(matrixId: String,
homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult
/**
* Authenticate with a matrixId and a password
* Usually call this after a successful call to getWellKnownData()
*/
fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
matrixId: String,
password: String,
initialDeviceName: String,
callback: MatrixCallback<Session>): Cancelable
suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
matrixId: String,
password: String,
initialDeviceName: String): Session
}

View File

@@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.auth.login
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
@@ -29,26 +28,23 @@ interface LoginWizard {
* @param callback the matrix callback on which you'll receive the result of authentication.
* @return a [Cancelable]
*/
fun login(login: String,
password: String,
deviceName: String,
callback: MatrixCallback<Session>): Cancelable
suspend fun login(login: String,
password: String,
deviceName: String): Session
/**
* Exchange a login token to an access token
*/
fun loginWithToken(loginToken: String,
callback: MatrixCallback<Session>): Cancelable
suspend fun loginWithToken(loginToken: String): Session
/**
* Reset user password
*/
fun resetPassword(email: String,
newPassword: String,
callback: MatrixCallback<Unit>): Cancelable
suspend fun resetPassword(email: String,
newPassword: String)
/**
* Confirm the new password, once the user has checked his email
* Confirm the new password, once the user has checked their email
*/
fun resetPasswordMailConfirmed(callback: MatrixCallback<Unit>): Cancelable
suspend fun resetPasswordMailConfirmed()
}

View File

@@ -16,28 +16,25 @@
package org.matrix.android.sdk.api.auth.registration
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable
interface RegistrationWizard {
fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun getRegistrationFlow(): RegistrationResult
fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?, callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?): RegistrationResult
fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun performReCaptcha(response: String): RegistrationResult
fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun acceptTerms(): RegistrationResult
fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun dummy(): RegistrationResult
fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult
fun sendAgainThreePid(callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun sendAgainThreePid(): RegistrationResult
fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun handleValidateThreePid(code: String): RegistrationResult
fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult
val currentThreePid: String?

View File

@@ -53,22 +53,24 @@ fun Throwable.isInvalidUIAAuth(): Boolean {
* Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
*/
fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
return if (this is Failure.OtherServerError && this.httpCode == 401) {
return if (this is Failure.OtherServerError && httpCode == 401) {
tryOrNull {
MoshiProvider.providesMoshi()
.adapter(RegistrationFlowResponse::class.java)
.fromJson(this.errorBody)
.fromJson(errorBody)
}
} else if (this is Failure.ServerError && this.httpCode == 401 && this.error.code == MatrixError.M_FORBIDDEN) {
} else if (this is Failure.ServerError && httpCode == 401 && error.code == MatrixError.M_FORBIDDEN) {
// This happens when the submission for this stage was bad (like bad password)
if (this.error.session != null && this.error.flows != null) {
if (error.session != null && error.flows != null) {
RegistrationFlowResponse(
flows = this.error.flows,
session = this.error.session,
completedStages = this.error.completedStages,
params = this.error.params
flows = error.flows,
session = error.session,
completedStages = error.completedStages,
params = error.params
)
} else null
} else {
null
}
} else {
null
}

View File

@@ -27,7 +27,8 @@ interface AccountService {
* @param password Current password.
* @param newPassword New password
*/
suspend fun changePassword(password: String, newPassword: String)
suspend fun changePassword(password: String,
newPassword: String)
/**
* Deactivate the account.
@@ -41,9 +42,10 @@ interface AccountService {
* be shared with any new or unregistered users, but registered users who already have access to these messages will still
* have access to their copy.
*
* @param password the account password
* @param eraseAllData set to true to forget all messages that have been sent. Warning: this will cause future users to see
* an incomplete view of conversations
* @param userInteractiveAuthInterceptor see [UserInteractiveAuthInterceptor]
*/
suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean)
suspend fun deactivateAccount(eraseAllData: Boolean,
userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
}

View File

@@ -16,12 +16,11 @@
package org.matrix.android.sdk.api.session.call
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable
interface CallSignalingService {
fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable
suspend fun getTurnServer(): TurnServerResponse
fun getPSTNProtocolChecker(): PSTNProtocolChecker
/**
* Create an outgoing call

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.call
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.thirdparty.GetThirdPartyProtocolsTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
private const val PSTN_VECTOR_KEY = "im.vector.protocol.pstn"
private const val PSTN_MATRIX_KEY = "m.protocol.pstn"
/**
* This class is responsible for checking if the HS support the PSTN protocol.
* As long as the request succeed, it'll check only once by session.
*/
@SessionScope
class PSTNProtocolChecker @Inject internal constructor(private val taskExecutor: TaskExecutor,
private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask) {
interface Listener {
fun onPSTNSupportUpdated()
}
private var alreadyChecked = AtomicBoolean(false)
private val pstnSupportListeners = mutableListOf<Listener>()
fun addListener(listener: Listener) {
pstnSupportListeners.add(listener)
}
fun removeListener(listener: Listener) {
pstnSupportListeners.remove(listener)
}
var supportedPSTNProtocol: String? = null
private set
fun checkForPSTNSupportIfNeeded() {
if (alreadyChecked.get()) return
taskExecutor.executorScope.checkForPSTNSupport()
}
private fun CoroutineScope.checkForPSTNSupport() = launch {
try {
supportedPSTNProtocol = getSupportedPSTN(3)
alreadyChecked.set(true)
if (supportedPSTNProtocol != null) {
pstnSupportListeners.forEach {
tryOrNull { it.onPSTNSupportUpdated() }
}
}
} catch (failure: Throwable) {
Timber.v("Fail to get supported PSTN, will check again next time.")
}
}
private suspend fun getSupportedPSTN(maxTries: Int): String? {
val thirdPartyProtocols: Map<String, ThirdPartyProtocol> = try {
getThirdPartyProtocolsTask.execute(Unit)
} catch (failure: Throwable) {
if (maxTries == 1) {
throw failure
} else {
// Wait for 10s before trying again
delay(10_000L)
return getSupportedPSTN(maxTries - 1)
}
}
return when {
thirdPartyProtocols.containsKey(PSTN_VECTOR_KEY) -> PSTN_VECTOR_KEY
thirdPartyProtocols.containsKey(PSTN_MATRIX_KEY) -> PSTN_MATRIX_KEY
else -> null
}
}
}

View File

@@ -56,8 +56,6 @@ interface CryptoService {
fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>)
fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>)
fun getCryptoVersion(context: Context, longFormat: Boolean): String
fun isCryptoEnabled(): Boolean

View File

@@ -20,7 +20,9 @@ import android.content.Context
import dagger.Binds
import dagger.Module
import dagger.Provides
import io.realm.RealmConfiguration
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import org.matrix.android.sdk.internal.auth.db.AuthRealmMigration
import org.matrix.android.sdk.internal.auth.db.AuthRealmModule
@@ -32,8 +34,6 @@ import org.matrix.android.sdk.internal.database.RealmKeysUtils
import org.matrix.android.sdk.internal.di.AuthDatabase
import org.matrix.android.sdk.internal.legacy.DefaultLegacySessionImporter
import org.matrix.android.sdk.internal.wellknown.WellknownModule
import io.realm.RealmConfiguration
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import java.io.File
@Module(includes = [WellknownModule::class])
@@ -82,6 +82,9 @@ internal abstract class AuthModule {
@Binds
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
@Binds
abstract fun bindIsValidClientServerApiTask(task: DefaultIsValidClientServerApiTask): IsValidClientServerApiTask
@Binds
abstract fun bindHomeServerHistoryService(service: DefaultHomeServerHistoryService): HomeServerHistoryService
}

View File

@@ -18,10 +18,7 @@ package org.matrix.android.sdk.internal.auth
import android.net.Uri
import dagger.Lazy
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@@ -32,8 +29,6 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.api.util.appendParamToUrl
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
@@ -50,11 +45,6 @@ import org.matrix.android.sdk.internal.network.RetrofitFactory
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory
import org.matrix.android.sdk.internal.network.ssl.UnrecognizedCertificateException
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.toCancelable
import org.matrix.android.sdk.internal.wellknown.GetWellknownTask
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection
@@ -63,14 +53,12 @@ internal class DefaultAuthenticationService @Inject constructor(
@Unauthenticated
private val okHttpClient: Lazy<OkHttpClient>,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager,
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore,
private val getWellknownTask: GetWellknownTask,
private val directLoginTask: DirectLoginTask,
private val taskExecutor: TaskExecutor
private val directLoginTask: DirectLoginTask
) : AuthenticationService {
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
@@ -89,15 +77,11 @@ internal class DefaultAuthenticationService @Inject constructor(
}
}
override fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable {
override suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult {
val homeServerConnectionConfig = sessionParamsStore.get(sessionId)?.homeServerConnectionConfig
?: throw IllegalStateException("Session not found")
return if (homeServerConnectionConfig == null) {
callback.onFailure(IllegalStateException("Session not found"))
NoOpCancellable
} else {
getLoginFlow(homeServerConnectionConfig, callback)
}
return getLoginFlow(homeServerConnectionConfig)
}
override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
@@ -146,70 +130,70 @@ internal class DefaultAuthenticationService @Inject constructor(
?.trim { it == '/' }
}
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
/**
* This is the entry point of the authentication service.
* homeServerConnectionConfig contains a homeserver URL probably entered by the user, which can be a
* valid homeserver API url, the url of Element Web, or anything else.
*/
override suspend fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
pendingSessionData = null
return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
pendingSessionStore.delete()
pendingSessionStore.delete()
val result = runCatching {
getLoginFlowInternal(homeServerConnectionConfig)
}
result.fold(
{
if (it is LoginFlowResult.Success) {
// The homeserver exists and up to date, keep the config
// Homeserver url may have been changed, if it was a Riot url
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = Uri.parse(it.homeServerUrl)
)
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
.also { data -> pendingSessionStore.savePendingSessionData(data) }
}
callback.onSuccess(it)
},
{
if (it is UnrecognizedCertificateException) {
callback.onFailure(Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint))
} else {
callback.onFailure(it)
}
}
)
val result = runCatching {
getLoginFlowInternal(homeServerConnectionConfig)
}
.toCancelable()
return result.fold(
{
if (it is LoginFlowResult.Success) {
// The homeserver exists and up to date, keep the config
// Homeserver url may have been changed, if it was a Riot url
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = Uri.parse(it.homeServerUrl)
)
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
.also { data -> pendingSessionStore.savePendingSessionData(data) }
}
it
},
{
if (it is UnrecognizedCertificateException) {
throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint)
} else {
throw it
}
}
)
}
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
return withContext(coroutineDispatchers.io) {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
val authAPI = buildAuthAPI(homeServerConnectionConfig)
// First check the homeserver version
runCatching {
executeRequest<Versions>(null) {
apiCall = authAPI.versions()
}
// First check the homeserver version
return runCatching {
executeRequest<Versions>(null) {
apiCall = authAPI.versions()
}
.map { versions ->
// Ok, it seems that the homeserver url is valid
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
}
.fold(
{
it
},
{
if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// It's maybe a Riot url?
getRiotDomainLoginFlowInternal(homeServerConnectionConfig)
} else {
throw it
}
}
)
}
.map { versions ->
// Ok, it seems that the homeserver url is valid
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
}
.fold(
{
it
},
{
if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// It's maybe a Riot url?
getRiotDomainLoginFlowInternal(homeServerConnectionConfig)
} else {
throw it
}
}
)
}
private suspend fun getRiotDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
@@ -338,12 +322,9 @@ internal class DefaultAuthenticationService @Inject constructor(
?: let {
pendingSessionData?.homeServerConnectionConfig?.let {
DefaultRegistrationWizard(
buildClient(it),
retrofitFactory,
coroutineDispatchers,
buildAuthAPI(it),
sessionCreator,
pendingSessionStore,
taskExecutor.executorScope
pendingSessionStore
).also {
currentRegistrationWizard = it
}
@@ -359,12 +340,9 @@ internal class DefaultAuthenticationService @Inject constructor(
?: let {
pendingSessionData?.homeServerConnectionConfig?.let {
DefaultLoginWizard(
buildClient(it),
retrofitFactory,
coroutineDispatchers,
buildAuthAPI(it),
sessionCreator,
pendingSessionStore,
taskExecutor.executorScope
pendingSessionStore
).also {
currentLoginWizard = it
}
@@ -372,7 +350,7 @@ internal class DefaultAuthenticationService @Inject constructor(
}
}
override fun cancelPendingLoginOrRegistration() {
override suspend fun cancelPendingLoginOrRegistration() {
currentLoginWizard = null
currentRegistrationWizard = null
@@ -381,61 +359,39 @@ internal class DefaultAuthenticationService @Inject constructor(
pendingSessionData = pendingSessionData?.homeServerConnectionConfig
?.let { PendingSessionData(it) }
.also {
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
if (it == null) {
// Should not happen
pendingSessionStore.delete()
} else {
pendingSessionStore.savePendingSessionData(it)
}
if (it == null) {
// Should not happen
pendingSessionStore.delete()
} else {
pendingSessionStore.savePendingSessionData(it)
}
}
}
override fun reset() {
override suspend fun reset() {
currentLoginWizard = null
currentRegistrationWizard = null
pendingSessionData = null
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
pendingSessionStore.delete()
}
pendingSessionStore.delete()
}
override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials,
callback: MatrixCallback<Session>): Cancelable {
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
createSessionFromSso(credentials, homeServerConnectionConfig)
}
override suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials): Session {
return sessionCreator.createSession(credentials, homeServerConnectionConfig)
}
override fun getWellKnownData(matrixId: String,
homeServerConnectionConfig: HomeServerConnectionConfig?,
callback: MatrixCallback<WellknownResult>): Cancelable {
return getWellknownTask
.configureWith(GetWellknownTask.Params(matrixId, homeServerConnectionConfig)) {
this.callback = callback
}
.executeBy(taskExecutor)
override suspend fun getWellKnownData(matrixId: String,
homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult {
return getWellknownTask.execute(GetWellknownTask.Params(matrixId, homeServerConnectionConfig))
}
override fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
matrixId: String,
password: String,
initialDeviceName: String,
callback: MatrixCallback<Session>): Cancelable {
return directLoginTask
.configureWith(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
private suspend fun createSessionFromSso(credentials: Credentials,
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
sessionCreator.createSession(credentials, homeServerConnectionConfig)
override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
matrixId: String,
password: String,
initialDeviceName: String): Session {
return directLoginTask.execute(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName))
}
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.auth
import dagger.Lazy
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
import org.matrix.android.sdk.internal.di.Unauthenticated
import org.matrix.android.sdk.internal.network.RetrofitFactory
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection
internal interface IsValidClientServerApiTask : Task<IsValidClientServerApiTask.Params, Boolean> {
data class Params(
val homeServerConnectionConfig: HomeServerConnectionConfig
)
}
internal class DefaultIsValidClientServerApiTask @Inject constructor(
@Unauthenticated
private val okHttpClient: Lazy<OkHttpClient>,
private val retrofitFactory: RetrofitFactory
) : IsValidClientServerApiTask {
override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean {
val client = buildClient(params.homeServerConnectionConfig)
val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString()
val authAPI = retrofitFactory.create(client, homeServerUrl)
.create(AuthAPI::class.java)
return try {
executeRequest<LoginFlowResponse>(null) {
apiCall = authAPI.getLoginFlows()
}
// We get a response, so the API is valid
true
} catch (failure: Throwable) {
if (failure is Failure.OtherServerError
&& failure.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// Probably not valid
false
} else {
// Other error
throw failure
}
}
}
private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient {
return okHttpClient.get()
.newBuilder()
.addSocketFactory(homeServerConnectionConfig)
.build()
}
}

View File

@@ -20,6 +20,7 @@ import android.net.Uri
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.SessionManager
import timber.log.Timber
@@ -32,7 +33,8 @@ internal interface SessionCreator {
internal class DefaultSessionCreator @Inject constructor(
private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager,
private val pendingSessionStore: PendingSessionStore
private val pendingSessionStore: PendingSessionStore,
private val isValidClientServerApiTask: IsValidClientServerApiTask
) : SessionCreator {
/**
@@ -43,16 +45,28 @@ internal class DefaultSessionCreator @Inject constructor(
// We can cleanup the pending session params
pendingSessionStore.delete()
val overriddenUrl = credentials.discoveryInformation?.homeServer?.baseURL
// remove trailing "/"
?.trim { it == '/' }
?.takeIf { it.isNotBlank() }
?.also { Timber.d("Overriding homeserver url to $it (will check if valid)") }
?.let { Uri.parse(it) }
?.takeIf {
// Validate the URL, if the configuration is wrong server side, do not override
tryOrNull {
isValidClientServerApiTask.execute(
IsValidClientServerApiTask.Params(
homeServerConnectionConfig.copy(homeServerUri = it)
)
)
.also { Timber.d("Overriding homeserver url: $it") }
} ?: true // In case of other error (no network, etc.), consider it is valid...
}
val sessionParams = SessionParams(
credentials = credentials,
homeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = credentials.discoveryInformation?.homeServer?.baseURL
// remove trailing "/"
?.trim { it == '/' }
?.takeIf { it.isNotBlank() }
?.also { Timber.d("Overriding homeserver url to $it") }
?.let { Uri.parse(it) }
?: homeServerConnectionConfig.homeServerUri,
homeServerUri = overriddenUrl ?: homeServerConnectionConfig.homeServerUri,
identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL
// remove trailing "/"
?.trim { it == '/' }

View File

@@ -17,13 +17,10 @@
package org.matrix.android.sdk.internal.auth.login
import android.util.Patterns
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.internal.auth.AuthAPI
import org.matrix.android.sdk.internal.auth.PendingSessionStore
import org.matrix.android.sdk.internal.auth.SessionCreator
@@ -34,56 +31,19 @@ import org.matrix.android.sdk.internal.auth.db.PendingSessionData
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse
import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask
import org.matrix.android.sdk.internal.network.RetrofitFactory
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
internal class DefaultLoginWizard(
okHttpClient: OkHttpClient,
retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val authAPI: AuthAPI,
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore,
private val coroutineScope: CoroutineScope
private val pendingSessionStore: PendingSessionStore
) : LoginWizard {
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
private val authAPI = retrofitFactory.create(okHttpClient, pendingSessionData.homeServerConnectionConfig.homeServerUri.toString())
.create(AuthAPI::class.java)
override fun login(login: String,
password: String,
deviceName: String,
callback: MatrixCallback<Session>): Cancelable {
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
loginInternal(login, password, deviceName)
}
}
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint
*/
override fun loginWithToken(loginToken: String, callback: MatrixCallback<Session>): Cancelable {
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
val loginParams = TokenLoginParams(
token = loginToken
)
val credentials = executeRequest<Credentials>(null) {
apiCall = authAPI.login(loginParams)
}
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
}
}
private suspend fun loginInternal(login: String,
password: String,
deviceName: String) = withContext(coroutineDispatchers.computation) {
override suspend fun login(login: String,
password: String,
deviceName: String): Session {
val loginParams = if (Patterns.EMAIL_ADDRESS.matcher(login).matches()) {
PasswordLoginParams.thirdPartyIdentifier(ThreePidMedium.EMAIL, login, password, deviceName)
} else {
@@ -93,16 +53,24 @@ internal class DefaultLoginWizard(
apiCall = authAPI.login(loginParams)
}
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
}
override fun resetPassword(email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
resetPasswordInternal(email, newPassword)
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint
*/
override suspend fun loginWithToken(loginToken: String): Session {
val loginParams = TokenLoginParams(
token = loginToken
)
val credentials = executeRequest<Credentials>(null) {
apiCall = authAPI.login(loginParams)
}
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
}
private suspend fun resetPasswordInternal(email: String, newPassword: String) {
override suspend fun resetPassword(email: String, newPassword: String) {
val param = RegisterAddThreePidTask.Params(
RegisterThreePid.Email(email),
pendingSessionData.clientSecret,
@@ -120,21 +88,14 @@ internal class DefaultLoginWizard(
.also { pendingSessionStore.savePendingSessionData(it) }
}
override fun resetPasswordMailConfirmed(callback: MatrixCallback<Unit>): Cancelable {
val safeResetPasswordData = pendingSessionData.resetPasswordData ?: run {
callback.onFailure(IllegalStateException("developer error, no reset password in progress"))
return NoOpCancellable
}
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
resetPasswordMailConfirmedInternal(safeResetPasswordData)
}
}
override suspend fun resetPasswordMailConfirmed() {
val safeResetPasswordData = pendingSessionData.resetPasswordData
?: throw IllegalStateException("developer error, no reset password in progress")
private suspend fun resetPasswordMailConfirmedInternal(resetPasswordData: ResetPasswordData) {
val param = ResetPasswordMailConfirmed.create(
pendingSessionData.clientSecret,
resetPasswordData.addThreePidRegistrationResponse.sid,
resetPasswordData.newPassword
safeResetPasswordData.addThreePidRegistrationResponse.sid,
safeResetPasswordData.newPassword
)
executeRequest<Unit>(null) {

View File

@@ -16,10 +16,7 @@
package org.matrix.android.sdk.internal.auth.registration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
@@ -27,31 +24,22 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.registration.toFlowResult
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.internal.auth.AuthAPI
import org.matrix.android.sdk.internal.auth.PendingSessionStore
import org.matrix.android.sdk.internal.auth.SessionCreator
import org.matrix.android.sdk.internal.auth.db.PendingSessionData
import org.matrix.android.sdk.internal.network.RetrofitFactory
import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
/**
* This class execute the registration request and is responsible to keep the session of interactive authentication
*/
internal class DefaultRegistrationWizard(
private val okHttpClient: OkHttpClient,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
authAPI: AuthAPI,
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore,
private val coroutineScope: CoroutineScope
private val pendingSessionStore: PendingSessionStore
) : RegistrationWizard {
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
private val authAPI = buildAuthAPI()
private val registerTask = DefaultRegisterTask(authAPI)
private val registerAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI)
private val validateCodeTask = DefaultValidateCodeTask(authAPI)
@@ -71,70 +59,54 @@ internal class DefaultRegistrationWizard(
override val isRegistrationStarted: Boolean
get() = pendingSessionData.isRegistrationStarted
override fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable {
override suspend fun getRegistrationFlow(): RegistrationResult {
val params = RegistrationParams()
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
}
return performRegistrationRequest(params)
}
override fun createAccount(userName: String,
password: String,
initialDeviceDisplayName: String?,
callback: MatrixCallback<RegistrationResult>): Cancelable {
override suspend fun createAccount(userName: String,
password: String,
initialDeviceDisplayName: String?): RegistrationResult {
val params = RegistrationParams(
username = userName,
password = password,
initialDeviceDisplayName = initialDeviceDisplayName
)
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
.also {
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
.also { pendingSessionStore.savePendingSessionData(it) }
}
}
return performRegistrationRequest(params)
.also {
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
.also { pendingSessionStore.savePendingSessionData(it) }
}
}
override fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = pendingSessionData.currentSession ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
override suspend fun performReCaptcha(response: String): RegistrationResult {
val safeSession = pendingSessionData.currentSession
?: throw IllegalStateException("developer error, call createAccount() method first")
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
}
return performRegistrationRequest(params)
}
override fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = pendingSessionData.currentSession ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
override suspend fun acceptTerms(): RegistrationResult {
val safeSession = pendingSessionData.currentSession
?: throw IllegalStateException("developer error, call createAccount() method first")
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
}
return performRegistrationRequest(params)
}
override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable {
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
.also { pendingSessionStore.savePendingSessionData(it) }
override suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult {
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
.also { pendingSessionStore.savePendingSessionData(it) }
sendThreePid(threePid)
}
return sendThreePid(threePid)
}
override fun sendAgainThreePid(callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
sendThreePid(safeCurrentThreePid)
}
override suspend fun sendAgainThreePid(): RegistrationResult {
val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid
?: throw IllegalStateException("developer error, call createAccount() method first")
return sendThreePid(safeCurrentThreePid)
}
private suspend fun sendThreePid(threePid: RegisterThreePid): RegistrationResult {
@@ -173,20 +145,15 @@ internal class DefaultRegistrationWizard(
return performRegistrationRequest(params)
}
override fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeParam = pendingSessionData.currentThreePidData?.registrationParams ?: run {
callback.onFailure(IllegalStateException("developer error, no pending three pid"))
return NoOpCancellable
}
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(safeParam, delayMillis)
}
override suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult {
val safeParam = pendingSessionData.currentThreePidData?.registrationParams
?: throw IllegalStateException("developer error, no pending three pid")
return performRegistrationRequest(safeParam, delayMillis)
}
override fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
validateThreePid(code)
}
override suspend fun handleValidateThreePid(code: String): RegistrationResult {
return validateThreePid(code)
}
private suspend fun validateThreePid(code: String): RegistrationResult {
@@ -210,15 +177,12 @@ internal class DefaultRegistrationWizard(
}
}
override fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = pendingSessionData.currentSession ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
performRegistrationRequest(params)
}
override suspend fun dummy(): RegistrationResult {
val safeSession = pendingSessionData.currentSession
?: throw IllegalStateException("developer error, call createAccount() method first")
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
return performRegistrationRequest(params)
}
private suspend fun performRegistrationRequest(registrationParams: RegistrationParams,
@@ -239,9 +203,4 @@ internal class DefaultRegistrationWizard(
val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
return RegistrationResult.Success(session)
}
private fun buildAuthAPI(): AuthAPI {
val retrofit = retrofitFactory.create(okHttpClient, pendingSessionData.homeServerConnectionConfig.homeServerUri.toString())
return retrofit.create(AuthAPI::class.java)
}
}

View File

@@ -16,14 +16,25 @@
package org.matrix.android.sdk.internal.auth.registration
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
import org.matrix.android.sdk.api.auth.UIABaseAuth
import timber.log.Timber
import kotlin.coroutines.suspendCoroutine
internal suspend fun handleUIA(failure: Throwable, interceptor: UserInteractiveAuthInterceptor, retryBlock: suspend (UIABaseAuth) -> Unit): Boolean {
/**
* Handle a UIA challenge
*
* @param failure the failure to handle
* @param interceptor see doc in [UserInteractiveAuthInterceptor]
* @param retryBlock called at the end of the process, in this block generally retry executing the task, with
* provided authUpdate
* @return true if UIA is handled without error
*/
internal suspend fun handleUIA(failure: Throwable,
interceptor: UserInteractiveAuthInterceptor,
retryBlock: suspend (UIABaseAuth) -> Unit): Boolean {
Timber.d("## UIA: check error ${failure.message}")
val flowResponse = failure.toRegistrationFlowResponse()
?: return false.also {
@@ -38,16 +49,16 @@ internal suspend fun handleUIA(failure: Throwable, interceptor: UserInteractiveA
suspendCoroutine<UIABaseAuth> { continuation ->
interceptor.performStage(flowResponse, (failure as? Failure.ServerError)?.error?.code, continuation)
}
} catch (failure: Throwable) {
Timber.w(failure, "## UIA: failed to participate")
} catch (failure2: Throwable) {
Timber.w(failure2, "## UIA: failed to participate")
return false
}
Timber.d("## UIA: updated auth $authUpdate")
Timber.d("## UIA: updated auth")
return try {
retryBlock(authUpdate)
true
} catch (failure: Throwable) {
handleUIA(failure, interceptor, retryBlock)
} catch (failure3: Throwable) {
handleUIA(failure3, interceptor, retryBlock)
}
}

View File

@@ -61,7 +61,6 @@ import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceWithUserPasswordTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers
import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDeviceInfoTask
@@ -75,7 +74,6 @@ import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSignaturesTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSigningKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
import org.matrix.android.sdk.internal.crypto.tasks.EncryptEventTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
@@ -240,9 +238,6 @@ internal abstract class CryptoModule {
@Binds
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
@Binds
abstract fun bindDeleteDeviceWithUserPasswordTask(task: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask
@Binds
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService

View File

@@ -75,7 +75,6 @@ import org.matrix.android.sdk.internal.crypto.model.toRest
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
@@ -153,9 +152,8 @@ internal class DefaultCryptoService @Inject constructor(
// Repository
private val megolmEncryptionFactory: MXMegolmEncryptionFactory,
private val olmEncryptionFactory: MXOlmEncryptionFactory,
private val deleteDeviceTask: DeleteDeviceTask,
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
// Tasks
private val deleteDeviceTask: DeleteDeviceTask,
private val getDevicesTask: GetDevicesTask,
private val getDeviceInfoTask: GetDeviceInfoTask,
private val setDeviceNameTask: SetDeviceNameTask,
@@ -217,15 +215,6 @@ internal class DefaultCryptoService @Inject constructor(
.executeBy(taskExecutor)
}
override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>) {
deleteDeviceWithUserPasswordTask
.configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password)) {
this.executionThread = TaskThread.CRYPTO
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
}

View File

@@ -47,12 +47,16 @@ internal class DefaultDeleteDeviceTask @Inject constructor(
}
} catch (throwable: Throwable) {
if (params.userInteractiveAuthInterceptor == null
|| !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
execute(params.copy(userAuthParam = auth))
}
|| !handleUIA(
failure = throwable,
interceptor = params.userInteractiveAuthInterceptor,
retryBlock = { authUpdate ->
execute(params.copy(userAuthParam = authUpdate))
}
)
) {
Timber.d("## UIA: propagate failure")
throw throwable
throw throwable
}
}
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.tasks
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface DeleteDeviceWithUserPasswordTask : Task<DeleteDeviceWithUserPasswordTask.Params, Unit> {
data class Params(
val deviceId: String,
val authSession: String?,
val password: String
)
}
internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(
private val cryptoApi: CryptoApi,
@UserId private val userId: String,
private val globalErrorReceiver: GlobalErrorReceiver
) : DeleteDeviceWithUserPasswordTask {
override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) {
return executeRequest(globalErrorReceiver) {
apiCall = cryptoApi.deleteDevice(params.deviceId,
DeleteDeviceParams(
auth = UserPasswordAuth(
type = LoginFlowTypes.PASSWORD,
session = params.authSession,
user = userId,
password = params.password
).asMap()
)
)
}
}
}

View File

@@ -126,11 +126,16 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
uploadSigningKeysTask.execute(uploadSigningKeysParams)
} catch (failure: Throwable) {
if (params.interactiveAuthInterceptor == null
|| !handleUIA(failure, params.interactiveAuthInterceptor) { authUpdate ->
uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
}) {
|| !handleUIA(
failure = failure,
interceptor = params.interactiveAuthInterceptor,
retryBlock = { authUpdate ->
uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
}
)
) {
Timber.d("## UIA: propagate failure")
throw failure
throw failure
}
}

View File

@@ -16,10 +16,9 @@
package org.matrix.android.sdk.internal.session.account
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.internal.auth.registration.handleUIA
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.cleanup.CleanupSession
@@ -30,8 +29,8 @@ import javax.inject.Inject
internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> {
data class Params(
val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
val eraseAllData: Boolean,
val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
val userAuthParam: UIABaseAuth? = null
)
}
@@ -39,7 +38,6 @@ internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Un
internal class DefaultDeactivateAccountTask @Inject constructor(
private val accountAPI: AccountAPI,
private val globalErrorReceiver: GlobalErrorReceiver,
@UserId private val userId: String,
private val identityDisconnectTask: IdentityDisconnectTask,
private val cleanupSession: CleanupSession
) : DeactivateAccountTask {
@@ -47,23 +45,33 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
override suspend fun execute(params: DeactivateAccountTask.Params) {
val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData)
try {
val canCleanup = try {
executeRequest<Unit>(globalErrorReceiver) {
apiCall = accountAPI.deactivate(deactivateAccountParams)
}
true
} catch (throwable: Throwable) {
if (!handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
execute(params.copy(userAuthParam = auth))
}
if (!handleUIA(
failure = throwable,
interceptor = params.userInteractiveAuthInterceptor,
retryBlock = { authUpdate ->
execute(params.copy(userAuthParam = authUpdate))
}
)
) {
Timber.d("## UIA: propagate failure")
throw throwable
throw throwable
} else {
false
}
}
// Logout from identity server if any, ignoring errors
runCatching { identityDisconnectTask.execute(Unit) }
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
cleanupSession.handle()
if (canCleanup) {
// Logout from identity server if any, ignoring errors
runCatching { identityDisconnectTask.execute(Unit) }
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
cleanupSession.handle()
}
}
}

View File

@@ -27,7 +27,7 @@ internal class DefaultAccountService @Inject constructor(private val changePassw
changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword))
}
override suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean) {
deactivateAccountTask.execute(DeactivateAccountTask.Params(userInteractiveAuthInterceptor, eraseAllData))
override suspend fun deactivateAccount(eraseAllData: Boolean, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
deactivateAccountTask.execute(DeactivateAccountTask.Params(eraseAllData, userInteractiveAuthInterceptor))
}
}

View File

@@ -16,16 +16,12 @@
package org.matrix.android.sdk.internal.session.call
import kotlinx.coroutines.Dispatchers
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.call.CallListener
import org.matrix.android.sdk.api.session.call.CallSignalingService
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker
import org.matrix.android.sdk.api.session.call.TurnServerResponse
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.launchToCallback
import timber.log.Timber
import javax.inject.Inject
@@ -34,14 +30,16 @@ internal class DefaultCallSignalingService @Inject constructor(
private val callSignalingHandler: CallSignalingHandler,
private val mxCallFactory: MxCallFactory,
private val activeCallHandler: ActiveCallHandler,
private val taskExecutor: TaskExecutor,
private val turnServerDataSource: TurnServerDataSource
private val turnServerDataSource: TurnServerDataSource,
private val pstnProtocolChecker: PSTNProtocolChecker
) : CallSignalingService {
override fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable {
return taskExecutor.executorScope.launchToCallback(Dispatchers.Default, callback) {
turnServerDataSource.getTurnServer()
}
override suspend fun getTurnServer(): TurnServerResponse {
return turnServerDataSource.getTurnServer()
}
override fun getPSTNProtocolChecker(): PSTNProtocolChecker {
return pstnProtocolChecker
}
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {

View File

@@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
@@ -47,11 +46,12 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
private val profileAPI: ProfileAPI,
@SessionDatabase private val monarchy: Monarchy,
private val pendingThreePidMapper: PendingThreePidMapper,
@UserId private val userId: String,
private val globalErrorReceiver: GlobalErrorReceiver) : FinalizeAddingThreePidTask() {
override suspend fun execute(params: Params) {
if (params.userWantsToCancel.not()) {
val canCleanup = if (params.userWantsToCancel) {
true
} else {
// Get the required pending data
val pendingThreePids = monarchy.fetchAllMappedSync(
{ it.where(PendingThreePidEntity::class.java) },
@@ -69,21 +69,30 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
)
apiCall = profileAPI.finalizeAddThreePid(body)
}
true
} catch (throwable: Throwable) {
if (params.userInteractiveAuthInterceptor == null
|| !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
execute(params.copy(userAuthParam = auth))
}
|| !handleUIA(
failure = throwable,
interceptor = params.userInteractiveAuthInterceptor,
retryBlock = { authUpdate ->
execute(params.copy(userAuthParam = authUpdate))
}
)
) {
Timber.d("## UIA: propagate failure")
throw throwable.toRegistrationFlowResponse()
throw throwable.toRegistrationFlowResponse()
?.let { Failure.RegistrationFlowError(it) }
?: throwable
} else {
false
}
}
}
cleanupDatabase(params)
if (canCleanup) {
cleanupDatabase(params)
}
}
private suspend fun cleanupDatabase(params: Params) {

View File

@@ -41,6 +41,9 @@ parser.add_argument('-b',
type=int,
required=True,
help='the buildkite build number.')
parser.add_argument('-f',
'--filename',
help='the filename, to download only one artifact.')
parser.add_argument('-e',
'--expecting',
type=int,
@@ -148,6 +151,8 @@ for elt in data:
print(" %s: %s" % (key, str(value)))
url = elt.get("download_url")
filename = elt.get("filename")
if args.filename is not None and args.filename != filename:
continue
target = targetDir + "/" + filename
print("Downloading %s to '%s'..." % (filename, targetDir))
if not args.simulate:

View File

@@ -12,8 +12,8 @@ kapt {
// Note: 2 digits max for each value
ext.versionMajor = 1
ext.versionMinor = 0
ext.versionPatch = 18
ext.versionMinor = 1
ext.versionPatch = 0
static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct'

View File

@@ -23,11 +23,11 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.Assert
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.sync.SyncState
@@ -47,22 +47,21 @@ abstract class VerificationTestBase {
withInitialSync: Boolean): Session {
val hs = createHomeServerConfig()
doSync<LoginFlowResult> {
matrix.authenticationService()
.getLoginFlow(hs, it)
runBlockingTest {
matrix.authenticationService().getLoginFlow(hs)
}
doSync<RegistrationResult> {
runBlockingTest {
matrix.authenticationService()
.getRegistrationWizard()
.createAccount(userName, password, null, it)
.createAccount(userName, password, null)
}
// Perform dummy step
val registrationResult = doSync<RegistrationResult> {
val registrationResult = runBlockingTest {
matrix.authenticationService()
.getRegistrationWizard()
.dummy(it)
.dummy()
}
Assert.assertTrue(registrationResult is RegistrationResult.Success)
@@ -80,6 +79,14 @@ abstract class VerificationTestBase {
.build()
}
protected fun <T> runBlockingTest(timeout: Long = 20_000, block: suspend () -> T): T {
return runBlocking {
withTimeout(timeout) {
block()
}
}
}
// Transform a method with a MatrixCallback to a synchronous method
inline fun <reified T> doSync(block: (MatrixCallback<T>) -> Unit): T {
val lock = CountDownLatch(1)

View File

@@ -0,0 +1,34 @@
/*
* 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.app.core.epoxy
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents
@EpoxyModelClass(layout = R.layout.item_timeline_empty)
abstract class TimelineEmptyItem : VectorEpoxyModel<TimelineEmptyItem.Holder>(), ItemWithEvents {
@EpoxyAttribute lateinit var eventId: String
override fun getEventIds(): List<String> {
return listOf(eventId)
}
class Holder : VectorEpoxyHolder()
}

View File

@@ -113,7 +113,7 @@ class ReAuthActivity : SimpleFragmentActivity(), ReAuthViewModel.Factory {
override fun onResume() {
super.onResume()
// It's the only way we have to know if sso falback flow was successful
// It's the only way we have to know if sso fallback flow was successful
withState(sharedViewModel) {
if (it.ssoFallbackPageWasShown) {
Timber.d("## UIA ssoFallbackPageWasShown tentative success")

View File

@@ -16,6 +16,7 @@
package im.vector.app.features.call
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
@@ -29,17 +30,16 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.call.audio.CallAudioManager
import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.call.webrtc.WebRtcCallManager
import org.matrix.android.sdk.api.MatrixCallback
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
import org.matrix.android.sdk.api.session.call.TurnServerResponse
import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import java.util.Timer
import java.util.TimerTask
class VectorCallViewModel @AssistedInject constructor(
@Assisted initialState: VectorCallViewState,
@@ -50,7 +50,7 @@ class VectorCallViewModel @AssistedInject constructor(
private var call: WebRtcCall? = null
private var connectionTimeoutTimer: Timer? = null
private var connectionTimeoutJob: Job? = null
private var hasBeenConnectedOnce = false
private val callListener = object : WebRtcCall.Listener {
@@ -92,26 +92,20 @@ class VectorCallViewModel @AssistedInject constructor(
val callState = call.state
if (callState is CallState.Connected && callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
hasBeenConnectedOnce = true
connectionTimeoutTimer?.cancel()
connectionTimeoutTimer = null
connectionTimeoutJob?.cancel()
connectionTimeoutJob = null
} else {
// do we reset as long as it's moving?
connectionTimeoutTimer?.cancel()
connectionTimeoutJob?.cancel()
if (hasBeenConnectedOnce) {
connectionTimeoutTimer = Timer().apply {
schedule(object : TimerTask() {
override fun run() {
session.callSignalingService().getTurnServer(object : MatrixCallback<TurnServerResponse> {
override fun onFailure(failure: Throwable) {
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(null))
}
override fun onSuccess(data: TurnServerResponse) {
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(data))
}
})
}
}, 30_000)
connectionTimeoutJob = viewModelScope.launch {
delay(30_000)
try {
val turn = session.callSignalingService().getTurnServer()
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(turn))
} catch (failure: Throwable) {
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(null))
}
}
}
}

View File

@@ -83,12 +83,12 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
fun setAudioDevice(device: Device) {
runInAudioThread(Runnable {
if (!_availableDevices.contains(device)) {
Timber.w(" Audio device not available: $device")
Timber.w("Audio device not available: $device")
userSelectedDevice = null
return@Runnable
}
if (mode != Mode.DEFAULT) {
Timber.i(" User selected device set to: $device")
Timber.i("User selected device set to: $device")
userSelectedDevice = device
updateAudioRoute(mode, false)
}
@@ -108,7 +108,7 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
success = updateAudioRoute(mode, false)
} catch (e: Throwable) {
success = false
Timber.e(e, " Failed to update audio route for mode: " + mode)
Timber.e(e, "Failed to update audio route for mode: $mode")
}
if (success) {
this@CallAudioManager.mode = mode
@@ -124,7 +124,7 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
* `false`, otherwise.
*/
private fun updateAudioRoute(mode: Mode, force: Boolean): Boolean {
Timber.i(" Update audio route for mode: " + mode)
Timber.i("Update audio route for mode: $mode")
if (!audioDeviceRouter?.setMode(mode).orFalse()) {
return false
}
@@ -158,7 +158,7 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
return true
}
selectedDevice = audioDevice
Timber.i(" Selected audio device: " + audioDevice)
Timber.i("Selected audio device: $audioDevice")
audioDeviceRouter?.setAudioRoute(audioDevice)
configChange?.invoke()
return true

View File

@@ -22,20 +22,22 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject
class DialPadLookup @Inject constructor(val session: Session,
val directRoomHelper: DirectRoomHelper,
val callManager: WebRtcCallManager
class DialPadLookup @Inject constructor(
private val session: Session,
private val directRoomHelper: DirectRoomHelper,
private val callManager: WebRtcCallManager
) {
class Failure : Throwable()
data class Result(val userId: String, val roomId: String)
suspend fun lookupPhoneNumber(phoneNumber: String): Result {
val supportedProtocolKey = callManager.supportedPSTNProtocol ?: throw Failure()
val thirdPartyUser = tryOrNull {
session.thirdPartyService().getThirdPartyUser(supportedProtocolKey, fields = mapOf(
"m.id.phone" to phoneNumber
)).firstOrNull()
session.thirdPartyService().getThirdPartyUser(
protocol = supportedProtocolKey,
fields = mapOf("m.id.phone" to phoneNumber)
).firstOrNull()
} ?: throw Failure()
val roomId = directRoomHelper.ensureDMExists(thirdPartyUser.userId)

View File

@@ -1,43 +0,0 @@
/*
* Copyright (c) 2021 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.app.features.call.webrtc
import kotlinx.coroutines.delay
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
private const val PSTN_VECTOR_KEY = "im.vector.protocol.pstn"
private const val PSTN_MATRIX_KEY = "m.protocol.pstn"
suspend fun Session.getSupportedPSTN(maxTries: Int): String? {
val thirdPartyProtocols: Map<String, ThirdPartyProtocol> = try {
thirdPartyService().getThirdPartyProtocols()
} catch (failure: Throwable) {
if (maxTries == 1) {
return null
} else {
// Wait for 10s before trying again
delay(10_000L)
return getSupportedPSTN(maxTries - 1)
}
}
return when {
thirdPartyProtocols.containsKey(PSTN_VECTOR_KEY) -> PSTN_VECTOR_KEY
thirdPartyProtocols.containsKey(PSTN_MATRIX_KEY) -> PSTN_MATRIX_KEY
else -> null
}
}

View File

@@ -53,7 +53,6 @@ import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
import org.matrix.android.sdk.api.session.room.model.call.SdpType
import org.matrix.android.sdk.internal.util.awaitCallback
import org.threeten.bp.Duration
import org.webrtc.AudioSource
import org.webrtc.AudioTrack
@@ -420,9 +419,7 @@ class WebRtcCall(val mxCall: MxCall,
private suspend fun getTurnServer(): TurnServerResponse? {
return tryOrNull {
awaitCallback {
sessionProvider.get()?.callSignalingService()?.getTurnServer(it)
}
sessionProvider.get()?.callSignalingService()?.getTurnServer()
}
}

View File

@@ -26,14 +26,13 @@ import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.audio.CallAudioManager
import im.vector.app.features.call.utils.EglUtils
import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.call.CallListener
import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
@@ -65,22 +64,26 @@ class WebRtcCallManager @Inject constructor(
private val currentSession: Session?
get() = activeSessionDataSource.currentValue?.orNull()
private val pstnProtocolChecker: PSTNProtocolChecker?
get() = currentSession?.callSignalingService()?.getPSTNProtocolChecker()
interface CurrentCallListener {
fun onCurrentCallChange(call: WebRtcCall?) {}
fun onAudioDevicesChange() {}
}
interface PSTNSupportListener {
fun onPSTNSupportUpdated()
val supportedPSTNProtocol: String?
get() = pstnProtocolChecker?.supportedPSTNProtocol
val supportsPSTNProtocol: Boolean
get() = supportedPSTNProtocol != null
fun addPstnSupportListener(listener: PSTNProtocolChecker.Listener) {
pstnProtocolChecker?.addListener(listener)
}
private val pstnSupportListeners = emptyList<PSTNSupportListener>().toMutableList()
fun addPstnSupportListener(listener: PSTNSupportListener) {
pstnSupportListeners.add(listener)
}
fun removePstnSupportListener(listener: PSTNSupportListener) {
pstnSupportListeners.remove(listener)
fun removePstnSupportListener(listener: PSTNProtocolChecker.Listener) {
pstnProtocolChecker?.removeListener(listener)
}
private val currentCallsListeners = CopyOnWriteArrayList<CurrentCallListener>()
@@ -104,27 +107,11 @@ class WebRtcCallManager @Inject constructor(
private var peerConnectionFactory: PeerConnectionFactory? = null
private val executor = Executors.newSingleThreadExecutor()
private val dispatcher = executor.asCoroutineDispatcher()
var supportedPSTNProtocol: String? = null
private set
val supportsPSTNProtocol: Boolean
get() = supportedPSTNProtocol != null
private val rootEglBase by lazy { EglUtils.rootEglBase }
private var isInBackground: Boolean = true
init {
GlobalScope.launch {
supportedPSTNProtocol = currentSession?.getSupportedPSTN(3)
if (supportedPSTNProtocol != null) {
pstnSupportListeners.forEach {
tryOrNull { it.onPSTNSupportUpdated() }
}
}
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun entersForeground() {
isInBackground = false
@@ -167,6 +154,10 @@ class WebRtcCallManager @Inject constructor(
return callsByCallId.values.toList()
}
fun checkForPSTNSupportIfNeeded() {
pstnProtocolChecker?.checkForPSTNSupportIfNeeded()
}
/**
* @return a set of all advertised call during the lifetime of the app.
*/
@@ -176,7 +167,6 @@ class WebRtcCallManager @Inject constructor(
Timber.v("## VOIP headSetButtonTapped")
val call = getCurrentCall() ?: return
if (call.mxCall.state is CallState.LocalRinging) {
// accept call
call.acceptIncomingCall()
}
if (call.mxCall.state is CallState.Connected) {

View File

@@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.util.awaitCallback
import java.io.OutputStream
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class BootstrapSharedViewModel @AssistedInject constructor(
@Assisted initialState: BootstrapViewState,
@@ -421,7 +422,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
_viewEvents.post(BootstrapViewEvents.RequestReAuth(flowResponse, errCode))
}
else -> {
promise.resumeWith(Result.failure(UnsupportedOperationException()))
promise.resumeWithException(UnsupportedOperationException())
}
}
}

View File

@@ -52,6 +52,7 @@ import org.matrix.android.sdk.rx.rx
import timber.log.Timber
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class HomeActivityViewModel @AssistedInject constructor(
@Assisted initialState: HomeActivityViewState,
@@ -228,7 +229,7 @@ class HomeActivityViewModel @AssistedInject constructor(
)
)
} else {
promise.resumeWith(Result.failure(Exception("Cannot silently initialize cross signing, UIA missing")))
promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing"))
}
}
},

View File

@@ -26,8 +26,8 @@ import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay
import com.jakewharton.rxrelay2.PublishRelay
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
@@ -64,6 +64,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.LocalEcho
@@ -120,7 +121,7 @@ class RoomDetailViewModel @AssistedInject constructor(
private val directRoomHelper: DirectRoomHelper,
timelineSettingsFactory: TimelineSettingsFactory
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
Timeline.Listener, ChatEffectManager.Delegate, WebRtcCallManager.PSTNSupportListener {
Timeline.Listener, ChatEffectManager.Delegate, PSTNProtocolChecker.Listener {
private val room = session.getRoom(initialState.roomId)!!
private val eventId = initialState.eventId
@@ -176,6 +177,7 @@ class RoomDetailViewModel @AssistedInject constructor(
// Inform the SDK that the room is displayed
session.onRoomDisplayed(initialState.roomId)
callManager.addPstnSupportListener(this)
callManager.checkForPSTNSupportIfNeeded()
chatEffectManager.delegate = this
}
@@ -231,65 +233,65 @@ class RoomDetailViewModel @AssistedInject constructor(
override fun handle(action: RoomDetailAction) {
when (action) {
is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action)
is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
is RoomDetailAction.SendMessage -> handleSendMessage(action)
is RoomDetailAction.SendMedia -> handleSendMedia(action)
is RoomDetailAction.SendSticker -> handleSendSticker(action)
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
is RoomDetailAction.SendReaction -> handleSendReaction(action)
is RoomDetailAction.AcceptInvite -> handleAcceptInvite()
is RoomDetailAction.RejectInvite -> handleRejectInvite()
is RoomDetailAction.RedactAction -> handleRedactEvent(action)
is RoomDetailAction.UndoReaction -> handleUndoReact(action)
is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action)
is RoomDetailAction.EnterEditMode -> handleEditAction(action)
is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action)
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action)
is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action)
is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
is RoomDetailAction.ResendMessage -> handleResendEvent(action)
is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
is RoomDetailAction.ResendAll -> handleResendAll()
is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
is RoomDetailAction.ReportContent -> handleReportContent(action)
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action)
is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
is RoomDetailAction.SendMessage -> handleSendMessage(action)
is RoomDetailAction.SendMedia -> handleSendMedia(action)
is RoomDetailAction.SendSticker -> handleSendSticker(action)
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
is RoomDetailAction.SendReaction -> handleSendReaction(action)
is RoomDetailAction.AcceptInvite -> handleAcceptInvite()
is RoomDetailAction.RejectInvite -> handleRejectInvite()
is RoomDetailAction.RedactAction -> handleRedactEvent(action)
is RoomDetailAction.UndoReaction -> handleUndoReact(action)
is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action)
is RoomDetailAction.EnterEditMode -> handleEditAction(action)
is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action)
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action)
is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action)
is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
is RoomDetailAction.ResendMessage -> handleResendEvent(action)
is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
is RoomDetailAction.ResendAll -> handleResendAll()
is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
is RoomDetailAction.ReportContent -> handleReportContent(action)
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action)
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action)
is RoomDetailAction.StartCall -> handleStartCall(action)
is RoomDetailAction.AcceptCall -> handleAcceptCall(action)
is RoomDetailAction.EndCall -> handleEndCall()
is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
is RoomDetailAction.CancelSend -> handleCancel(action)
is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action)
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople()
RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar()
is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action)
RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings)
is RoomDetailAction.ShowRoomAvatarFullScreen -> {
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action)
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action)
is RoomDetailAction.StartCall -> handleStartCall(action)
is RoomDetailAction.AcceptCall -> handleAcceptCall(action)
is RoomDetailAction.EndCall -> handleEndCall()
is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
is RoomDetailAction.CancelSend -> handleCancel(action)
is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action)
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople()
RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar()
is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action)
RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings)
is RoomDetailAction.ShowRoomAvatarFullScreen -> {
_viewEvents.post(
RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView)
)
}
is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action)
is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action)
}.exhaustive
}
@@ -618,10 +620,10 @@ class RoomDetailViewModel @AssistedInject constructor(
return@withState false
}
when (itemId) {
R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true
R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true
R.id.timeline_setting -> true
R.id.invite -> state.canInvite
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
R.id.invite -> state.canInvite
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
R.id.open_matrix_apps -> true
R.id.voice_call,
R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty()
@@ -741,7 +743,7 @@ class RoomDetailViewModel @AssistedInject constructor(
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
popDraft()
}
is ParsedCommand.SendChatEffect -> {
is ParsedCommand.SendChatEffect -> {
sendChatEffect(slashCommandResult)
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
popDraft()
@@ -774,7 +776,7 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}.exhaustive
}
is SendMode.EDIT -> {
is SendMode.EDIT -> {
// is original event a reply?
val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId
if (inReplyTo != null) {
@@ -799,7 +801,7 @@ class RoomDetailViewModel @AssistedInject constructor(
_viewEvents.post(RoomDetailViewEvents.MessageSent)
popDraft()
}
is SendMode.QUOTE -> {
is SendMode.QUOTE -> {
val messageContent: MessageContent? =
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
@@ -822,7 +824,7 @@ class RoomDetailViewModel @AssistedInject constructor(
_viewEvents.post(RoomDetailViewEvents.MessageSent)
popDraft()
}
is SendMode.REPLY -> {
is SendMode.REPLY -> {
state.sendMode.timelineEvent.let {
room.replyToMessage(it, action.text.toString(), action.autoMarkdown)
_viewEvents.post(RoomDetailViewEvents.MessageSent)
@@ -1441,7 +1443,7 @@ class RoomDetailViewModel @AssistedInject constructor(
}
override fun onPSTNSupportUpdated() {
updateShowDialerOptionState()
updateShowDialerOptionState()
}
private fun updateShowDialerOptionState() {

View File

@@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail
import androidx.recyclerview.widget.LinearLayoutManager
import im.vector.app.core.platform.DefaultListUpdateCallback
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.item.BaseEventItem
import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents
import timber.log.Timber
import java.util.concurrent.CopyOnWriteArrayList
@@ -47,8 +47,8 @@ class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager,
if (layoutManager.findFirstVisibleItemPosition() != position) {
return
}
val firstNewItem = timelineEventController.adapter.getModelAtPosition(position) as? BaseEventItem ?: return
val firstNewItemIds = firstNewItem.getEventIds().firstOrNull()
val firstNewItem = timelineEventController.adapter.getModelAtPosition(position) as? ItemWithEvents ?: return
val firstNewItemIds = firstNewItem.getEventIds().firstOrNull() ?: return
val indexOfFirstNewItem = newTimelineEventIds.indexOf(firstNewItemIds)
if (indexOfFirstNewItem != -1) {
Timber.v("Should scroll to position: $position")

View File

@@ -38,18 +38,15 @@ import im.vector.app.features.home.room.detail.timeline.factory.MergedHeaderItem
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactory
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.ReadMarkerVisibilityStateChangedListener
import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.app.features.home.room.detail.timeline.item.BaseEventItem
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem
import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem
import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem_
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
import im.vector.app.features.media.ImageContentRenderer
import im.vector.app.features.media.VideoContentRenderer
@@ -194,75 +191,20 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
}
}
private val interceptorHelper = TimelineControllerInterceptorHelper(
::positionOfReadMarker,
adapterPositionMapping,
vectorPreferences,
callManager
)
init {
addInterceptor(this)
requestModelBuild()
}
// Update position when we are building new items
override fun intercept(models: MutableList<EpoxyModel<*>>) = synchronized(modelCache) {
positionOfReadMarker = null
adapterPositionMapping.clear()
val callIds = mutableSetOf<String>()
val modelsIterator = models.listIterator()
val showHiddenEvents = vectorPreferences.shouldShowHiddenEvents()
modelsIterator.withIndex().forEach {
val index = it.index
val epoxyModel = it.value
if (epoxyModel is CallTileTimelineItem) {
val callId = epoxyModel.attributes.callId
// We should remove the call tile if we already have one for this call or
// if this is an active call tile without an actual call (which can happen with permalink)
val shouldRemoveCallItem = callIds.contains(callId)
|| (!callManager.getAdvertisedCalls().contains(callId) && epoxyModel.attributes.callStatus.isActive())
if (shouldRemoveCallItem && !showHiddenEvents) {
modelsIterator.remove()
return@forEach
}
callIds.add(callId)
}
if (epoxyModel is BaseEventItem) {
epoxyModel.getEventIds().forEach { eventId ->
adapterPositionMapping[eventId] = index
}
}
}
val currentUnreadState = this.unreadState
if (currentUnreadState is UnreadState.HasUnread) {
val position = adapterPositionMapping[currentUnreadState.firstUnreadEventId]?.plus(1)
positionOfReadMarker = position
if (position != null) {
val readMarker = TimelineReadMarkerItem_()
.also {
it.id("read_marker")
it.setOnVisibilityStateChanged(ReadMarkerVisibilityStateChangedListener(callback))
}
models.add(position, readMarker)
}
}
val shouldAddBackwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.BACKWARDS) ?: false
if (shouldAddBackwardPrefetch) {
val indexOfPrefetchBackward = (previousModelsSize - 1)
.coerceAtMost(models.size - DEFAULT_PREFETCH_THRESHOLD)
.coerceAtLeast(0)
val loadingItem = LoadingItem_()
.id("prefetch_backward_loading${System.currentTimeMillis()}")
.showLoader(false)
.setVisibilityStateChangedListener(Timeline.Direction.BACKWARDS)
models.add(indexOfPrefetchBackward, loadingItem)
}
val shouldAddForwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.FORWARDS) ?: false
if (shouldAddForwardPrefetch) {
val indexOfPrefetchForward = DEFAULT_PREFETCH_THRESHOLD.coerceAtMost(models.size - 1)
val loadingItem = LoadingItem_()
.id("prefetch_forward_loading${System.currentTimeMillis()}")
.showLoader(false)
.setVisibilityStateChangedListener(Timeline.Direction.FORWARDS)
models.add(indexOfPrefetchForward, loadingItem)
}
previousModelsSize = models.size
interceptorHelper.intercept(models, unreadState, timeline, callback)
}
fun update(viewState: RoomDetailViewState) {
@@ -431,6 +373,14 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
}
}
private fun LoadingItem_.setVisibilityStateChangedListener(direction: Timeline.Direction): LoadingItem_ {
return onVisibilityStateChanged { _, _, visibilityState ->
if (visibilityState == VisibilityState.VISIBLE) {
callback?.onLoadMore(direction)
}
}
}
private fun updateUTDStates(event: TimelineEvent, nextEvent: TimelineEvent?) {
if (vectorPreferences.labShowCompleteHistoryInEncryptedRoom()) {
return
@@ -461,14 +411,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
return shouldAdd
}
private fun LoadingItem_.setVisibilityStateChangedListener(direction: Timeline.Direction): LoadingItem_ {
return onVisibilityStateChanged { _, _, visibilityState ->
if (visibilityState == VisibilityState.VISIBLE) {
callback?.onLoadMore(direction)
}
}
}
fun searchPositionOfEvent(eventId: String?): Int? = synchronized(modelCache) {
return adapterPositionMapping[eventId]
}

View File

@@ -16,7 +16,8 @@
package im.vector.app.features.home.room.detail.timeline.factory
import im.vector.app.core.epoxy.EmptyItem_
import im.vector.app.core.epoxy.TimelineEmptyItem
import im.vector.app.core.epoxy.TimelineEmptyItem_
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
@@ -114,6 +115,12 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
Timber.e(throwable, "failed to create message item")
defaultItemFactory.create(event, highlight, callback, throwable)
}
return (computedModel ?: EmptyItem_())
return computedModel ?: buildEmptyItem(event)
}
private fun buildEmptyItem(timelineEvent: TimelineEvent): TimelineEmptyItem {
return TimelineEmptyItem_()
.id(timelineEvent.localId)
.eventId(timelineEvent.eventId)
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) 2021 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.app.features.home.room.detail.timeline.helper
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.VisibilityState
import im.vector.app.core.epoxy.LoadingItem_
import im.vector.app.core.epoxy.TimelineEmptyItem_
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.home.room.detail.UnreadState
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem
import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents
import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import kotlin.reflect.KMutableProperty0
private const val DEFAULT_PREFETCH_THRESHOLD = 30
class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMutableProperty0<Int?>,
private val adapterPositionMapping: MutableMap<String, Int>,
private val vectorPreferences: VectorPreferences,
private val callManager: WebRtcCallManager
) {
private var previousModelsSize = 0
// Update position when we are building new items
fun intercept(
models: MutableList<EpoxyModel<*>>,
unreadState: UnreadState,
timeline: Timeline?,
callback: TimelineEventController.Callback?
) {
positionOfReadMarker.set(null)
adapterPositionMapping.clear()
val callIds = mutableSetOf<String>()
// Add some prefetch loader if needed
models.addBackwardPrefetchIfNeeded(timeline, callback)
models.addForwardPrefetchIfNeeded(timeline, callback)
val modelsIterator = models.listIterator()
val showHiddenEvents = vectorPreferences.shouldShowHiddenEvents()
var index = 0
val firstUnreadEventId = (unreadState as? UnreadState.HasUnread)?.firstUnreadEventId
// Then iterate on models so we have the exact positions in the adapter
modelsIterator.forEach { epoxyModel ->
if (epoxyModel is ItemWithEvents) {
epoxyModel.getEventIds().forEach { eventId ->
adapterPositionMapping[eventId] = index
if (eventId == firstUnreadEventId) {
modelsIterator.addReadMarkerItem(callback)
index++
positionOfReadMarker.set(index)
}
}
}
if (epoxyModel is CallTileTimelineItem) {
modelsIterator.removeCallItemIfNeeded(epoxyModel, callIds, showHiddenEvents)
}
index++
}
previousModelsSize = models.size
}
private fun MutableListIterator<EpoxyModel<*>>.addReadMarkerItem(callback: TimelineEventController.Callback?) {
val readMarker = TimelineReadMarkerItem_()
.also {
it.id("read_marker")
it.setOnVisibilityStateChanged(ReadMarkerVisibilityStateChangedListener(callback))
}
add(readMarker)
// Use next as we still have some process to do before the next iterator loop
next()
}
private fun MutableListIterator<EpoxyModel<*>>.removeCallItemIfNeeded(
epoxyModel: CallTileTimelineItem,
callIds: MutableSet<String>,
showHiddenEvents: Boolean
) {
val callId = epoxyModel.attributes.callId
// We should remove the call tile if we already have one for this call or
// if this is an active call tile without an actual call (which can happen with permalink)
val shouldRemoveCallItem = callIds.contains(callId)
|| (!callManager.getAdvertisedCalls().contains(callId) && epoxyModel.attributes.callStatus.isActive())
if (shouldRemoveCallItem && !showHiddenEvents) {
remove()
val emptyItem = TimelineEmptyItem_()
.id(epoxyModel.id())
.eventId(epoxyModel.attributes.informationData.eventId)
add(emptyItem)
}
callIds.add(callId)
}
private fun MutableList<EpoxyModel<*>>.addBackwardPrefetchIfNeeded(timeline: Timeline?, callback: TimelineEventController.Callback?) {
val shouldAddBackwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.BACKWARDS) ?: false
if (shouldAddBackwardPrefetch) {
val indexOfPrefetchBackward = (previousModelsSize - 1)
.coerceAtMost(size - DEFAULT_PREFETCH_THRESHOLD)
.coerceAtLeast(0)
val loadingItem = LoadingItem_()
.id("prefetch_backward_loading${System.currentTimeMillis()}")
.showLoader(false)
.setVisibilityStateChangedListener(Timeline.Direction.BACKWARDS, callback)
add(indexOfPrefetchBackward, loadingItem)
}
}
private fun MutableList<EpoxyModel<*>>.addForwardPrefetchIfNeeded(timeline: Timeline?, callback: TimelineEventController.Callback?) {
val shouldAddForwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.FORWARDS) ?: false
if (shouldAddForwardPrefetch) {
val indexOfPrefetchForward = DEFAULT_PREFETCH_THRESHOLD.coerceAtMost(size - 1)
val loadingItem = LoadingItem_()
.id("prefetch_forward_loading${System.currentTimeMillis()}")
.showLoader(false)
.setVisibilityStateChangedListener(Timeline.Direction.FORWARDS, callback)
add(indexOfPrefetchForward, loadingItem)
}
}
private fun LoadingItem_.setVisibilityStateChangedListener(
direction: Timeline.Direction,
callback: TimelineEventController.Callback?
): LoadingItem_ {
return onVisibilityStateChanged { _, _, visibilityState ->
if (visibilityState == VisibilityState.VISIBLE) {
callback?.onLoadMore(direction)
}
}
}
}

View File

@@ -32,7 +32,7 @@ import im.vector.app.core.utils.DimensionConverter
/**
* Children must override getViewType()
*/
abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>() {
abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>(), ItemWithEvents {
// To use for instance when opening a permalink with an eventId
@EpoxyAttribute
@@ -53,12 +53,6 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
holder.checkableBackground.isChecked = highlighted
}
/**
* Returns the eventIds associated with the EventItem.
* Will generally get only one, but it handles the merging items.
*/
abstract fun getEventIds(): List<String>
abstract class BaseHolder(@IdRes val stubId: Int) : VectorEpoxyHolder() {
val leftGuideline by bind<View>(R.id.messageStartGuideline)
val checkableBackground by bind<CheckableView>(R.id.messageSelectedBackground)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright (c) 2021 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.
@@ -14,12 +14,12 @@
* limitations under the License.
*/
package im.vector.app.core.epoxy
package im.vector.app.features.home.room.detail.timeline.item
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
@EpoxyModelClass(layout = R.layout.item_empty)
abstract class EmptyItem : VectorEpoxyModel<EmptyItem.Holder>() {
class Holder : VectorEpoxyHolder()
interface ItemWithEvents {
/**
* Returns the eventIds associated with the EventItem.
* Will generally get only one, but it handles the merged items.
*/
fun getEventIds(): List<String>
}

View File

@@ -19,6 +19,7 @@ package im.vector.app.features.login
import android.content.Context
import android.net.Uri
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
@@ -27,8 +28,8 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.configureAndStart
@@ -37,7 +38,8 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.ensureTrailingSlash
import im.vector.app.features.signout.soft.SoftLogoutActivity
import org.matrix.android.sdk.api.MatrixCallback
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@@ -51,7 +53,6 @@ import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
import timber.log.Timber
import java.util.concurrent.CancellationException
@@ -117,7 +118,12 @@ class LoginViewModel @AssistedInject constructor(
private var loginConfig: LoginConfig? = null
private var currentTask: Cancelable? = null
private var currentJob: Job? = null
set(value) {
// Cancel any previous Job
field?.cancel()
field = value
}
override fun handle(action: LoginAction) {
when (action) {
@@ -140,7 +146,7 @@ class LoginViewModel @AssistedInject constructor(
}
private fun handleUserAcceptCertificate(action: LoginAction.UserAcceptCertificate) {
// It happen when we get the login flow, or during direct authentication.
// It happens when we get the login flow, or during direct authentication.
// So alter the homeserver config and retrieve again the login flow
when (val finalLastAction = lastAction) {
is LoginAction.UpdateHomeServer -> {
@@ -186,22 +192,20 @@ class LoginViewModel @AssistedInject constructor(
)
}
currentTask = safeLoginWizard.loginWithToken(
action.loginToken,
object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
onSessionCreated(data)
}
override fun onFailure(failure: Throwable) {
_viewEvents.post(LoginViewEvents.Failure(failure))
setState {
copy(
asyncLoginAction = Fail(failure)
)
}
}
})
currentJob = viewModelScope.launch {
try {
safeLoginWizard.loginWithToken(action.loginToken)
} catch (failure: Throwable) {
_viewEvents.post(LoginViewEvents.Failure(failure))
setState {
copy(
asyncLoginAction = Fail(failure)
)
}
null
}
?.let { onSessionCreated(it) }
}
}
}
@@ -231,46 +235,49 @@ class LoginViewModel @AssistedInject constructor(
private fun handleCheckIfEmailHasBeenValidated(action: LoginAction.CheckIfEmailHasBeenValidated) {
// We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state
currentTask?.cancel()
currentTask = registrationWizard?.checkIfEmailHasBeenValidated(action.delayMillis, registrationCallback)
currentJob = executeRegistrationStep(withLoading = false) {
it.checkIfEmailHasBeenValidated(action.delayMillis)
}
}
private fun handleStopEmailValidationCheck() {
currentTask?.cancel()
currentTask = null
currentJob = null
}
private fun handleValidateThreePid(action: LoginAction.ValidateThreePid) {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.handleValidateThreePid(action.code, registrationCallback)
currentJob = executeRegistrationStep {
it.handleValidateThreePid(action.code)
}
}
private val registrationCallback = object : MatrixCallback<RegistrationResult> {
override fun onSuccess(data: RegistrationResult) {
/*
// Simulate registration disabled
onFailure(Failure.ServerError(MatrixError(
code = MatrixError.FORBIDDEN,
message = "Registration is disabled"
), 403))
*/
setState {
copy(
asyncRegistration = Uninitialized
)
}
when (data) {
is RegistrationResult.Success -> onSessionCreated(data.session)
is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
}
private fun executeRegistrationStep(withLoading: Boolean = true,
block: suspend (RegistrationWizard) -> RegistrationResult): Job {
if (withLoading) {
setState { copy(asyncRegistration = Loading()) }
}
override fun onFailure(failure: Throwable) {
if (failure !is CancellationException) {
_viewEvents.post(LoginViewEvents.Failure(failure))
return viewModelScope.launch {
try {
registrationWizard?.let { block(it) }
/*
// Simulate registration disabled
throw Failure.ServerError(MatrixError(
code = MatrixError.FORBIDDEN,
message = "Registration is disabled"
), 403))
*/
} catch (failure: Throwable) {
if (failure !is CancellationException) {
_viewEvents.post(LoginViewEvents.Failure(failure))
}
null
}
?.let { data ->
when (data) {
is RegistrationResult.Success -> onSessionCreated(data.session)
is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
}
}
setState {
copy(
asyncRegistration = Uninitialized
@@ -281,78 +288,68 @@ class LoginViewModel @AssistedInject constructor(
private fun handleAddThreePid(action: LoginAction.AddThreePid) {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.addThreePid(action.threePid, object : MatrixCallback<RegistrationResult> {
override fun onSuccess(data: RegistrationResult) {
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
override fun onFailure(failure: Throwable) {
currentJob = viewModelScope.launch {
try {
registrationWizard?.addThreePid(action.threePid)
} catch (failure: Throwable) {
_viewEvents.post(LoginViewEvents.Failure(failure))
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
})
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
}
private fun handleSendAgainThreePid() {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.sendAgainThreePid(object : MatrixCallback<RegistrationResult> {
override fun onSuccess(data: RegistrationResult) {
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
override fun onFailure(failure: Throwable) {
currentJob = viewModelScope.launch {
try {
registrationWizard?.sendAgainThreePid()
} catch (failure: Throwable) {
_viewEvents.post(LoginViewEvents.Failure(failure))
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
})
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
}
private fun handleAcceptTerms() {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.acceptTerms(registrationCallback)
currentJob = executeRegistrationStep {
it.acceptTerms()
}
}
private fun handleRegisterDummy() {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.dummy(registrationCallback)
currentJob = executeRegistrationStep {
it.dummy()
}
}
private fun handleRegisterWith(action: LoginAction.LoginOrRegister) {
setState { copy(asyncRegistration = Loading()) }
reAuthHelper.data = action.password
currentTask = registrationWizard?.createAccount(
action.username,
action.password,
action.initialDeviceName,
registrationCallback
)
currentJob = executeRegistrationStep {
it.createAccount(
action.username,
action.password,
action.initialDeviceName
)
}
}
private fun handleCaptchaDone(action: LoginAction.CaptchaDone) {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.performReCaptcha(action.captchaResponse, registrationCallback)
currentJob = executeRegistrationStep {
it.performReCaptcha(action.captchaResponse)
}
}
private fun handleResetAction(action: LoginAction.ResetAction) {
// Cancel any request
currentTask?.cancel()
currentTask = null
currentJob = null
when (action) {
LoginAction.ResetHomeServerType -> {
@@ -363,16 +360,17 @@ class LoginViewModel @AssistedInject constructor(
}
}
LoginAction.ResetHomeServerUrl -> {
authenticationService.reset()
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized,
homeServerUrl = null,
loginMode = LoginMode.Unknown,
serverType = ServerType.Unknown,
loginModeSupportedTypes = emptyList()
)
viewModelScope.launch {
authenticationService.reset()
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized,
homeServerUrl = null,
loginMode = LoginMode.Unknown,
serverType = ServerType.Unknown,
loginModeSupportedTypes = emptyList()
)
}
}
}
LoginAction.ResetSignMode -> {
@@ -386,13 +384,14 @@ class LoginViewModel @AssistedInject constructor(
}
}
LoginAction.ResetLogin -> {
authenticationService.cancelPendingLoginOrRegistration()
setState {
copy(
asyncLoginAction = Uninitialized,
asyncRegistration = Uninitialized
)
viewModelScope.launch {
authenticationService.cancelPendingLoginOrRegistration()
setState {
copy(
asyncLoginAction = Uninitialized,
asyncRegistration = Uninitialized
)
}
}
}
LoginAction.ResetResetPassword -> {
@@ -473,26 +472,27 @@ class LoginViewModel @AssistedInject constructor(
)
}
currentTask = safeLoginWizard.resetPassword(action.email, action.newPassword, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
setState {
copy(
asyncResetPassword = Success(data),
resetPasswordEmail = action.email
)
}
_viewEvents.post(LoginViewEvents.OnResetPasswordSendThreePidDone)
}
override fun onFailure(failure: Throwable) {
currentJob = viewModelScope.launch {
try {
safeLoginWizard.resetPassword(action.email, action.newPassword)
} catch (failure: Throwable) {
setState {
copy(
asyncResetPassword = Fail(failure)
)
}
return@launch
}
})
setState {
copy(
asyncResetPassword = Success(Unit),
resetPasswordEmail = action.email
)
}
_viewEvents.post(LoginViewEvents.OnResetPasswordSendThreePidDone)
}
}
}
@@ -514,26 +514,26 @@ class LoginViewModel @AssistedInject constructor(
)
}
currentTask = safeLoginWizard.resetPasswordMailConfirmed(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
setState {
copy(
asyncResetMailConfirmed = Success(data),
resetPasswordEmail = null
)
}
_viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess)
}
override fun onFailure(failure: Throwable) {
currentJob = viewModelScope.launch {
try {
safeLoginWizard.resetPasswordMailConfirmed()
} catch (failure: Throwable) {
setState {
copy(
asyncResetMailConfirmed = Fail(failure)
)
}
return@launch
}
})
setState {
copy(
asyncResetMailConfirmed = Success(Unit),
resetPasswordEmail = null
)
}
_viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess)
}
}
}
@@ -553,36 +553,36 @@ class LoginViewModel @AssistedInject constructor(
)
}
authenticationService.getWellKnownData(action.username, homeServerConnectionConfig, object : MatrixCallback<WellknownResult> {
override fun onSuccess(data: WellknownResult) {
when (data) {
is WellknownResult.Prompt ->
onWellknownSuccess(action, data, homeServerConnectionConfig)
is WellknownResult.FailPrompt ->
// Relax on IS discovery if home server is valid
if (data.homeServerUrl != null && data.wellKnown != null) {
onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig)
} else {
onWellKnownError()
}
is WellknownResult.InvalidMatrixId -> {
setState {
copy(
asyncLoginAction = Uninitialized
)
}
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id))))
}
else -> {
currentJob = viewModelScope.launch {
val data = try {
authenticationService.getWellKnownData(action.username, homeServerConnectionConfig)
} catch (failure: Throwable) {
onDirectLoginError(failure)
return@launch
}
when (data) {
is WellknownResult.Prompt ->
onWellknownSuccess(action, data, homeServerConnectionConfig)
is WellknownResult.FailPrompt ->
// Relax on IS discovery if home server is valid
if (data.homeServerUrl != null && data.wellKnown != null) {
onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig)
} else {
onWellKnownError()
}
}.exhaustive
}
override fun onFailure(failure: Throwable) {
onDirectLoginError(failure)
}
})
is WellknownResult.InvalidMatrixId -> {
setState {
copy(
asyncLoginAction = Uninitialized
)
}
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id))))
}
else -> {
onWellKnownError()
}
}.exhaustive
}
}
private fun onWellKnownError() {
@@ -594,9 +594,9 @@ class LoginViewModel @AssistedInject constructor(
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error))))
}
private fun onWellknownSuccess(action: LoginAction.LoginOrRegister,
wellKnownPrompt: WellknownResult.Prompt,
homeServerConnectionConfig: HomeServerConnectionConfig?) {
private suspend fun onWellknownSuccess(action: LoginAction.LoginOrRegister,
wellKnownPrompt: WellknownResult.Prompt,
homeServerConnectionConfig: HomeServerConnectionConfig?) {
val alteredHomeServerConnectionConfig = homeServerConnectionConfig
?.copy(
homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
@@ -607,20 +607,17 @@ class LoginViewModel @AssistedInject constructor(
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
)
authenticationService.directAuthentication(
alteredHomeServerConnectionConfig,
action.username,
action.password,
action.initialDeviceName,
object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
onSessionCreated(data)
}
override fun onFailure(failure: Throwable) {
onDirectLoginError(failure)
}
})
val data = try {
authenticationService.directAuthentication(
alteredHomeServerConnectionConfig,
action.username,
action.password,
action.initialDeviceName)
} catch (failure: Throwable) {
onDirectLoginError(failure)
return
}
onSessionCreated(data)
}
private fun onDirectLoginError(failure: Throwable) {
@@ -657,35 +654,33 @@ class LoginViewModel @AssistedInject constructor(
)
}
currentTask = safeLoginWizard.login(
action.username,
action.password,
action.initialDeviceName,
object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
currentJob = viewModelScope.launch {
try {
safeLoginWizard.login(
action.username,
action.password,
action.initialDeviceName
)
} catch (failure: Throwable) {
setState {
copy(
asyncLoginAction = Fail(failure)
)
}
null
}
?.let {
reAuthHelper.data = action.password
onSessionCreated(data)
onSessionCreated(it)
}
override fun onFailure(failure: Throwable) {
setState {
copy(
asyncLoginAction = Fail(failure)
)
}
}
})
}
}
}
private fun startRegistrationFlow() {
setState {
copy(
asyncRegistration = Loading()
)
currentJob = executeRegistrationStep {
it.getRegistrationFlow()
}
currentTask = registrationWizard?.getRegistrationFlow(registrationCallback)
}
private fun startAuthenticationFlow() {
@@ -706,8 +701,9 @@ class LoginViewModel @AssistedInject constructor(
}
}
private fun onSessionCreated(session: Session) {
private suspend fun onSessionCreated(session: Session) {
activeSessionHolder.setActiveSession(session)
authenticationService.reset()
session.configureAndStart(applicationContext)
setState {
@@ -724,15 +720,17 @@ class LoginViewModel @AssistedInject constructor(
// Should not happen
Timber.w("homeServerConnectionConfig is null")
} else {
authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials, object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
onSessionCreated(data)
currentJob = viewModelScope.launch {
try {
authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials)
} catch (failure: Throwable) {
setState {
copy(asyncLoginAction = Fail(failure))
}
null
}
override fun onFailure(failure: Throwable) = setState {
copy(asyncLoginAction = Fail(failure))
}
})
?.let { onSessionCreated(it) }
}
}
}
@@ -749,21 +747,21 @@ class LoginViewModel @AssistedInject constructor(
private fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig) {
currentHomeServerConnectionConfig = homeServerConnectionConfig
currentTask?.cancel()
currentTask = null
authenticationService.cancelPendingLoginOrRegistration()
currentJob = viewModelScope.launch {
authenticationService.cancelPendingLoginOrRegistration()
setState {
copy(
asyncHomeServerLoginFlowRequest = Loading(),
// If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg
// It is also useful to set the value again in the case of a certificate error on matrix.org
serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) ServerType.MatrixOrg else serverType
)
}
setState {
copy(
asyncHomeServerLoginFlowRequest = Loading(),
// If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg
// It is also useful to set the value again in the case of a certificate error on matrix.org
serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) ServerType.MatrixOrg else serverType
)
}
currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback<LoginFlowResult> {
override fun onFailure(failure: Throwable) {
val data = try {
authenticationService.getLoginFlow(homeServerConnectionConfig)
} catch (failure: Throwable) {
_viewEvents.post(LoginViewEvents.Failure(failure))
setState {
copy(
@@ -772,47 +770,39 @@ class LoginViewModel @AssistedInject constructor(
serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType
)
}
null
}
override fun onSuccess(data: LoginFlowResult) {
if (data is LoginFlowResult.Success) {
// Valid Homeserver, add it to the history.
// Note: we add what the user has input, data.homeServerUrl can be different
rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString())
when (data) {
is LoginFlowResult.Success -> {
val loginMode = when {
// SSO login is taken first
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported
}
val loginMode = when {
// SSO login is taken first
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported
}
// FIXME We should post a view event here normally?
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized,
homeServerUrl = data.homeServerUrl,
loginMode = loginMode,
loginModeSupportedTypes = data.supportedLoginTypes.toList()
)
}
if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported)
|| data.isOutdatedHomeserver) {
// Notify the UI
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
}
}
// FIXME We should post a view event here normally?
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized,
homeServerUrl = data.homeServerUrl,
loginMode = loginMode,
loginModeSupportedTypes = data.supportedLoginTypes.toList()
)
}
if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported)
|| data.isOutdatedHomeserver) {
// Notify the UI
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
}
}
})
}
override fun onCleared() {
currentTask?.cancel()
super.onCleared()
}
}
fun getInitialHomeServerUrl(): String? {

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 2021 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.app.features.rageshake
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.logging.Formatter
import java.util.logging.LogRecord
class LogFormatter : Formatter() {
override fun format(r: LogRecord): String {
if (!mIsTimeZoneSet) {
DATE_FORMAT.timeZone = TimeZone.getTimeZone("UTC")
mIsTimeZoneSet = true
}
val thrown = r.thrown
if (thrown != null) {
val sw = StringWriter()
val pw = PrintWriter(sw)
sw.write(r.message)
sw.write(LINE_SEPARATOR)
thrown.printStackTrace(pw)
pw.flush()
return sw.toString()
} else {
val b = StringBuilder()
val date = DATE_FORMAT.format(Date(r.millis))
b.append(date)
b.append("Z ")
b.append(r.message)
b.append(LINE_SEPARATOR)
return b.toString()
}
}
companion object {
private val LINE_SEPARATOR = System.getProperty("line.separator") ?: "\n"
// private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss*SSSZZZZ", Locale.US)
private var mIsTimeZoneSet = false
}
}

View File

@@ -22,45 +22,41 @@ import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import timber.log.Timber
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.logging.FileHandler
import java.util.logging.Formatter
import java.util.logging.Level
import java.util.logging.LogRecord
import java.util.logging.Logger
import javax.inject.Inject
import javax.inject.Singleton
private const val SIZE_20MB = 20 * 1024 * 1024
private const val SIZE_50MB = 50 * 1024 * 1024
@Singleton
class VectorFileLogger @Inject constructor(val context: Context, private val vectorPreferences: VectorPreferences) : Timber.Tree() {
class VectorFileLogger @Inject constructor(
context: Context,
private val vectorPreferences: VectorPreferences
) : Timber.Tree() {
private val maxLogSizeByte: Int
private val logRotationCount: Int
companion object {
private const val SIZE_20MB = 20 * 1024 * 1024
private const val SIZE_50MB = 50 * 1024 * 1024
}
init {
if (vectorPreferences.labAllowedExtendedLogging()) {
maxLogSizeByte = SIZE_50MB
logRotationCount = 15
} else {
maxLogSizeByte = SIZE_20MB
logRotationCount = 7
private val maxLogSizeByte = if (vectorPreferences.labAllowedExtendedLogging()) SIZE_50MB else SIZE_20MB
private val logRotationCount = if (vectorPreferences.labAllowedExtendedLogging()) 15 else 7
private val logger = Logger.getLogger(context.packageName).apply {
tryOrNull {
useParentHandlers = false
level = Level.ALL
}
}
private val sLogger = Logger.getLogger("im.vector.app")
private var sFileHandler: FileHandler? = null
private var sCacheDirectory: File? = null
private var sFileName = "elementLogs"
private val fileHandler: FileHandler?
private val cacheDirectory = File(context.cacheDir, "logs")
private var fileNamePrefix = "logs"
private val prioPrefixes = mapOf(
Log.VERBOSE to "V/ ",
@@ -72,24 +68,29 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
)
init {
val logsDirectoryFile = context.cacheDir.absolutePath + "/logs"
setLogDirectory(File(logsDirectoryFile))
try {
if (sCacheDirectory != null) {
sFileHandler = FileHandler(sCacheDirectory!!.absolutePath + "/" + sFileName + ".%g.txt", maxLogSizeByte, logRotationCount)
sFileHandler?.formatter = LogFormatter()
sLogger.useParentHandlers = false
sLogger.level = Level.ALL
sFileHandler?.let { sLogger.addHandler(it) }
}
} catch (e: Throwable) {
Timber.e(e, "Failed to initialize FileLogger")
if (!cacheDirectory.exists()) {
cacheDirectory.mkdirs()
}
for (i in 0..15) {
val file = File(cacheDirectory, "elementLogs.$i.txt")
tryOrNull { file.delete() }
}
fileHandler = tryOrNull("Failed to initialize FileLogger") {
FileHandler(
cacheDirectory.absolutePath + "/" + fileNamePrefix + ".%g.txt",
maxLogSizeByte,
logRotationCount
)
.also { it.formatter = LogFormatter() }
.also { logger.addHandler(it) }
}
}
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
fileHandler ?: return
GlobalScope.launch(Dispatchers.IO) {
if (sFileHandler == null) return@launch
if (skipLog(priority)) return@launch
if (t != null) {
logToFile(t)
@@ -107,84 +108,22 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
}
}
/**
* Set the directory to put log files.
*
* @param cacheDir The directory, usually [android.content.ContextWrapper.getCacheDir]
*/
private fun setLogDirectory(cacheDir: File) {
if (!cacheDir.exists()) {
cacheDir.mkdirs()
}
sCacheDirectory = cacheDir
}
/**
* Adds our own log files to the provided list of files.
*
* @param files The list of files to add to.
* @return The same list with more files added.
* @return The list of files with logs.
*/
fun getLogFiles(): List<File> {
val files = ArrayList<File>()
try {
// reported by GA
if (null != sFileHandler) {
sFileHandler!!.flush()
val absPath = sCacheDirectory?.absolutePath ?: return emptyList()
for (i in 0..logRotationCount) {
val filepath = "$absPath/$sFileName.$i.txt"
val file = File(filepath)
if (file.exists()) {
files.add(file)
return tryOrNull("## getLogFiles() failed") {
fileHandler
?.flush()
?.let { 0 until logRotationCount }
?.mapNotNull { index ->
File(cacheDirectory, "$fileNamePrefix.$index.txt")
.takeIf { it.exists() }
}
}
}
} catch (e: Exception) {
Timber.e(e, "## addLogFiles() failed")
}
return files
}
class LogFormatter : Formatter() {
override fun format(r: LogRecord): String {
if (!mIsTimeZoneSet) {
DATE_FORMAT.timeZone = TimeZone.getTimeZone("UTC")
mIsTimeZoneSet = true
}
val thrown = r.thrown
if (thrown != null) {
val sw = StringWriter()
val pw = PrintWriter(sw)
sw.write(r.message)
sw.write(LINE_SEPARATOR)
thrown.printStackTrace(pw)
pw.flush()
return sw.toString()
} else {
val b = StringBuilder()
val date = DATE_FORMAT.format(Date(r.millis))
b.append(date)
b.append("Z ")
b.append(r.message)
b.append(LINE_SEPARATOR)
return b.toString()
}
}
companion object {
private val LINE_SEPARATOR = System.getProperty("line.separator") ?: "\n"
// private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss*SSSZZZZ", Locale.US)
private var mIsTimeZoneSet = false
}
.orEmpty()
}
/**
@@ -193,20 +132,15 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
* @param throwable the throwable to log
*/
private fun logToFile(throwable: Throwable?) {
if (null == sCacheDirectory || throwable == null) {
return
}
throwable ?: return
val errors = StringWriter()
throwable.printStackTrace(PrintWriter(errors))
sLogger.info(errors.toString())
logger.info(errors.toString())
}
private fun logToFile(level: String, tag: String, content: String) {
if (null == sCacheDirectory) {
return
}
val b = StringBuilder()
b.append(Thread.currentThread().id)
b.append(" ")
@@ -215,6 +149,6 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
b.append(tag)
b.append(": ")
b.append(content)
sLogger.info(b.toString())
logger.info(b.toString())
}
}

View File

@@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth
import timber.log.Timber
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
data class DeactivateAccountViewState(
val passwordShown: Boolean = false
@@ -64,7 +65,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
if (pendingAuth != null) {
uiaContinuation?.resume(pendingAuth!!)
} else {
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException())))
uiaContinuation?.resumeWithException(IllegalArgumentException())
}
}
is DeactivateAccountAction.PasswordAuthDone -> {
@@ -79,7 +80,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
}
DeactivateAccountAction.ReAuthCancelled -> {
Timber.d("## UIA - Reauth cancelled")
uiaContinuation?.resumeWith(Result.failure((Exception())))
uiaContinuation?.resumeWithException(Exception())
uiaContinuation = null
pendingAuth = null
}
@@ -98,13 +99,15 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
viewModelScope.launch {
val event = try {
session.deactivateAccount(
action.eraseAllData,
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
_viewEvents.post(DeactivateAccountViewEvents.RequestReAuth(flowResponse, errCode))
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
uiaContinuation = promise
}
}, action.eraseAllData)
}
)
DeactivateAccountViewEvents.Done
} catch (failure: Exception) {
if (failure.isInvalidUIAAuth()) {

View File

@@ -49,6 +49,7 @@ import org.matrix.android.sdk.rx.rx
import timber.log.Timber
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class CrossSigningSettingsViewModel @AssistedInject constructor(
@Assisted private val initialState: CrossSigningSettingsViewState,
@@ -130,7 +131,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
if (pendingAuth != null) {
uiaContinuation?.resume(pendingAuth!!)
} else {
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException())))
uiaContinuation?.resumeWithException(IllegalArgumentException())
}
}
is CrossSigningSettingsAction.PasswordAuthDone -> {
@@ -146,7 +147,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
CrossSigningSettingsAction.ReAuthCancelled -> {
Timber.d("## UIA - Reauth cancelled")
_viewEvents.post(CrossSigningSettingsViewEvents.HideModalWaitingView)
uiaContinuation?.resumeWith(Result.failure((Exception())))
uiaContinuation?.resumeWithException(Exception())
uiaContinuation = null
pendingAuth = null
}

View File

@@ -64,6 +64,7 @@ import java.util.concurrent.TimeUnit
import javax.net.ssl.HttpsURLConnection
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
data class DevicesViewState(
val myDeviceId: String = "",
@@ -217,7 +218,7 @@ class DevicesViewModel @AssistedInject constructor(
if (pendingAuth != null) {
uiaContinuation?.resume(pendingAuth!!)
} else {
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException())))
uiaContinuation?.resumeWithException(IllegalArgumentException())
}
Unit
}
@@ -235,7 +236,7 @@ class DevicesViewModel @AssistedInject constructor(
DevicesAction.ReAuthCancelled -> {
Timber.d("## UIA - Reauth cancelled")
// _viewEvents.post(DevicesViewEvents.Loading)
uiaContinuation?.resumeWith(Result.failure((Exception())))
uiaContinuation?.resumeWithException(Exception())
uiaContinuation = null
pendingAuth = null
}

View File

@@ -46,6 +46,7 @@ import org.matrix.android.sdk.rx.rx
import timber.log.Timber
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class ThreePidsSettingsViewModel @AssistedInject constructor(
@Assisted initialState: ThreePidsSettingsViewState,
@@ -140,7 +141,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor(
if (pendingAuth != null) {
uiaContinuation?.resume(pendingAuth!!)
} else {
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException())))
uiaContinuation?.resumeWithException(IllegalArgumentException())
}
}
is ThreePidsSettingsAction.PasswordAuthDone -> {
@@ -155,7 +156,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor(
}
ThreePidsSettingsAction.ReAuthCancelled -> {
Timber.d("## UIA - Reauth cancelled")
uiaContinuation?.resumeWith(Result.failure((Exception())))
uiaContinuation?.resumeWithException(Exception())
uiaContinuation = null
pendingAuth = null
}

View File

@@ -25,19 +25,17 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.hasUnsavedKeys
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.login.LoginMode
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
import timber.log.Timber
/**
@@ -76,54 +74,49 @@ class SoftLogoutViewModel @AssistedInject constructor(
}
}
private var currentTask: Cancelable? = null
init {
// Get the supported login flow
getSupportedLoginFlow()
}
private fun getSupportedLoginFlow() {
currentTask?.cancel()
currentTask = null
authenticationService.cancelPendingLoginOrRegistration()
viewModelScope.launch {
authenticationService.cancelPendingLoginOrRegistration()
setState {
copy(
asyncHomeServerLoginFlowRequest = Loading()
)
}
setState {
copy(
asyncHomeServerLoginFlowRequest = Loading()
)
}
currentTask = authenticationService.getLoginFlowOfSession(session.sessionId, object : MatrixCallback<LoginFlowResult> {
override fun onFailure(failure: Throwable) {
val data = try {
authenticationService.getLoginFlowOfSession(session.sessionId)
} catch (failure: Throwable) {
setState {
copy(
asyncHomeServerLoginFlowRequest = Fail(failure)
)
}
null
}
override fun onSuccess(data: LoginFlowResult) {
when (data) {
is LoginFlowResult.Success -> {
val loginMode = when {
// SSO login is taken first
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported
}
if (data is LoginFlowResult.Success) {
val loginMode = when {
// SSO login is taken first
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported
}
setState {
copy(
asyncHomeServerLoginFlowRequest = Success(loginMode)
)
}
}
setState {
copy(
asyncHomeServerLoginFlowRequest = Success(loginMode)
)
}
}
})
}
}
override fun handle(action: SoftLogoutAction) {
@@ -227,9 +220,4 @@ class SoftLogoutViewModel @AssistedInject constructor(
)
}
}
override fun onCleared() {
currentTask?.cancel()
super.onCleared()
}
}

View File

@@ -81,17 +81,19 @@
app:layout_constraintTop_toBottomOf="@+id/loginSignupSigninSubmit"
tools:visibility="visible" />
<!-- Social Logins buttons -->
<!-- Social Login buttons -->
<LinearLayout
android:id="@+id/loginSignupSigninSignInSocialLoginContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginSignupSigninSignIn">
app:layout_constraintTop_toBottomOf="@id/loginSignupSigninSignIn"
tools:visibility="visible">
<TextView
android:id="@+id/loginSignupSigninSocialLoginHeader"

View File

@@ -1,55 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="VectorStyles">
<!-- application bar text color -->
<attr name="vctr_toolbar_primary_text_color" format="color" />
<attr name="vctr_toolbar_secondary_text_color" format="color" />
<attr name="vctr_toolbar_link_text_color" format="color" />
<!-- application bar text color -->
<attr name="vctr_toolbar_primary_text_color" format="color" />
<attr name="vctr_toolbar_secondary_text_color" format="color" />
<attr name="vctr_toolbar_link_text_color" format="color" />
<!-- default text colors -->
<attr name="vctr_default_text_hint_color" format="color" />
<!-- default text colors -->
<attr name="vctr_default_text_hint_color" format="color" />
<!-- room message colors -->
<attr name="vctr_unsent_message_text_color" format="color" />
<attr name="vctr_message_text_color" format="color" />
<attr name="vctr_notice_text_color" format="color" />
<attr name="vctr_notice_secondary" format="color" />
<attr name="vctr_encrypting_message_text_color" format="color" />
<attr name="vctr_sending_message_text_color" format="color" />
<attr name="vctr_markdown_block_background_color" format="color" />
<attr name="vctr_spoiler_background_color" format="color" />
<!-- room message colors -->
<attr name="vctr_unsent_message_text_color" format="color" />
<attr name="vctr_message_text_color" format="color" />
<attr name="vctr_notice_text_color" format="color" />
<attr name="vctr_notice_secondary" format="color" />
<attr name="vctr_encrypting_message_text_color" format="color" />
<attr name="vctr_sending_message_text_color" format="color" />
<attr name="vctr_markdown_block_background_color" format="color" />
<attr name="vctr_spoiler_background_color" format="color" />
<!-- tab bar colors -->
<attr name="vctr_tab_bar_inverted_background_color" format="color" />
<!-- tab bar colors -->
<attr name="vctr_tab_bar_inverted_background_color" format="color" />
<!-- list colors -->
<attr name="vctr_list_header_background_color" format="color" />
<attr name="vctr_list_header_primary_text_color" format="color" />
<attr name="vctr_list_header_secondary_text_color" format="color" />
<!-- list colors -->
<attr name="vctr_list_header_background_color" format="color" />
<attr name="vctr_list_header_primary_text_color" format="color" />
<attr name="vctr_list_header_secondary_text_color" format="color" />
<attr name="vctr_list_divider_color" format="color" />
<attr name="vctr_list_divider_color" format="color" />
<!-- outgoing call background color -->
<attr name="vctr_pending_outgoing_view_background_color" format="color" />
<!-- outgoing call background color -->
<attr name="vctr_pending_outgoing_view_background_color" format="color" />
<!-- room notification text color (typing, unsent...) -->
<attr name="vctr_room_notification_text_color" format="color" />
<!-- room notification text color (typing, unsent...) -->
<attr name="vctr_room_notification_text_color" format="color" />
<!-- icon colors -->
<attr name="vctr_icon_tint_on_light_action_bar_color" format="color" />
<attr name="vctr_settings_icon_tint_color" format="color" />
<!-- icon colors -->
<attr name="vctr_icon_tint_on_light_action_bar_color" format="color" />
<attr name="vctr_settings_icon_tint_color" format="color" />
<attr name="vctr_social_login_button_google_style" format="reference" />
<attr name="vctr_social_login_button_github_style" format="reference" />
<attr name="vctr_social_login_button_facebook_style" format="reference" />
<attr name="vctr_social_login_button_twitter_style" format="reference" />
<attr name="vctr_social_login_button_apple_style" format="reference" />
<attr name="vctr_social_login_button_gitlab_style" format="reference" />
<attr name="vctr_social_login_button_google_style" format="reference" />
<attr name="vctr_social_login_button_github_style" format="reference" />
<attr name="vctr_social_login_button_facebook_style" format="reference" />
<attr name="vctr_social_login_button_twitter_style" format="reference" />
<attr name="vctr_social_login_button_apple_style" format="reference" />
<attr name="vctr_social_login_button_gitlab_style" format="reference" />
<attr name="vctr_chat_effect_snow_background" format="color" />
</declare-styleable>
<attr name="vctr_chat_effect_snow_background" format="color" />
<declare-styleable name="PollResultLineView">
<attr name="optionName" format="string" localization="suggested" />
@@ -70,16 +67,16 @@
<declare-styleable name="SignOutBottomSheetActionButton">
<attr name="iconTint" format="color" />
<attr name="actionTitle"/>
<attr name="actionTitle" />
<attr name="leftIcon" />
<attr name="textColor" format="color" />
</declare-styleable>
<declare-styleable name="SocialLoginButtonsView">
<attr name="signMode" format="enum">
<enum name="signin" value="0"/>
<enum name="signup" value="1"/>
<enum name="continue_with" value="2"/>
<enum name="signin" value="0" />
<enum name="signup" value="1" />
<enum name="continue_with" value="2" />
</attr>
</declare-styleable>
</resources>

View File

@@ -1,7 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Source: https://zpl.io/aBKw9Mk -->
<!-- Error colors -->
<color name="vector_success_color">#70BF56</color>
<color name="vector_warning_color">#ff4b55</color>
<color name="vector_error_color">#ff4b55</color>
<color name="vector_info_color">#2f9edb</color>
<!-- main app colors -->
<color name="vector_fuchsia_color">#ff4b55</color>
<color name="vector_silver_color">#FFC7C7C7</color>
<color name="vector_dark_grey_color">#FF999999</color>
<!-- theses colours are requested a background cannot be set by an ?attr on android < 5 -->
<!-- dedicated drawables are created for each theme -->
<!-- Default/Background-->
<color name="riot_primary_background_color_light">#FFFFFFFF</color>
<!-- Dark/Background-->
<color name="riot_primary_background_color_dark">#FF181B21</color>
<!-- Black/Background-->
<color name="riot_primary_background_color_black">#F000</color>
<!--Default/Android Status Bar-->
<color name="primary_color_dark_light">#FF1A2027</color>
<!--Default/Base-->
<color name="primary_color_light">#03b381</color>
<!--Default/Accent-->
<color name="accent_color_light">#03b381</color>
<!--Dark/Android Status Bar-->
<color name="primary_color_dark_dark">#FF0D0E10</color>
<!--Dark/Base-->
<color name="primary_color_dark">#FF15171B</color>
<!--Dark/Accent-->
<color name="accent_color_dark">#03b381</color>
<!--Black/Android Status Bar-->
<color name="primary_color_dark_black">#000</color>
<!--Black/Base-->
<color name="primary_color_black">#FF060708</color>
<!--Default/Line break mobile-->
<attr name="list_divider_color" format="color" />
<color name="list_divider_color_light">#EEEFEF</color>
<!--Dark/Line break mobile-->
<color name="list_divider_color_dark">#FF61708B</color>
<!--Black/Line break mobile-->
<color name="list_divider_color_black">#FF22262E</color>
<attr name="tab_bar_selected_background_color" format="color" />
<color name="tab_bar_selected_background_color_light">@color/riotx_android_secondary_light</color>
<color name="tab_bar_selected_background_color_dark">@color/riotx_android_secondary_dark</color>
<attr name="tab_bar_unselected_background_color" format="color" />
<color name="tab_bar_unselected_background_color_light">@color/riotx_background_light</color>
<color name="tab_bar_unselected_background_color_dark">@color/riotx_background_dark</color>
<!-- Hint Colors -->
<color name="primary_hint_text_color_light">#FFFFFF</color>
<color name="primary_hint_text_color_dark">#FFFFFF</color>
<color name="default_text_hint_color_light">#903C3C3C</color>
<color name="default_text_hint_color_dark">#CCDDDDDD</color>
<!-- Text Colors -->
<attr name="riot_primary_text_color" format="color" />
<attr name="riot_primary_text_color_disabled" format="color" />
<!--Default/Text Primary-->
<color name="riot_primary_text_color_light">#FF2E2F32</color>
<color name="riot_primary_text_color_disabled_light">#FF9E9E9E</color>
<!--Default/Text Secondary-->
<color name="riot_secondary_text_color_light">#FF9E9E9E</color>
<color name="riot_tertiary_text_color_light">@color/riot_primary_text_color_light</color>
<!--Dark /Text Primary-->
<color name="riot_primary_text_color_dark">#FFEDF3FF</color>
<color name="riot_primary_text_color_disabled_dark">#FFA1B2D1</color>
<!--Dark /Text Secondary-->
<color name="riot_secondary_text_color_dark">#FFA1B2D1</color>
<color name="riot_tertiary_text_color_dark">@color/riot_primary_text_color_dark</color>
<!-- Notification view colors -->
<color name="soft_resource_limit_exceeded">#2f9edb</color>
<color name="hard_resource_limit_exceeded">@color/vector_fuchsia_color</color>
<!-- Password Strength bar colors -->
<color name="password_strength_bar_weak">#FFF56679</color>
<color name="password_strength_bar_low">#FFFFC666</color>
<color name="password_strength_bar_ok">#FFF8E71C</color>
<color name="password_strength_bar_strong">#FF7AC9A1</color>
<color name="password_strength_bar_undefined">#FF9E9E9E</color>
<!-- Button color -->
<color name="button_enabled_text_color">#FFFFFFFF</color>
<color name="button_disabled_text_color">#FFFFFFFF</color>
<color name="button_destructive_enabled_text_color">#FF4B55</color>
<color name="button_destructive_disabled_text_color">#FF4B55</color>
<color name="button_bot_enabled_text_color">#FF368BD6</color>
<color name="button_bot_disabled_text_color">#61708B</color>
<!-- Link color -->
<color name="link_color_light">#368BD6</color>
<color name="link_color_dark">#368BD6</color>
<!-- Notification (do not depends on theme) -->
<color name="notification_accent_color">#368BD6</color>
<color name="key_share_req_accent_color">#ff812d</color>
<!-- Source: https://zpl.io/aBKw9Mk -->
<!-- Accents -->
<color name="riotx_accent">#FF0DBD8B</color>
@@ -38,7 +146,7 @@
<color name="riotx_username_7">#5c56f5</color>
<color name="riotx_username_8">#74d12c</color>
<!-- Other usefull color -->
<!-- Other useful color -->
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="black_alpha">#55000000</color>
@@ -256,6 +364,4 @@
<color name="riotx_keys_backup_banner_accent_color_light">#FFF8E3</color>
<color name="riotx_keys_backup_banner_accent_color_dark">#22262E</color>
</resources>
</resources>

View File

@@ -1,112 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Error colors -->
<color name="vector_success_color">#70BF56</color>
<color name="vector_warning_color">#ff4b55</color>
<color name="vector_error_color">#ff4b55</color>
<color name="vector_info_color">#2f9edb</color>
<!-- main app colors -->
<color name="vector_fuchsia_color">#ff4b55</color>
<color name="vector_silver_color">#FFC7C7C7</color>
<color name="vector_dark_grey_color">#FF999999</color>
<!-- theses colours are requested a background cannot be set by an ?attr on android < 5 -->
<!-- dedicated drawables are created for each theme -->
<!-- Default/Background-->
<color name="riot_primary_background_color_light">#FFFFFFFF</color>
<!-- Dark/Background-->
<color name="riot_primary_background_color_dark">#FF181B21</color>
<!-- Black/Background-->
<color name="riot_primary_background_color_black">#F000</color>
<!--Default/Android Status Bar-->
<color name="primary_color_dark_light">#FF1A2027</color>
<!--Default/Base-->
<color name="primary_color_light">#03b381</color>
<!--Default/Accent-->
<color name="accent_color_light">#03b381</color>
<!--Dark/Android Status Bar-->
<color name="primary_color_dark_dark">#FF0D0E10</color>
<!--Dark/Base-->
<color name="primary_color_dark">#FF15171B</color>
<!--Dark/Accent-->
<color name="accent_color_dark">#03b381</color>
<!--Black/Android Status Bar-->
<color name="primary_color_dark_black">#000</color>
<!--Black/Base-->
<color name="primary_color_black">#FF060708</color>
<!--Default/Line break mobile-->
<attr name="list_divider_color" format="color" />
<color name="list_divider_color_light">#EEEFEF</color>
<!--Dark/Line break mobile-->
<color name="list_divider_color_dark">#FF61708B</color>
<!--Black/Line break mobile-->
<color name="list_divider_color_black">#FF22262E</color>
<attr name="tab_bar_selected_background_color" format="color" />
<color name="tab_bar_selected_background_color_light">@color/riotx_android_secondary_light</color>
<color name="tab_bar_selected_background_color_dark">@color/riotx_android_secondary_dark</color>
<attr name="tab_bar_unselected_background_color" format="color" />
<color name="tab_bar_unselected_background_color_light">@color/riotx_background_light</color>
<color name="tab_bar_unselected_background_color_dark">@color/riotx_background_dark</color>
<!-- Hint Colors -->
<color name="primary_hint_text_color_light">#FFFFFF</color>
<color name="primary_hint_text_color_dark">#FFFFFF</color>
<color name="default_text_hint_color_light">#903C3C3C</color>
<color name="default_text_hint_color_dark">#CCDDDDDD</color>
<!-- Text Colors -->
<attr name="riot_primary_text_color" format="color" />
<attr name="riot_primary_text_color_disabled" format="color" />
<!--Default/Text Primary-->
<color name="riot_primary_text_color_light">#FF2E2F32</color>
<color name="riot_primary_text_color_disabled_light">#FF9E9E9E</color>
<!--Default/Text Secondary-->
<color name="riot_secondary_text_color_light">#FF9E9E9E</color>
<color name="riot_tertiary_text_color_light">@color/riot_primary_text_color_light</color>
<!--Dark /Text Primary-->
<color name="riot_primary_text_color_dark">#FFEDF3FF</color>
<color name="riot_primary_text_color_disabled_dark">#FFA1B2D1</color>
<!--Dark /Text Secondary-->
<color name="riot_secondary_text_color_dark">#FFA1B2D1</color>
<color name="riot_tertiary_text_color_dark">@color/riot_primary_text_color_dark</color>
<!-- Notification view colors -->
<color name="soft_resource_limit_exceeded">#2f9edb</color>
<color name="hard_resource_limit_exceeded">@color/vector_fuchsia_color</color>
<!-- Password Strength bar colors -->
<color name="password_strength_bar_weak">#FFF56679</color>
<color name="password_strength_bar_low">#FFFFC666</color>
<color name="password_strength_bar_ok">#FFF8E71C</color>
<color name="password_strength_bar_strong">#FF7AC9A1</color>
<color name="password_strength_bar_undefined">#FF9E9E9E</color>
<!-- Button color -->
<color name="button_enabled_text_color">#FFFFFFFF</color>
<color name="button_disabled_text_color">#FFFFFFFF</color>
<color name="button_destructive_enabled_text_color">#FF4B55</color>
<color name="button_destructive_disabled_text_color">#FF4B55</color>
<color name="button_bot_enabled_text_color">#FF368BD6</color>
<color name="button_bot_disabled_text_color">#61708B</color>
<!-- Link color -->
<color name="link_color_light">#368BD6</color>
<color name="link_color_dark">#368BD6</color>
<!-- Notification (do not depends on theme) -->
<color name="notification_accent_color">#368BD6</color>
<color name="key_share_req_accent_color">#ff812d</color>
</resources>