mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-21 17:05:39 +03:00
Merge pull request #8890 from element-hq/feature/bma/removeLegacyQrCodeLogin
Remove legacy qr code login
This commit is contained in:
commit
76616b1a28
61 changed files with 82 additions and 2948 deletions
1
changelog.d/8889.misc
Normal file
1
changelog.d/8889.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Remove legacy QR code login.
|
|
@ -2228,7 +2228,8 @@
|
|||
<string name="login_signin_matrix_id_password_notice">If you don’t know your password, go back to reset it.</string>
|
||||
<string name="login_signin_matrix_id_error_invalid_matrix_id">This is not a valid user identifier. Expected format: \'@user:homeserver.org\'</string>
|
||||
<string name="autodiscover_well_known_error">Unable to find a valid homeserver. Please check your identifier</string>
|
||||
<string name="login_scan_qr_code">Scan QR code</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="login_scan_qr_code">Scan QR code</string>
|
||||
|
||||
<string name="seen_by">Seen by</string>
|
||||
|
||||
|
@ -3475,9 +3476,10 @@
|
|||
<string name="device_manager_session_rename_edit_hint">Session name</string>
|
||||
<string name="device_manager_session_rename_description">Custom session names can help you recognize your devices more easily.</string>
|
||||
<string name="device_manager_session_rename_warning">Please be aware that session names are also visible to people you communicate with.</string>
|
||||
<string name="device_manager_sessions_sign_in_with_qr_code_title">Sign in with QR Code</string>
|
||||
<string name="device_manager_sessions_sign_in_with_qr_code_description">You can use this device to sign in a mobile or web device with a QR code. There are two ways to do this:</string>
|
||||
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="device_manager_sessions_sign_in_with_qr_code_title">Sign in with QR Code</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="device_manager_sessions_sign_in_with_qr_code_description">You can use this device to sign in a mobile or web device with a QR code. There are two ways to do this:</string>
|
||||
<string name="device_manager_learn_more_sessions_inactive_title">Inactive sessions</string>
|
||||
<string name="device_manager_learn_more_sessions_inactive">Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.\n\nRemoving inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.</string>
|
||||
<string name="device_manager_learn_more_sessions_unverified_title">Unverified sessions</string>
|
||||
|
@ -3515,45 +3517,82 @@
|
|||
<string name="onboarding_new_app_layout_feedback_message">Tap top right to see the option to feedback.</string>
|
||||
<string name="onboarding_new_app_layout_button_try">Try it out</string>
|
||||
|
||||
<string name="one">1</string>
|
||||
<string name="two">2</string>
|
||||
<string name="three">3</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="one">1</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="two">2</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="three">3</string>
|
||||
|
||||
<!-- QR Code Login -->
|
||||
<string name="qr_code_login_header_scan_qr_code_title">Scan QR code</string>
|
||||
<string name="qr_code_login_header_scan_qr_code_description">Use the camera on this device to scan the QR code shown on your other device:</string>
|
||||
<string name="qr_code_login_header_show_qr_code_title">Sign in with QR code</string>
|
||||
<string name="qr_code_login_header_show_qr_code_new_device_description">Use your signed in device to scan the QR code below:</string>
|
||||
<string name="qr_code_login_header_show_qr_code_link_a_device_description">Scan the QR code below with your device that’s signed out.</string>
|
||||
<string name="qr_code_login_header_connected_title">Secure connection established</string>
|
||||
<string name="qr_code_login_header_connected_description">Check your signed in device, the code below should be displayed. Confirm that the code below matches with that device:</string>
|
||||
<string name="qr_code_login_header_failed_title">Unsuccessful connection</string>
|
||||
<string name="qr_code_login_header_failed_device_is_not_supported_description">Linking with this device is not supported.</string>
|
||||
<string name="qr_code_login_header_failed_timeout_description">The linking wasn’t completed in the required time.</string>
|
||||
<string name="qr_code_login_header_failed_denied_description">The request was denied on the other device.</string>
|
||||
<string name="qr_code_login_header_failed_other_description">The request failed.</string>
|
||||
<string name="qr_code_login_header_failed_e2ee_security_issue_description">A security issue was encountered setting up secure messaging. One of the following may be compromised: Your homeserver; Your internet connection(s); Your device(s);</string>
|
||||
<string name="qr_code_login_header_failed_other_device_already_signed_in_description">The other device is already signed in.</string>
|
||||
<string name="qr_code_login_header_failed_other_device_not_signed_in_description">The other device must be signed in.</string>
|
||||
<string name="qr_code_login_header_failed_invalid_qr_code_description">That QR code is invalid.</string>
|
||||
<string name="qr_code_login_header_failed_user_cancelled_description">The sign in was cancelled on the other device.</string>
|
||||
<string name="qr_code_login_header_failed_homeserver_is_not_supported_description">The homeserver doesn\'t support sign in with QR code.</string>
|
||||
<string name="qr_code_login_new_device_instruction_1">Open the app on your other device</string>
|
||||
<string name="qr_code_login_new_device_instruction_2">Go to Settings -> Security & Privacy</string>
|
||||
<string name="qr_code_login_new_device_instruction_3">Select \'Show QR code\'</string>
|
||||
<string name="qr_code_login_link_a_device_scan_qr_code_instruction_1">Start at the sign in screen</string>
|
||||
<string name="qr_code_login_link_a_device_scan_qr_code_instruction_2">Select \'Sign in with QR code\'</string>
|
||||
<string name="qr_code_login_link_a_device_show_qr_code_instruction_1">Start at the sign in screen</string>
|
||||
<string name="qr_code_login_link_a_device_show_qr_code_instruction_2">Select \'Scan QR code\'</string>
|
||||
<string name="qr_code_login_show_qr_code_button">Show QR code in this device</string>
|
||||
<string name="qr_code_login_signing_in_a_mobile_device">Signing in a mobile device?</string>
|
||||
<string name="qr_code_login_scan_qr_code_button">Scan QR code</string>
|
||||
<string name="qr_code_login_connecting_to_device">Connecting to device</string>
|
||||
<string name="qr_code_login_signing_in">Signing you in</string>
|
||||
<string name="qr_code_login_status_no_match">No match?</string>
|
||||
<string name="qr_code_login_try_again">Try again</string>
|
||||
<string name="qr_code_login_confirm_security_code">Confirm</string>
|
||||
<string name="qr_code_login_confirm_security_code_description">Please ensure that you know the origin of this code. By linking devices, you will provide someone with full access to your account.</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_scan_qr_code_title">Scan QR code</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_scan_qr_code_description">Use the camera on this device to scan the QR code shown on your other device:</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_show_qr_code_title">Sign in with QR code</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_show_qr_code_new_device_description">Use your signed in device to scan the QR code below:</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_show_qr_code_link_a_device_description">Scan the QR code below with your device that’s signed out.</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_connected_title">Secure connection established</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_connected_description">Check your signed in device, the code below should be displayed. Confirm that the code below matches with that device:</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_title">Unsuccessful connection</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_device_is_not_supported_description">Linking with this device is not supported.</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_timeout_description">The linking wasn’t completed in the required time.</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_denied_description">The request was denied on the other device.</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_other_description">The request failed.</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_e2ee_security_issue_description">A security issue was encountered setting up secure messaging. One of the following may be compromised: Your homeserver; Your internet connection(s); Your device(s);</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_other_device_already_signed_in_description">The other device is already signed in.</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_other_device_not_signed_in_description">The other device must be signed in.</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_invalid_qr_code_description">That QR code is invalid.</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_user_cancelled_description">The sign in was cancelled on the other device.</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_homeserver_is_not_supported_description">The homeserver doesn\'t support sign in with QR code.</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_new_device_instruction_1">Open the app on your other device</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_new_device_instruction_2">Go to Settings -> Security & Privacy</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_new_device_instruction_3">Select \'Show QR code\'</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_link_a_device_scan_qr_code_instruction_1">Start at the sign in screen</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_link_a_device_scan_qr_code_instruction_2">Select \'Sign in with QR code\'</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_link_a_device_show_qr_code_instruction_1">Start at the sign in screen</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_link_a_device_show_qr_code_instruction_2">Select \'Scan QR code\'</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_show_qr_code_button">Show QR code in this device</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_signing_in_a_mobile_device">Signing in a mobile device?</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_scan_qr_code_button">Scan QR code</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_connecting_to_device">Connecting to device</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_signing_in">Signing you in</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_status_no_match">No match?</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_try_again">Try again</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_confirm_security_code">Confirm</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string tools:ignore="UnusedResources" name="qr_code_login_confirm_security_code_description">Please ensure that you know the origin of this code. By linking devices, you will provide someone with full access to your account.</string>
|
||||
|
||||
<!-- Rich text editor -->
|
||||
<string name="rich_text_editor_format_bold">Apply bold format</string>
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="QrCodeLoginInstructionsView">
|
||||
<attr name="qrCodeLoginInstruction1" format="string" />
|
||||
<attr name="qrCodeLoginInstruction2" format="string" />
|
||||
<attr name="qrCodeLoginInstruction3" format="string" />
|
||||
<attr name="qrCodeLoginInstruction4" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="QrCodeLoginHeaderView">
|
||||
<attr name="qrCodeLoginHeaderTitle" format="string" />
|
||||
<attr name="qrCodeLoginHeaderDescription" format="string" />
|
||||
<attr name="qrCodeLoginHeaderImageResource" format="reference" />
|
||||
<attr name="qrCodeLoginHeaderImageBackgroundTint" format="color" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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.rendezvous
|
||||
|
||||
import org.amshove.kluent.invoking
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.amshove.kluent.shouldBeInstanceOf
|
||||
import org.amshove.kluent.shouldThrow
|
||||
import org.amshove.kluent.with
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
|
||||
class RendezvousTest : InstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun shouldSuccessfullyBuildChannels() = CommonTestHelper.runCryptoTest(context()) { _, _ ->
|
||||
val cases = listOf(
|
||||
// v1:
|
||||
"{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256\"," +
|
||||
"\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
|
||||
"{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
|
||||
"\"intent\":\"login.reciprocate\"}",
|
||||
// v2:
|
||||
"{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256\"," +
|
||||
"\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
|
||||
"{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
|
||||
"\"intent\":\"login.reciprocate\"}",
|
||||
)
|
||||
|
||||
cases.forEach { input ->
|
||||
Rendezvous.buildChannelFromCode(input).channel shouldBeInstanceOf ECDHRendezvousChannel::class
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldFailToBuildChannelAsUnsupportedAlgorithm() {
|
||||
invoking {
|
||||
Rendezvous.buildChannelFromCode(
|
||||
"{\"rendezvous\":{\"algorithm\":\"bad algo\"," +
|
||||
"\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
|
||||
"{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
|
||||
"\"intent\":\"login.reciprocate\"}"
|
||||
)
|
||||
} shouldThrow RendezvousError::class with {
|
||||
this.reason shouldBeEqualTo RendezvousFailureReason.UnsupportedAlgorithm
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldFailToBuildChannelAsUnsupportedTransport() {
|
||||
invoking {
|
||||
Rendezvous.buildChannelFromCode(
|
||||
"{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256\"," +
|
||||
"\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
|
||||
"{\"type\":\"bad transport\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
|
||||
"\"intent\":\"login.reciprocate\"}"
|
||||
)
|
||||
} shouldThrow RendezvousError::class with {
|
||||
this.reason shouldBeEqualTo RendezvousFailureReason.UnsupportedTransport
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldFailToBuildChannelWithInvalidIntent() {
|
||||
invoking {
|
||||
Rendezvous.buildChannelFromCode(
|
||||
"{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256\"," +
|
||||
"\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
|
||||
"{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
|
||||
"\"intent\":\"foo\"}"
|
||||
)
|
||||
} shouldThrow RendezvousError::class with {
|
||||
this.reason shouldBeEqualTo RendezvousFailureReason.InvalidCode
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldFailToBuildChannelAsInvalidCode() {
|
||||
val cases = listOf(
|
||||
"{}",
|
||||
"rubbish",
|
||||
""
|
||||
)
|
||||
|
||||
cases.forEach { input ->
|
||||
invoking {
|
||||
Rendezvous.buildChannelFromCode(input)
|
||||
} shouldThrow RendezvousError::class with {
|
||||
this.reason shouldBeEqualTo RendezvousFailureReason.InvalidCode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,254 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous
|
||||
|
||||
import android.net.Uri
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||
import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel
|
||||
import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode
|
||||
import org.matrix.android.sdk.api.rendezvous.model.Outcome
|
||||
import org.matrix.android.sdk.api.rendezvous.model.Payload
|
||||
import org.matrix.android.sdk.api.rendezvous.model.PayloadType
|
||||
import org.matrix.android.sdk.api.rendezvous.model.Protocol
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousCode
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportType
|
||||
import org.matrix.android.sdk.api.rendezvous.model.SecureRendezvousChannelAlgorithm
|
||||
import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransport
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.api.util.MatrixJsonParser
|
||||
import timber.log.Timber
|
||||
|
||||
// n.b MSC3886/MSC3903/MSC3906 that this is based on are now closed.
|
||||
// However, we want to keep this implementation around for some time.
|
||||
// TODO define an end-of-life date for this implementation.
|
||||
|
||||
/**
|
||||
* Implementation of MSC3906 to sign in + E2EE set up using a QR code.
|
||||
*/
|
||||
class Rendezvous(
|
||||
val channel: RendezvousChannel,
|
||||
val theirIntent: RendezvousIntent,
|
||||
) {
|
||||
companion object {
|
||||
private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
fun buildChannelFromCode(code: String): Rendezvous {
|
||||
// we first check that the code is valid JSON and has right high-level structure
|
||||
val genericParsed = try {
|
||||
// we rely on moshi validating the code and throwing exception if invalid JSON or algorithm doesn't match
|
||||
MatrixJsonParser.getMoshi().adapter(RendezvousCode::class.java).fromJson(code)
|
||||
} catch (a: Throwable) {
|
||||
throw RendezvousError("Malformed code", RendezvousFailureReason.InvalidCode)
|
||||
} ?: throw RendezvousError("Code is null", RendezvousFailureReason.InvalidCode)
|
||||
|
||||
// then we check that algorithm is supported
|
||||
if (!SecureRendezvousChannelAlgorithm.values().map { it.value }.contains(genericParsed.rendezvous.algorithm)) {
|
||||
throw RendezvousError("Unsupported algorithm", RendezvousFailureReason.UnsupportedAlgorithm)
|
||||
}
|
||||
|
||||
// and, that the transport is supported
|
||||
if (!RendezvousTransportType.values().map { it.value }.contains(genericParsed.rendezvous.transport.type)) {
|
||||
throw RendezvousError("Unsupported transport", RendezvousFailureReason.UnsupportedTransport)
|
||||
}
|
||||
|
||||
// now that we know the overall structure looks sensible, we rely on moshi validating the code and
|
||||
// throwing exception if other parts are invalid
|
||||
val supportedParsed = try {
|
||||
MatrixJsonParser.getMoshi().adapter(ECDHRendezvousCode::class.java).fromJson(code)
|
||||
} catch (a: Throwable) {
|
||||
throw RendezvousError("Malformed ECDH rendezvous code", RendezvousFailureReason.InvalidCode)
|
||||
} ?: throw RendezvousError("ECDH rendezvous code is null", RendezvousFailureReason.InvalidCode)
|
||||
|
||||
val transport = SimpleHttpRendezvousTransport(supportedParsed.rendezvous.transport.uri)
|
||||
|
||||
return Rendezvous(
|
||||
ECDHRendezvousChannel(transport, supportedParsed.rendezvous.algorithm, supportedParsed.rendezvous.key),
|
||||
supportedParsed.intent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val adapter = MatrixJsonParser.getMoshi().adapter(Payload::class.java)
|
||||
|
||||
// not yet implemented: RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE
|
||||
val ourIntent: RendezvousIntent = RendezvousIntent.LOGIN_ON_NEW_DEVICE
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
private suspend fun checkCompatibility() {
|
||||
val incompatible = theirIntent == ourIntent
|
||||
|
||||
Timber.tag(TAG).d("ourIntent: $ourIntent, theirIntent: $theirIntent, incompatible: $incompatible")
|
||||
|
||||
if (incompatible) {
|
||||
// inform the other side
|
||||
send(Payload(PayloadType.FINISH, intent = ourIntent))
|
||||
if (ourIntent == RendezvousIntent.LOGIN_ON_NEW_DEVICE) {
|
||||
throw RendezvousError("The other device isn't signed in", RendezvousFailureReason.OtherDeviceNotSignedIn)
|
||||
} else {
|
||||
throw RendezvousError("The other device is already signed in", RendezvousFailureReason.OtherDeviceAlreadySignedIn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun startAfterScanningCode(): String {
|
||||
val checksum = channel.connect()
|
||||
|
||||
Timber.tag(TAG).i("Connected to secure channel with checksum: $checksum")
|
||||
|
||||
checkCompatibility()
|
||||
|
||||
// get protocols
|
||||
Timber.tag(TAG).i("Waiting for protocols")
|
||||
val protocolsResponse = receive()
|
||||
|
||||
if (protocolsResponse?.protocols == null || !protocolsResponse.protocols.contains(Protocol.LOGIN_TOKEN)) {
|
||||
send(Payload(PayloadType.FINISH, outcome = Outcome.UNSUPPORTED))
|
||||
throw RendezvousError("Unsupported protocols", RendezvousFailureReason.UnsupportedHomeserver)
|
||||
}
|
||||
|
||||
send(Payload(PayloadType.PROGRESS, protocol = Protocol.LOGIN_TOKEN))
|
||||
|
||||
return checksum
|
||||
}
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun waitForLoginOnNewDevice(authenticationService: AuthenticationService): Session {
|
||||
Timber.tag(TAG).i("Waiting for login_token")
|
||||
|
||||
val loginToken = receive()
|
||||
|
||||
if (loginToken?.type == PayloadType.FINISH) {
|
||||
when (loginToken.outcome) {
|
||||
Outcome.DECLINED -> {
|
||||
throw RendezvousError("Login declined by other device", RendezvousFailureReason.UserDeclined)
|
||||
}
|
||||
Outcome.UNSUPPORTED -> {
|
||||
throw RendezvousError("Homeserver lacks support", RendezvousFailureReason.UnsupportedHomeserver)
|
||||
}
|
||||
else -> {
|
||||
throw RendezvousError("Unknown error", RendezvousFailureReason.Unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val homeserver = loginToken?.homeserver ?: throw RendezvousError("No homeserver returned", RendezvousFailureReason.ProtocolError)
|
||||
val token = loginToken.loginToken ?: throw RendezvousError("No login token returned", RendezvousFailureReason.ProtocolError)
|
||||
|
||||
Timber.tag(TAG).i("Got login_token now attempting to sign in with $homeserver")
|
||||
|
||||
val hsConfig = HomeServerConnectionConfig(homeServerUri = Uri.parse(homeserver))
|
||||
return authenticationService.loginUsingQrLoginToken(hsConfig, token)
|
||||
}
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun completeVerificationOnNewDevice(session: Session) {
|
||||
val userId = session.myUserId
|
||||
val crypto = session.cryptoService()
|
||||
val deviceId = crypto.getMyCryptoDevice().deviceId
|
||||
val deviceKey = crypto.getMyCryptoDevice().fingerprint()
|
||||
send(Payload(PayloadType.PROGRESS, outcome = Outcome.SUCCESS, deviceId = deviceId, deviceKey = deviceKey))
|
||||
|
||||
try {
|
||||
// explicitly download keys for ourself rather than racing with initial sync which might not complete in time
|
||||
crypto.downloadKeysIfNeeded(listOf(userId), false)
|
||||
} catch (e: Throwable) {
|
||||
// log as warning and continue as initial sync might still complete
|
||||
Timber.tag(TAG).w(e, "Failed to download keys for self")
|
||||
}
|
||||
|
||||
// await confirmation of verification
|
||||
val verificationResponse = receive()
|
||||
if (verificationResponse?.outcome == Outcome.VERIFIED) {
|
||||
val verifyingDeviceId = verificationResponse.verifyingDeviceId
|
||||
?: throw RendezvousError("No verifying device id returned", RendezvousFailureReason.ProtocolError)
|
||||
val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId)
|
||||
if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifyingDeviceKey) {
|
||||
Timber.tag(TAG).w(
|
||||
"Verifying device $verifyingDeviceId key doesn't match: ${
|
||||
verifyingDeviceFromServer?.fingerprint()
|
||||
} vs ${verificationResponse.verifyingDeviceKey})"
|
||||
)
|
||||
// inform the other side
|
||||
send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR))
|
||||
throw RendezvousError("Key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue)
|
||||
}
|
||||
|
||||
verificationResponse.masterKey?.let { masterKeyFromVerifyingDevice ->
|
||||
// verifying device provided us with a master key, so use it to check integrity
|
||||
|
||||
// see what the homeserver told us
|
||||
val localMasterKey = crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey()
|
||||
|
||||
// n.b. if no local master key this is a problem, as well as it not matching
|
||||
if (localMasterKey?.unpaddedBase64PublicKey != masterKeyFromVerifyingDevice) {
|
||||
Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey")
|
||||
// inform the other side
|
||||
send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR))
|
||||
throw RendezvousError("Master key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue)
|
||||
}
|
||||
|
||||
// set other device as verified
|
||||
Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified")
|
||||
crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId)
|
||||
|
||||
Timber.tag(TAG).i("Setting master key as trusted")
|
||||
crypto.crossSigningService().markMyMasterKeyAsTrusted()
|
||||
} ?: run {
|
||||
// set other device as verified anyway
|
||||
Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified")
|
||||
crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId)
|
||||
|
||||
Timber.tag(TAG).i("No master key given by verifying device")
|
||||
}
|
||||
|
||||
// request secrets from other sessions.
|
||||
Timber.tag(TAG).i("Requesting secrets from other sessions")
|
||||
|
||||
session.sharedSecretStorageService().requestMissingSecrets()
|
||||
} else {
|
||||
Timber.tag(TAG).i("Not doing verification")
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
private suspend fun receive(): Payload? {
|
||||
val data = channel.receive() ?: return null
|
||||
val payload = try {
|
||||
adapter.fromJson(data.toString(Charsets.UTF_8))
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(TAG).w(e, "Failed to parse payload")
|
||||
throw RendezvousError("Invalid payload received", RendezvousFailureReason.Unknown)
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
private suspend fun send(payload: Payload) {
|
||||
channel.send(adapter.toJson(payload).toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
suspend fun close() {
|
||||
channel.close()
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous
|
||||
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
|
||||
|
||||
/**
|
||||
* Representation of a rendezvous channel such as that described by MSC3903.
|
||||
*/
|
||||
interface RendezvousChannel {
|
||||
val transport: RendezvousTransport
|
||||
|
||||
/**
|
||||
* @returns the checksum/confirmation digits to be shown to the user
|
||||
*/
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun connect(): String
|
||||
|
||||
/**
|
||||
* Send a payload via the channel.
|
||||
* @param data payload to send
|
||||
*/
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun send(data: ByteArray)
|
||||
|
||||
/**
|
||||
* Receive a payload from the channel.
|
||||
* @returns the received payload
|
||||
*/
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun receive(): ByteArray?
|
||||
|
||||
/**
|
||||
* Closes the channel and cleans up.
|
||||
*/
|
||||
suspend fun close()
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous
|
||||
|
||||
enum class RendezvousFailureReason(val canRetry: Boolean = true) {
|
||||
UserDeclined,
|
||||
OtherDeviceNotSignedIn,
|
||||
OtherDeviceAlreadySignedIn,
|
||||
Unknown,
|
||||
Expired,
|
||||
UserCancelled,
|
||||
InvalidCode,
|
||||
UnsupportedAlgorithm(false),
|
||||
UnsupportedTransport(false),
|
||||
UnsupportedHomeserver(false),
|
||||
ProtocolError,
|
||||
E2EESecurityIssue(false)
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous
|
||||
|
||||
import okhttp3.MediaType
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails
|
||||
|
||||
interface RendezvousTransport {
|
||||
var ready: Boolean
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun details(): RendezvousTransportDetails
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun send(contentType: MediaType, data: ByteArray)
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
suspend fun receive(): ByteArray?
|
||||
|
||||
suspend fun close()
|
||||
}
|
|
@ -1,199 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.channels
|
||||
|
||||
import android.util.Base64
|
||||
import com.squareup.moshi.JsonClass
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||
import org.matrix.android.sdk.api.rendezvous.RendezvousChannel
|
||||
import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
|
||||
import org.matrix.android.sdk.api.rendezvous.RendezvousTransport
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
|
||||
import org.matrix.android.sdk.api.rendezvous.model.SecureRendezvousChannelAlgorithm
|
||||
import org.matrix.android.sdk.api.util.MatrixJsonParser
|
||||
import org.matrix.android.sdk.internal.crypto.verification.getDecimalCodeRepresentation
|
||||
import org.matrix.olm.OlmSAS
|
||||
import timber.log.Timber
|
||||
import java.security.SecureRandom
|
||||
import java.util.LinkedList
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
/**
|
||||
* Implements X25519 ECDH key agreement and AES-256-GCM encryption channel as per MSC3903:
|
||||
* https://github.com/matrix-org/matrix-spec-proposals/pull/3903
|
||||
*/
|
||||
class ECDHRendezvousChannel(
|
||||
override var transport: RendezvousTransport,
|
||||
private val algorithm: SecureRendezvousChannelAlgorithm,
|
||||
theirPublicKeyBase64: String?,
|
||||
) : RendezvousChannel {
|
||||
companion object {
|
||||
private const val ALGORITHM_SPEC = "AES/GCM/NoPadding"
|
||||
private const val KEY_SPEC = "AES"
|
||||
private val TAG = LoggerTag(ECDHRendezvousChannel::class.java.simpleName, LoggerTag.RENDEZVOUS).value
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class ECDHPayload(
|
||||
val algorithm: SecureRendezvousChannelAlgorithm? = null,
|
||||
val key: String? = null,
|
||||
val ciphertext: String? = null,
|
||||
val iv: String? = null,
|
||||
)
|
||||
|
||||
private val olmSASMutex = Mutex()
|
||||
private var olmSAS: OlmSAS?
|
||||
private val ourPublicKey: ByteArray
|
||||
private val ecdhAdapter = MatrixJsonParser.getMoshi().adapter(ECDHPayload::class.java)
|
||||
private var theirPublicKey: ByteArray? = null
|
||||
private var aesKey: ByteArray? = null
|
||||
|
||||
init {
|
||||
theirPublicKeyBase64?.let {
|
||||
theirPublicKey = decodeBase64(it)
|
||||
}
|
||||
olmSAS = OlmSAS()
|
||||
ourPublicKey = decodeBase64(olmSAS!!.publicKey)
|
||||
}
|
||||
|
||||
fun encodeBase64(input: ByteArray?): String? {
|
||||
if (algorithm == SecureRendezvousChannelAlgorithm.ECDH_V2) {
|
||||
return Base64.encodeToString(input, Base64.NO_WRAP or Base64.NO_PADDING)
|
||||
}
|
||||
return Base64.encodeToString(input, Base64.NO_WRAP)
|
||||
}
|
||||
|
||||
fun decodeBase64(input: String?): ByteArray {
|
||||
// for decoding we aren't concerned about padding
|
||||
return Base64.decode(input, Base64.NO_WRAP)
|
||||
}
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
override suspend fun connect(): String {
|
||||
val sas = olmSAS ?: throw RendezvousError("Channel closed", RendezvousFailureReason.Unknown)
|
||||
val isInitiator = theirPublicKey == null
|
||||
|
||||
if (isInitiator) {
|
||||
Timber.tag(TAG).i("Waiting for other device to send their public key")
|
||||
val res = this.receiveAsPayload() ?: throw RendezvousError("No reply from other device", RendezvousFailureReason.ProtocolError)
|
||||
|
||||
if (res.key == null) {
|
||||
throw RendezvousError(
|
||||
"Unsupported algorithm: ${res.algorithm}",
|
||||
RendezvousFailureReason.UnsupportedAlgorithm,
|
||||
)
|
||||
}
|
||||
theirPublicKey = decodeBase64(res.key)
|
||||
} else {
|
||||
// send our public key unencrypted
|
||||
Timber.tag(TAG).i("Sending public key")
|
||||
send(
|
||||
ECDHPayload(
|
||||
algorithm = algorithm,
|
||||
key = encodeBase64(ourPublicKey)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
olmSASMutex.withLock {
|
||||
sas.setTheirPublicKey(encodeBase64(theirPublicKey))
|
||||
sas.setTheirPublicKey(encodeBase64(theirPublicKey))
|
||||
|
||||
val initiatorKey = encodeBase64(if (isInitiator) ourPublicKey else theirPublicKey)
|
||||
val recipientKey = encodeBase64(if (isInitiator) theirPublicKey else ourPublicKey)
|
||||
val aesInfo = "${algorithm.value}|$initiatorKey|$recipientKey"
|
||||
|
||||
aesKey = sas.generateShortCode(aesInfo, 32)
|
||||
|
||||
val rawChecksum = sas.generateShortCode(aesInfo, 5)
|
||||
return rawChecksum.getDecimalCodeRepresentation(separator = "-")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun send(payload: ECDHPayload) {
|
||||
transport.send("application/json".toMediaType(), ecdhAdapter.toJson(payload).toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
override suspend fun send(data: ByteArray) {
|
||||
if (aesKey == null) {
|
||||
throw IllegalStateException("Shared secret not established")
|
||||
}
|
||||
send(encrypt(data))
|
||||
}
|
||||
|
||||
private suspend fun receiveAsPayload(): ECDHPayload? {
|
||||
transport.receive()?.toString(Charsets.UTF_8)?.let {
|
||||
return ecdhAdapter.fromJson(it)
|
||||
} ?: return null
|
||||
}
|
||||
|
||||
override suspend fun receive(): ByteArray? {
|
||||
if (aesKey == null) {
|
||||
throw IllegalStateException("Shared secret not established")
|
||||
}
|
||||
val payload = receiveAsPayload() ?: return null
|
||||
return decrypt(payload)
|
||||
}
|
||||
|
||||
override suspend fun close() {
|
||||
val sas = olmSAS ?: throw IllegalStateException("Channel already closed")
|
||||
olmSASMutex.withLock {
|
||||
// this does a double release check already so we don't re-check ourselves
|
||||
sas.releaseSas()
|
||||
olmSAS = null
|
||||
}
|
||||
transport.close()
|
||||
}
|
||||
|
||||
private fun encrypt(plainText: ByteArray): ECDHPayload {
|
||||
val iv = ByteArray(16)
|
||||
SecureRandom().nextBytes(iv)
|
||||
|
||||
val cipherText = LinkedList<Byte>()
|
||||
|
||||
val encryptCipher = Cipher.getInstance(ALGORITHM_SPEC)
|
||||
val secretKeySpec = SecretKeySpec(aesKey, KEY_SPEC)
|
||||
val ivParameterSpec = IvParameterSpec(iv)
|
||||
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
|
||||
cipherText.addAll(encryptCipher.update(plainText).toList())
|
||||
cipherText.addAll(encryptCipher.doFinal().toList())
|
||||
|
||||
return ECDHPayload(
|
||||
ciphertext = encodeBase64(cipherText.toByteArray()),
|
||||
iv = encodeBase64(iv)
|
||||
)
|
||||
}
|
||||
|
||||
private fun decrypt(payload: ECDHPayload): ByteArray {
|
||||
val iv = decodeBase64(payload.iv)
|
||||
val encryptCipher = Cipher.getInstance(ALGORITHM_SPEC)
|
||||
val secretKeySpec = SecretKeySpec(aesKey, KEY_SPEC)
|
||||
val ivParameterSpec = IvParameterSpec(iv)
|
||||
encryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
|
||||
|
||||
val plainText = LinkedList<Byte>()
|
||||
plainText.addAll(encryptCipher.update(decodeBase64(payload.ciphertext)).toList())
|
||||
plainText.addAll(encryptCipher.doFinal().toList())
|
||||
|
||||
return plainText.toByteArray()
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ECDHRendezvous(
|
||||
val transport: SimpleHttpRendezvousTransportDetails,
|
||||
val algorithm: SecureRendezvousChannelAlgorithm,
|
||||
val key: String
|
||||
)
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ECDHRendezvousCode(
|
||||
val intent: RendezvousIntent,
|
||||
val rendezvous: ECDHRendezvous
|
||||
)
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class Outcome(val value: String) {
|
||||
@Json(name = "success")
|
||||
SUCCESS("success"),
|
||||
|
||||
@Json(name = "declined")
|
||||
DECLINED("declined"),
|
||||
|
||||
@Json(name = "unsupported")
|
||||
UNSUPPORTED("unsupported"),
|
||||
|
||||
@Json(name = "verified")
|
||||
VERIFIED("verified"),
|
||||
|
||||
@Json(name = "e2ee_security_error")
|
||||
E2EE_SECURITY_ERROR("e2ee_security_error")
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Payload(
|
||||
val type: PayloadType,
|
||||
val intent: RendezvousIntent? = null,
|
||||
val outcome: Outcome? = null,
|
||||
val protocols: List<Protocol>? = null,
|
||||
val protocol: Protocol? = null,
|
||||
val homeserver: String? = null,
|
||||
@Json(name = "login_token") val loginToken: String? = null,
|
||||
@Json(name = "device_id") val deviceId: String? = null,
|
||||
@Json(name = "device_key") val deviceKey: String? = null,
|
||||
@Json(name = "verifying_device_id") val verifyingDeviceId: String? = null,
|
||||
@Json(name = "verifying_device_key") val verifyingDeviceKey: String? = null,
|
||||
@Json(name = "master_key") val masterKey: String? = null
|
||||
)
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
internal enum class PayloadType(val value: String) {
|
||||
@Json(name = "m.login.start")
|
||||
START("m.login.start"),
|
||||
|
||||
@Json(name = "m.login.finish")
|
||||
FINISH("m.login.finish"),
|
||||
|
||||
@Json(name = "m.login.progress")
|
||||
PROGRESS("m.login.progress")
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class Protocol(val value: String) {
|
||||
@Json(name = "org.matrix.msc3906.login_token")
|
||||
LOGIN_TOKEN("org.matrix.msc3906.login_token")
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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.rendezvous.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
open class Rendezvous(
|
||||
val transport: RendezvousTransportDetails,
|
||||
val algorithm: String,
|
||||
)
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 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.rendezvous.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
open class RendezvousCode(
|
||||
open val intent: RendezvousIntent,
|
||||
open val rendezvous: Rendezvous
|
||||
)
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.model
|
||||
|
||||
import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
|
||||
|
||||
class RendezvousError(val description: String, val reason: RendezvousFailureReason) : Exception(description)
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class RendezvousIntent {
|
||||
@Json(name = "login.start") LOGIN_ON_NEW_DEVICE,
|
||||
@Json(name = "login.reciprocate") RECIPROCATE_LOGIN_ON_EXISTING_DEVICE
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
open class RendezvousTransportDetails(
|
||||
val type: String
|
||||
)
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class RendezvousTransportType(val value: String) {
|
||||
@Json(name = "org.matrix.msc3886.http.v1")
|
||||
MSC3886_SIMPLE_HTTP_V1("org.matrix.msc3886.http.v1")
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class SecureRendezvousChannelAlgorithm(val value: String) {
|
||||
@Json(name = "org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256")
|
||||
ECDH_V1("org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256"),
|
||||
@Json(name = "org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256")
|
||||
ECDH_V2("org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256")
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SimpleHttpRendezvousTransportDetails(
|
||||
val uri: String
|
||||
) : RendezvousTransportDetails(type = RendezvousTransportType.MSC3886_SIMPLE_HTTP_V1.name)
|
|
@ -1,173 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022 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.rendezvous.transports
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||
import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
|
||||
import org.matrix.android.sdk.api.rendezvous.RendezvousTransport
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails
|
||||
import org.matrix.android.sdk.api.rendezvous.model.SimpleHttpRendezvousTransportDetails
|
||||
import timber.log.Timber
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Implementation of the Simple HTTP transport MSC3886: https://github.com/matrix-org/matrix-spec-proposals/pull/3886
|
||||
*/
|
||||
class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTransport {
|
||||
companion object {
|
||||
private val TAG = LoggerTag(SimpleHttpRendezvousTransport::class.java.simpleName, LoggerTag.RENDEZVOUS).value
|
||||
}
|
||||
|
||||
override var ready = false
|
||||
private var cancelled = false
|
||||
private var uri: String?
|
||||
private var etag: String? = null
|
||||
private var expiresAt: Date? = null
|
||||
|
||||
init {
|
||||
uri = rendezvousUri
|
||||
}
|
||||
|
||||
override suspend fun details(): RendezvousTransportDetails {
|
||||
val uri = uri ?: throw IllegalStateException("Rendezvous not set up")
|
||||
|
||||
return SimpleHttpRendezvousTransportDetails(uri)
|
||||
}
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
override suspend fun send(contentType: MediaType, data: ByteArray) {
|
||||
if (cancelled) {
|
||||
throw IllegalStateException("Rendezvous cancelled")
|
||||
}
|
||||
|
||||
val method = if (uri != null) "PUT" else "POST"
|
||||
val uri = this.uri ?: throw RuntimeException("No rendezvous URI")
|
||||
|
||||
val httpClient = okhttp3.OkHttpClient.Builder().build()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(uri)
|
||||
.method(method, data.toRequestBody())
|
||||
.header("content-type", contentType.toString())
|
||||
|
||||
etag?.let {
|
||||
request.header("if-match", it)
|
||||
}
|
||||
|
||||
val response = httpClient.newCall(request.build()).execute()
|
||||
|
||||
if (response.code == 404) {
|
||||
throw get404Error()
|
||||
}
|
||||
etag = response.header("etag")
|
||||
|
||||
Timber.tag(TAG).i("Sent data to $uri new etag $etag")
|
||||
|
||||
if (method == "POST") {
|
||||
val location = response.header("location") ?: throw RuntimeException("No rendezvous URI found in response")
|
||||
|
||||
response.header("expires")?.let {
|
||||
val format = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
|
||||
expiresAt = format.parse(it)
|
||||
}
|
||||
|
||||
// resolve location header which could be relative or absolute
|
||||
this.uri = response.request.url.toUri().resolve(location).toString()
|
||||
ready = true
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(RendezvousError::class)
|
||||
override suspend fun receive(): ByteArray? {
|
||||
if (cancelled) {
|
||||
throw IllegalStateException("Rendezvous cancelled")
|
||||
}
|
||||
val uri = uri ?: throw IllegalStateException("Rendezvous not set up")
|
||||
val httpClient = okhttp3.OkHttpClient.Builder().build()
|
||||
while (true) {
|
||||
Timber.tag(TAG).i("Polling: $uri after etag $etag")
|
||||
val request = Request.Builder()
|
||||
.url(uri)
|
||||
.get()
|
||||
|
||||
etag?.let {
|
||||
request.header("if-none-match", it)
|
||||
}
|
||||
|
||||
val response = httpClient.newCall(request.build()).execute()
|
||||
|
||||
try {
|
||||
// expired
|
||||
if (response.code == 404) {
|
||||
throw get404Error()
|
||||
}
|
||||
|
||||
// rely on server expiring the channel rather than checking ourselves
|
||||
|
||||
if (response.header("content-type") != "application/json") {
|
||||
response.header("etag")?.let {
|
||||
etag = it
|
||||
}
|
||||
} else if (response.code == 200) {
|
||||
response.header("etag")?.let {
|
||||
etag = it
|
||||
}
|
||||
return response.body?.bytes()
|
||||
}
|
||||
|
||||
// sleep for a second before polling again
|
||||
// we rely on the server expiring the channel rather than checking it ourselves
|
||||
delay(1000)
|
||||
} finally {
|
||||
response.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun get404Error(): RendezvousError {
|
||||
if (expiresAt != null && Date() > expiresAt) {
|
||||
return RendezvousError("Expired", RendezvousFailureReason.Expired)
|
||||
}
|
||||
|
||||
return RendezvousError("Received unexpected 404", RendezvousFailureReason.Unknown)
|
||||
}
|
||||
|
||||
override suspend fun close() {
|
||||
cancelled = true
|
||||
ready = false
|
||||
|
||||
uri?.let {
|
||||
try {
|
||||
val httpClient = okhttp3.OkHttpClient.Builder().build()
|
||||
val request = Request.Builder()
|
||||
.url(it)
|
||||
.delete()
|
||||
.build()
|
||||
httpClient.newCall(request).execute()
|
||||
} catch (e: Throwable) {
|
||||
Timber.tag(TAG).w(e, "Failed to delete channel")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -85,21 +85,6 @@ class DebugFeaturesStateFactory @Inject constructor(
|
|||
key = DebugFeatureKeys.newAppLayoutEnabled,
|
||||
factory = VectorFeatures::isNewAppLayoutFeatureEnabled
|
||||
),
|
||||
createBooleanFeature(
|
||||
label = "Enable QR Code Login",
|
||||
key = DebugFeatureKeys.qrCodeLoginEnabled,
|
||||
factory = VectorFeatures::isQrCodeLoginEnabled
|
||||
),
|
||||
createBooleanFeature(
|
||||
label = "Allow QR Code Login for all servers",
|
||||
key = DebugFeatureKeys.qrCodeLoginForAllServers,
|
||||
factory = VectorFeatures::isQrCodeLoginForAllServers
|
||||
),
|
||||
createBooleanFeature(
|
||||
label = "Show QR Code Login in Device Manager",
|
||||
key = DebugFeatureKeys.reciprocateQrCodeLogin,
|
||||
factory = VectorFeatures::isReciprocateQrCodeLogin
|
||||
),
|
||||
createBooleanFeature(
|
||||
label = "Enable Voice Broadcast",
|
||||
key = DebugFeatureKeys.voiceBroadcastEnabled,
|
||||
|
|
|
@ -76,15 +76,6 @@ class DebugVectorFeatures(
|
|||
override fun isNewAppLayoutFeatureEnabled(): Boolean = read(DebugFeatureKeys.newAppLayoutEnabled)
|
||||
?: vectorFeatures.isNewAppLayoutFeatureEnabled()
|
||||
|
||||
override fun isQrCodeLoginEnabled() = read(DebugFeatureKeys.qrCodeLoginEnabled)
|
||||
?: vectorFeatures.isQrCodeLoginEnabled()
|
||||
|
||||
override fun isQrCodeLoginForAllServers() = read(DebugFeatureKeys.qrCodeLoginForAllServers)
|
||||
?: vectorFeatures.isQrCodeLoginForAllServers()
|
||||
|
||||
override fun isReciprocateQrCodeLogin() = read(DebugFeatureKeys.reciprocateQrCodeLogin)
|
||||
?: vectorFeatures.isReciprocateQrCodeLogin()
|
||||
|
||||
override fun isVoiceBroadcastEnabled(): Boolean = read(DebugFeatureKeys.voiceBroadcastEnabled)
|
||||
?: vectorFeatures.isVoiceBroadcastEnabled()
|
||||
|
||||
|
@ -150,9 +141,6 @@ object DebugFeatureKeys {
|
|||
val screenSharing = booleanPreferencesKey("screen-sharing")
|
||||
val forceUsageOfOpusEncoder = booleanPreferencesKey("force-usage-of-opus-encoder")
|
||||
val newAppLayoutEnabled = booleanPreferencesKey("new-app-layout-enabled")
|
||||
val qrCodeLoginEnabled = booleanPreferencesKey("qr-code-login-enabled")
|
||||
val qrCodeLoginForAllServers = booleanPreferencesKey("qr-code-login-for-all-servers")
|
||||
val reciprocateQrCodeLogin = booleanPreferencesKey("reciprocate-qr-code-login")
|
||||
val voiceBroadcastEnabled = booleanPreferencesKey("voice-broadcast-enabled")
|
||||
val unverifiedSessionsAlertEnabled = booleanPreferencesKey("unverified-sessions-alert-enabled")
|
||||
}
|
||||
|
|
|
@ -349,7 +349,6 @@
|
|||
<activity android:name=".features.settings.devices.v2.othersessions.OtherSessionsActivity" />
|
||||
<activity android:name=".features.settings.devices.v2.details.SessionDetailsActivity" />
|
||||
<activity android:name=".features.settings.devices.v2.rename.RenameSessionActivity" />
|
||||
<activity android:name=".features.login.qr.QrCodeLoginActivity" />
|
||||
<activity android:name=".features.roomprofile.polls.detail.ui.RoomPollDetailActivity" />
|
||||
|
||||
<!-- Services -->
|
||||
|
|
|
@ -61,7 +61,6 @@ import im.vector.app.features.location.LocationSharingViewModel
|
|||
import im.vector.app.features.location.live.map.LiveLocationMapViewModel
|
||||
import im.vector.app.features.location.preview.LocationPreviewViewModel
|
||||
import im.vector.app.features.login.LoginViewModel
|
||||
import im.vector.app.features.login.qr.QrCodeLoginViewModel
|
||||
import im.vector.app.features.matrixto.MatrixToBottomSheetViewModel
|
||||
import im.vector.app.features.media.VectorAttachmentViewerViewModel
|
||||
import im.vector.app.features.onboarding.OnboardingViewModel
|
||||
|
@ -668,11 +667,6 @@ interface MavericksViewModelModule {
|
|||
@MavericksViewModelKey(RenameSessionViewModel::class)
|
||||
fun renameSessionViewModelFactory(factory: RenameSessionViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(QrCodeLoginViewModel::class)
|
||||
fun qrCodeLoginViewModelFactory(factory: QrCodeLoginViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(SessionLearnMoreViewModel::class)
|
||||
|
|
|
@ -40,9 +40,6 @@ interface VectorFeatures {
|
|||
* use [VectorPreferences.isNewAppLayoutEnabled] instead.
|
||||
*/
|
||||
fun isNewAppLayoutFeatureEnabled(): Boolean
|
||||
fun isQrCodeLoginEnabled(): Boolean
|
||||
fun isQrCodeLoginForAllServers(): Boolean
|
||||
fun isReciprocateQrCodeLogin(): Boolean
|
||||
fun isVoiceBroadcastEnabled(): Boolean
|
||||
fun isUnverifiedSessionsAlertEnabled(): Boolean
|
||||
}
|
||||
|
@ -60,9 +57,6 @@ class DefaultVectorFeatures : VectorFeatures {
|
|||
override fun isLocationSharingEnabled() = Config.ENABLE_LOCATION_SHARING
|
||||
override fun forceUsageOfOpusEncoder(): Boolean = false
|
||||
override fun isNewAppLayoutFeatureEnabled(): Boolean = true
|
||||
override fun isQrCodeLoginEnabled(): Boolean = true
|
||||
override fun isQrCodeLoginForAllServers(): Boolean = false
|
||||
override fun isReciprocateQrCodeLogin(): Boolean = false
|
||||
override fun isVoiceBroadcastEnabled(): Boolean = true
|
||||
override fun isUnverifiedSessionsAlertEnabled(): Boolean = true
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.login.qr
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class QrCodeLoginAction : VectorViewModelAction {
|
||||
data class OnQrCodeScanned(val qrCode: String) : QrCodeLoginAction()
|
||||
object GenerateQrCode : QrCodeLoginAction()
|
||||
object ShowQrCode : QrCodeLoginAction()
|
||||
object TryAgain : QrCodeLoginAction()
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.login.qr
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.extensions.addFragment
|
||||
import im.vector.app.core.extensions.replaceFragment
|
||||
import im.vector.app.core.platform.SimpleFragmentActivity
|
||||
import im.vector.app.features.home.HomeActivity
|
||||
import im.vector.lib.core.utils.compat.getParcelableCompat
|
||||
import timber.log.Timber
|
||||
|
||||
// n.b MSC3886/MSC3903/MSC3906 that this is based on are now closed.
|
||||
// However, we want to keep this implementation around for some time.
|
||||
// TODO define an end-of-life date for this implementation.
|
||||
|
||||
@AndroidEntryPoint
|
||||
class QrCodeLoginActivity : SimpleFragmentActivity() {
|
||||
|
||||
private val viewModel: QrCodeLoginViewModel by viewModel()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
views.toolbar.visibility = View.GONE
|
||||
|
||||
if (isFirstCreation()) {
|
||||
navigateToInitialFragment()
|
||||
}
|
||||
|
||||
observeViewEvents()
|
||||
}
|
||||
|
||||
private fun navigateToInitialFragment() {
|
||||
val qrCodeLoginArgs: QrCodeLoginArgs? = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG)
|
||||
when (qrCodeLoginArgs?.loginType) {
|
||||
QrCodeLoginType.LOGIN -> {
|
||||
showInstructionsFragment(qrCodeLoginArgs)
|
||||
}
|
||||
QrCodeLoginType.LINK_A_DEVICE -> {
|
||||
if (qrCodeLoginArgs.showQrCodeImmediately) {
|
||||
handleNavigateToShowQrCodeScreen()
|
||||
} else {
|
||||
showInstructionsFragment(qrCodeLoginArgs)
|
||||
}
|
||||
}
|
||||
null -> {
|
||||
Timber.i("QrCodeLoginArgs is null. This is not expected.")
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showInstructionsFragment(qrCodeLoginArgs: QrCodeLoginArgs) {
|
||||
replaceFragment(
|
||||
views.container,
|
||||
QrCodeLoginInstructionsFragment::class.java,
|
||||
qrCodeLoginArgs,
|
||||
tag = FRAGMENT_QR_CODE_INSTRUCTIONS_TAG
|
||||
)
|
||||
}
|
||||
|
||||
private fun observeViewEvents() {
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
QrCodeLoginViewEvents.NavigateToStatusScreen -> handleNavigateToStatusScreen()
|
||||
QrCodeLoginViewEvents.NavigateToShowQrCodeScreen -> handleNavigateToShowQrCodeScreen()
|
||||
QrCodeLoginViewEvents.NavigateToHomeScreen -> handleNavigateToHomeScreen()
|
||||
QrCodeLoginViewEvents.NavigateToInitialScreen -> handleNavigateToInitialScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNavigateToInitialScreen() {
|
||||
navigateToInitialFragment()
|
||||
}
|
||||
|
||||
private fun handleNavigateToShowQrCodeScreen() {
|
||||
addFragment(
|
||||
views.container,
|
||||
QrCodeLoginShowQrCodeFragment::class.java,
|
||||
tag = FRAGMENT_SHOW_QR_CODE_TAG
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleNavigateToStatusScreen() {
|
||||
addFragment(
|
||||
views.container,
|
||||
QrCodeLoginStatusFragment::class.java,
|
||||
tag = FRAGMENT_QR_CODE_STATUS_TAG
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleNavigateToHomeScreen() {
|
||||
val intent = HomeActivity.newIntent(this, firstStartMainActivity = false, existingSession = true)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val FRAGMENT_QR_CODE_INSTRUCTIONS_TAG = "FRAGMENT_QR_CODE_INSTRUCTIONS_TAG"
|
||||
private const val FRAGMENT_SHOW_QR_CODE_TAG = "FRAGMENT_SHOW_QR_CODE_TAG"
|
||||
private const val FRAGMENT_QR_CODE_STATUS_TAG = "FRAGMENT_QR_CODE_STATUS_TAG"
|
||||
|
||||
fun getIntent(context: Context, qrCodeLoginArgs: QrCodeLoginArgs): Intent {
|
||||
return Intent(context, QrCodeLoginActivity::class.java).apply {
|
||||
putExtra(Mavericks.KEY_ARG, qrCodeLoginArgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.login.qr
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class QrCodeLoginArgs(
|
||||
val loginType: QrCodeLoginType,
|
||||
val showQrCodeImmediately: Boolean,
|
||||
) : Parcelable
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.login.qr
|
||||
|
||||
import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
|
||||
|
||||
sealed class QrCodeLoginConnectionStatus {
|
||||
object ConnectingToDevice : QrCodeLoginConnectionStatus()
|
||||
data class Connected(val securityCode: String, val canConfirmSecurityCode: Boolean) : QrCodeLoginConnectionStatus()
|
||||
object SigningIn : QrCodeLoginConnectionStatus()
|
||||
data class Failed(val errorType: RendezvousFailureReason, val canTryAgain: Boolean) : QrCodeLoginConnectionStatus()
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.login.qr
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.TypedArray
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.res.use
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.databinding.ViewQrCodeLoginHeaderBinding
|
||||
|
||||
class QrCodeLoginHeaderView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val binding = ViewQrCodeLoginHeaderBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
this
|
||||
)
|
||||
|
||||
init {
|
||||
context.obtainStyledAttributes(
|
||||
attrs,
|
||||
im.vector.lib.ui.styles.R.styleable.QrCodeLoginHeaderView,
|
||||
0,
|
||||
0
|
||||
).use {
|
||||
setTitle(it)
|
||||
setDescription(it)
|
||||
setImage(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTitle(typedArray: TypedArray) {
|
||||
val title = typedArray.getString(im.vector.lib.ui.styles.R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderTitle)
|
||||
setTitle(title)
|
||||
}
|
||||
|
||||
private fun setDescription(typedArray: TypedArray) {
|
||||
val description = typedArray.getString(im.vector.lib.ui.styles.R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderDescription)
|
||||
setDescription(description)
|
||||
}
|
||||
|
||||
private fun setImage(typedArray: TypedArray) {
|
||||
val imageResource = typedArray.getResourceId(im.vector.lib.ui.styles.R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderImageResource, 0)
|
||||
val backgroundTint = typedArray.getColor(im.vector.lib.ui.styles.R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderImageBackgroundTint, 0)
|
||||
setImage(imageResource, backgroundTint)
|
||||
}
|
||||
|
||||
fun setTitle(title: String?) {
|
||||
binding.qrCodeLoginHeaderTitleTextView.setTextOrHide(title)
|
||||
}
|
||||
|
||||
fun setDescription(description: String?) {
|
||||
binding.qrCodeLoginHeaderDescriptionTextView.setTextOrHide(description)
|
||||
}
|
||||
|
||||
fun setImage(imageResource: Int, backgroundTintColor: Int) {
|
||||
binding.qrCodeLoginHeaderImageView.setImageResource(imageResource)
|
||||
binding.qrCodeLoginHeaderImageView.backgroundTintList = ColorStateList.valueOf(backgroundTintColor)
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.login.qr
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentQrCodeLoginInstructionsBinding
|
||||
import im.vector.app.features.qrcode.QrCodeScannerActivity
|
||||
import im.vector.lib.strings.CommonStrings
|
||||
import timber.log.Timber
|
||||
|
||||
@AndroidEntryPoint
|
||||
class QrCodeLoginInstructionsFragment : VectorBaseFragment<FragmentQrCodeLoginInstructionsBinding>() {
|
||||
|
||||
private val viewModel: QrCodeLoginViewModel by activityViewModel()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeLoginInstructionsBinding {
|
||||
return FragmentQrCodeLoginInstructionsBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initScanQrCodeButton()
|
||||
initShowQrCodeButton()
|
||||
}
|
||||
|
||||
private fun initShowQrCodeButton() {
|
||||
views.qrCodeLoginInstructionsShowQrCodeButton.debouncedClicks {
|
||||
viewModel.handle(QrCodeLoginAction.ShowQrCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initScanQrCodeButton() {
|
||||
views.qrCodeLoginInstructionsScanQrCodeButton.debouncedClicks {
|
||||
QrCodeScannerActivity.startForResult(requireActivity(), scanActivityResultLauncher)
|
||||
}
|
||||
}
|
||||
|
||||
private val scanActivityResultLauncher = registerStartForActivityResult { activityResult ->
|
||||
if (activityResult.resultCode == Activity.RESULT_OK) {
|
||||
val scannedQrCode = QrCodeScannerActivity.getResultText(activityResult.data)
|
||||
val wasQrCode = QrCodeScannerActivity.getResultIsQrCode(activityResult.data)
|
||||
|
||||
Timber.d("Scanned QR code: $scannedQrCode, was QR code: $wasQrCode")
|
||||
if (wasQrCode && !scannedQrCode.isNullOrBlank()) {
|
||||
onQrCodeScanned(scannedQrCode)
|
||||
} else {
|
||||
onQrCodeScannerFailed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onQrCodeScanned(scannedQrCode: String) {
|
||||
viewModel.handle(QrCodeLoginAction.OnQrCodeScanned(scannedQrCode))
|
||||
}
|
||||
|
||||
private fun onQrCodeScannerFailed() {
|
||||
// The user scanned something unexpected, so we try scanning again.
|
||||
// This seems to happen particularly with the large QRs needed for rendezvous
|
||||
// especially when the QR is partially off the screen
|
||||
Timber.d("QrCodeLoginInstructionsFragment.onQrCodeScannerFailed - showing scanner again")
|
||||
QrCodeScannerActivity.startForResult(requireActivity(), scanActivityResultLauncher)
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
if (state.loginType == QrCodeLoginType.LOGIN) {
|
||||
views.qrCodeLoginInstructionsView.setInstructions(
|
||||
listOf(
|
||||
getString(CommonStrings.qr_code_login_new_device_instruction_1),
|
||||
getString(CommonStrings.qr_code_login_new_device_instruction_2),
|
||||
getString(CommonStrings.qr_code_login_new_device_instruction_3),
|
||||
)
|
||||
)
|
||||
} else {
|
||||
views.qrCodeLoginInstructionsView.setInstructions(
|
||||
listOf(
|
||||
getString(CommonStrings.qr_code_login_link_a_device_scan_qr_code_instruction_1),
|
||||
getString(CommonStrings.qr_code_login_link_a_device_scan_qr_code_instruction_2),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.login.qr
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.res.use
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.app.databinding.ViewQrCodeLoginInstructionsBinding
|
||||
|
||||
class QrCodeLoginInstructionsView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val binding = ViewQrCodeLoginInstructionsBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
this
|
||||
)
|
||||
|
||||
init {
|
||||
context.obtainStyledAttributes(
|
||||
attrs,
|
||||
im.vector.lib.ui.styles.R.styleable.QrCodeLoginInstructionsView,
|
||||
0,
|
||||
0
|
||||
).use {
|
||||
setInstructions(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setInstructions(typedArray: TypedArray) {
|
||||
val instruction1 = typedArray.getString(im.vector.lib.ui.styles.R.styleable.QrCodeLoginInstructionsView_qrCodeLoginInstruction1)
|
||||
val instruction2 = typedArray.getString(im.vector.lib.ui.styles.R.styleable.QrCodeLoginInstructionsView_qrCodeLoginInstruction2)
|
||||
val instruction3 = typedArray.getString(im.vector.lib.ui.styles.R.styleable.QrCodeLoginInstructionsView_qrCodeLoginInstruction3)
|
||||
setInstructions(
|
||||
listOf(
|
||||
instruction1,
|
||||
instruction2,
|
||||
instruction3,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun setInstructions(instructions: List<String?>?) {
|
||||
setInstruction(binding.instructions1Layout, binding.instruction1TextView, instructions?.getOrNull(0))
|
||||
setInstruction(binding.instructions2Layout, binding.instruction2TextView, instructions?.getOrNull(1))
|
||||
setInstruction(binding.instructions3Layout, binding.instruction3TextView, instructions?.getOrNull(2))
|
||||
}
|
||||
|
||||
private fun setInstruction(instructionLayout: LinearLayout, instructionTextView: TextView, instruction: String?) {
|
||||
instruction?.let {
|
||||
instructionLayout.isVisible = true
|
||||
instructionTextView.text = instruction
|
||||
} ?: run {
|
||||
instructionLayout.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.login.qr
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentQrCodeLoginShowQrCodeBinding
|
||||
import im.vector.lib.strings.CommonStrings
|
||||
|
||||
@AndroidEntryPoint
|
||||
class QrCodeLoginShowQrCodeFragment : VectorBaseFragment<FragmentQrCodeLoginShowQrCodeBinding>() {
|
||||
|
||||
private val viewModel: QrCodeLoginViewModel by activityViewModel()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeLoginShowQrCodeBinding {
|
||||
return FragmentQrCodeLoginShowQrCodeBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initCancelButton()
|
||||
viewModel.handle(QrCodeLoginAction.GenerateQrCode)
|
||||
}
|
||||
|
||||
private fun initCancelButton() {
|
||||
views.qrCodeLoginShowQrCodeCancelButton.debouncedClicks {
|
||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setInstructions(loginType: QrCodeLoginType) {
|
||||
if (loginType == QrCodeLoginType.LOGIN) {
|
||||
views.qrCodeLoginShowQrCodeHeaderView.setDescription(getString(CommonStrings.qr_code_login_header_show_qr_code_new_device_description))
|
||||
views.qrCodeLoginShowQrCodeInstructionsView.setInstructions(
|
||||
listOf(
|
||||
getString(CommonStrings.qr_code_login_new_device_instruction_1),
|
||||
getString(CommonStrings.qr_code_login_new_device_instruction_2),
|
||||
getString(CommonStrings.qr_code_login_new_device_instruction_3),
|
||||
)
|
||||
)
|
||||
} else {
|
||||
views.qrCodeLoginShowQrCodeHeaderView.setDescription(getString(CommonStrings.qr_code_login_header_show_qr_code_link_a_device_description))
|
||||
views.qrCodeLoginShowQrCodeInstructionsView.setInstructions(
|
||||
listOf(
|
||||
getString(CommonStrings.qr_code_login_link_a_device_show_qr_code_instruction_1),
|
||||
getString(CommonStrings.qr_code_login_link_a_device_show_qr_code_instruction_2),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showQrCode(qrCodeData: String) {
|
||||
views.qrCodeLoginSHowQrCodeImageView.setData(qrCodeData)
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
state.generatedQrCodeData?.let { qrCodeData ->
|
||||
showQrCode(qrCodeData)
|
||||
}
|
||||
setInstructions(state.loginType)
|
||||
}
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.login.qr
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentQrCodeLoginStatusBinding
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.lib.strings.CommonStrings
|
||||
import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
|
||||
|
||||
@AndroidEntryPoint
|
||||
class QrCodeLoginStatusFragment : VectorBaseFragment<FragmentQrCodeLoginStatusBinding>() {
|
||||
|
||||
private val viewModel: QrCodeLoginViewModel by activityViewModel()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeLoginStatusBinding {
|
||||
return FragmentQrCodeLoginStatusBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initCancelButton()
|
||||
initTryAgainButton()
|
||||
}
|
||||
|
||||
private fun initTryAgainButton() {
|
||||
views.qrCodeLoginStatusTryAgainButton.debouncedClicks {
|
||||
viewModel.handle(QrCodeLoginAction.TryAgain)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initCancelButton() {
|
||||
views.qrCodeLoginStatusCancelButton.debouncedClicks {
|
||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFailed(connectionStatus: QrCodeLoginConnectionStatus.Failed) {
|
||||
views.qrCodeLoginConfirmSecurityCodeLayout.isVisible = false
|
||||
views.qrCodeLoginStatusLoadingLayout.isVisible = false
|
||||
views.qrCodeLoginStatusHeaderView.isVisible = true
|
||||
views.qrCodeLoginStatusSecurityCode.isVisible = false
|
||||
views.qrCodeLoginStatusNoMatchLayout.isVisible = false
|
||||
views.qrCodeLoginStatusCancelButton.isVisible = true
|
||||
views.qrCodeLoginStatusTryAgainButton.isVisible = connectionStatus.canTryAgain
|
||||
views.qrCodeLoginStatusHeaderView.setTitle(getString(CommonStrings.qr_code_login_header_failed_title))
|
||||
views.qrCodeLoginStatusHeaderView.setDescription(getErrorDescription(connectionStatus.errorType))
|
||||
views.qrCodeLoginStatusHeaderView.setImage(
|
||||
imageResource = R.drawable.ic_qr_code_login_failed,
|
||||
backgroundTintColor = ThemeUtils.getColor(requireContext(), com.google.android.material.R.attr.colorError)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getErrorDescription(reason: RendezvousFailureReason): String {
|
||||
return when (reason) {
|
||||
RendezvousFailureReason.UnsupportedAlgorithm,
|
||||
RendezvousFailureReason.UnsupportedTransport -> getString(CommonStrings.qr_code_login_header_failed_device_is_not_supported_description)
|
||||
RendezvousFailureReason.UnsupportedHomeserver -> getString(CommonStrings.qr_code_login_header_failed_homeserver_is_not_supported_description)
|
||||
RendezvousFailureReason.Expired -> getString(CommonStrings.qr_code_login_header_failed_timeout_description)
|
||||
RendezvousFailureReason.UserDeclined -> getString(CommonStrings.qr_code_login_header_failed_denied_description)
|
||||
RendezvousFailureReason.E2EESecurityIssue -> getString(CommonStrings.qr_code_login_header_failed_e2ee_security_issue_description)
|
||||
RendezvousFailureReason.OtherDeviceAlreadySignedIn ->
|
||||
getString(CommonStrings.qr_code_login_header_failed_other_device_already_signed_in_description)
|
||||
RendezvousFailureReason.OtherDeviceNotSignedIn -> getString(CommonStrings.qr_code_login_header_failed_other_device_not_signed_in_description)
|
||||
RendezvousFailureReason.InvalidCode -> getString(CommonStrings.qr_code_login_header_failed_invalid_qr_code_description)
|
||||
RendezvousFailureReason.UserCancelled -> getString(CommonStrings.qr_code_login_header_failed_user_cancelled_description)
|
||||
else -> getString(CommonStrings.qr_code_login_header_failed_other_description)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleConnectingToDevice() {
|
||||
views.qrCodeLoginConfirmSecurityCodeLayout.isVisible = false
|
||||
views.qrCodeLoginStatusLoadingLayout.isVisible = true
|
||||
views.qrCodeLoginStatusHeaderView.isVisible = false
|
||||
views.qrCodeLoginStatusSecurityCode.isVisible = false
|
||||
views.qrCodeLoginStatusNoMatchLayout.isVisible = false
|
||||
views.qrCodeLoginStatusCancelButton.isVisible = true
|
||||
views.qrCodeLoginStatusTryAgainButton.isVisible = false
|
||||
views.qrCodeLoginStatusLoadingTextView.setText(CommonStrings.qr_code_login_connecting_to_device)
|
||||
}
|
||||
|
||||
private fun handleSigningIn() {
|
||||
views.qrCodeLoginConfirmSecurityCodeLayout.isVisible = false
|
||||
views.qrCodeLoginStatusLoadingLayout.isVisible = true
|
||||
views.qrCodeLoginStatusHeaderView.apply {
|
||||
isVisible = true
|
||||
setTitle(getString(CommonStrings.dialog_title_success))
|
||||
setDescription("")
|
||||
setImage(R.drawable.ic_tick, ThemeUtils.getColor(requireContext(), com.google.android.material.R.attr.colorPrimary))
|
||||
}
|
||||
views.qrCodeLoginStatusSecurityCode.isVisible = false
|
||||
views.qrCodeLoginStatusNoMatchLayout.isVisible = false
|
||||
views.qrCodeLoginStatusCancelButton.isVisible = false
|
||||
views.qrCodeLoginStatusTryAgainButton.isVisible = false
|
||||
views.qrCodeLoginStatusLoadingTextView.setText(CommonStrings.qr_code_login_signing_in)
|
||||
}
|
||||
|
||||
private fun handleConnectionEstablished(connectionStatus: QrCodeLoginConnectionStatus.Connected, loginType: QrCodeLoginType) {
|
||||
views.qrCodeLoginConfirmSecurityCodeLayout.isVisible = loginType == QrCodeLoginType.LINK_A_DEVICE
|
||||
views.qrCodeLoginStatusLoadingLayout.isVisible = false
|
||||
views.qrCodeLoginStatusHeaderView.isVisible = true
|
||||
views.qrCodeLoginStatusSecurityCode.isVisible = true
|
||||
views.qrCodeLoginStatusNoMatchLayout.isVisible = loginType == QrCodeLoginType.LOGIN
|
||||
views.qrCodeLoginStatusCancelButton.isVisible = true
|
||||
views.qrCodeLoginStatusTryAgainButton.isVisible = false
|
||||
views.qrCodeLoginStatusSecurityCode.text = connectionStatus.securityCode
|
||||
views.qrCodeLoginStatusHeaderView.setTitle(getString(CommonStrings.qr_code_login_header_connected_title))
|
||||
views.qrCodeLoginStatusHeaderView.setDescription(getString(CommonStrings.qr_code_login_header_connected_description))
|
||||
views.qrCodeLoginStatusHeaderView.setImage(
|
||||
imageResource = R.drawable.ic_qr_code_login_connected,
|
||||
backgroundTintColor = ThemeUtils.getColor(requireContext(), com.google.android.material.R.attr.colorPrimary)
|
||||
)
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
when (state.connectionStatus) {
|
||||
is QrCodeLoginConnectionStatus.Connected -> handleConnectionEstablished(state.connectionStatus, state.loginType)
|
||||
QrCodeLoginConnectionStatus.ConnectingToDevice -> handleConnectingToDevice()
|
||||
QrCodeLoginConnectionStatus.SigningIn -> handleSigningIn()
|
||||
is QrCodeLoginConnectionStatus.Failed -> handleFailed(state.connectionStatus)
|
||||
null -> { /* NOOP */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.login.qr
|
||||
|
||||
enum class QrCodeLoginType {
|
||||
LOGIN,
|
||||
LINK_A_DEVICE,
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.login.qr
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
|
||||
sealed class QrCodeLoginViewEvents : VectorViewEvents {
|
||||
object NavigateToStatusScreen : QrCodeLoginViewEvents()
|
||||
object NavigateToShowQrCodeScreen : QrCodeLoginViewEvents()
|
||||
object NavigateToHomeScreen : QrCodeLoginViewEvents()
|
||||
object NavigateToInitialScreen : QrCodeLoginViewEvents()
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.login.qr
|
||||
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.session.ConfigureAndStartSessionUseCase
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.rendezvous.Rendezvous
|
||||
import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
|
||||
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
|
||||
import timber.log.Timber
|
||||
|
||||
class QrCodeLoginViewModel @AssistedInject constructor(
|
||||
@Assisted private val initialState: QrCodeLoginViewState,
|
||||
private val authenticationService: AuthenticationService,
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase,
|
||||
) : VectorViewModel<QrCodeLoginViewState, QrCodeLoginAction, QrCodeLoginViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<QrCodeLoginViewModel, QrCodeLoginViewState> {
|
||||
override fun create(initialState: QrCodeLoginViewState): QrCodeLoginViewModel
|
||||
}
|
||||
|
||||
companion object : MavericksViewModelFactory<QrCodeLoginViewModel, QrCodeLoginViewState> by hiltMavericksViewModelFactory() {
|
||||
val TAG: String = QrCodeLoginViewModel::class.java.simpleName
|
||||
}
|
||||
|
||||
override fun handle(action: QrCodeLoginAction) {
|
||||
when (action) {
|
||||
is QrCodeLoginAction.OnQrCodeScanned -> handleOnQrCodeScanned(action)
|
||||
QrCodeLoginAction.GenerateQrCode -> handleQrCodeViewStarted()
|
||||
QrCodeLoginAction.ShowQrCode -> handleShowQrCode()
|
||||
QrCodeLoginAction.TryAgain -> handleTryAgain()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleTryAgain() {
|
||||
setState {
|
||||
copy(
|
||||
connectionStatus = null
|
||||
)
|
||||
}
|
||||
_viewEvents.post(QrCodeLoginViewEvents.NavigateToInitialScreen)
|
||||
}
|
||||
|
||||
private fun handleShowQrCode() {
|
||||
_viewEvents.post(QrCodeLoginViewEvents.NavigateToShowQrCodeScreen)
|
||||
}
|
||||
|
||||
private fun handleQrCodeViewStarted() {
|
||||
val qrCodeData = generateQrCodeData()
|
||||
setState {
|
||||
copy(
|
||||
generatedQrCodeData = qrCodeData
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnQrCodeScanned(action: QrCodeLoginAction.OnQrCodeScanned) {
|
||||
Timber.tag(TAG).d("Scanned code of length ${action.qrCode.length}")
|
||||
|
||||
val rendezvous = try { Rendezvous.buildChannelFromCode(action.qrCode) } catch (t: Throwable) {
|
||||
Timber.tag(TAG).e(t, "Error occurred during sign in")
|
||||
if (t is RendezvousError) {
|
||||
onFailed(t.reason)
|
||||
} else {
|
||||
onFailed(RendezvousFailureReason.Unknown)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
connectionStatus = QrCodeLoginConnectionStatus.ConnectingToDevice
|
||||
)
|
||||
}
|
||||
|
||||
_viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen)
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val confirmationCode = rendezvous.startAfterScanningCode()
|
||||
Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode")
|
||||
|
||||
onConnectionEstablished(confirmationCode)
|
||||
|
||||
val session = rendezvous.waitForLoginOnNewDevice(authenticationService)
|
||||
onSigningIn()
|
||||
|
||||
activeSessionHolder.setActiveSession(session)
|
||||
authenticationService.reset()
|
||||
configureAndStartSessionUseCase.execute(session)
|
||||
|
||||
rendezvous.completeVerificationOnNewDevice(session)
|
||||
|
||||
_viewEvents.post(QrCodeLoginViewEvents.NavigateToHomeScreen)
|
||||
} catch (t: Throwable) {
|
||||
Timber.tag(TAG).e(t, "Error occurred during sign in")
|
||||
if (t is RendezvousError) {
|
||||
onFailed(t.reason)
|
||||
} else {
|
||||
onFailed(RendezvousFailureReason.Unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFailed(reason: RendezvousFailureReason) {
|
||||
_viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen)
|
||||
|
||||
setState {
|
||||
copy(
|
||||
connectionStatus = QrCodeLoginConnectionStatus.Failed(reason, reason.canRetry)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onConnectionEstablished(securityCode: String) {
|
||||
val canConfirmSecurityCode = initialState.loginType == QrCodeLoginType.LINK_A_DEVICE
|
||||
setState {
|
||||
copy(
|
||||
connectionStatus = QrCodeLoginConnectionStatus.Connected(securityCode, canConfirmSecurityCode)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSigningIn() {
|
||||
setState {
|
||||
copy(
|
||||
connectionStatus = QrCodeLoginConnectionStatus.SigningIn
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* QR code generation is not currently supported and this is a placeholder for future
|
||||
* functionality.
|
||||
*/
|
||||
private fun generateQrCodeData(): String {
|
||||
return "NOT SUPPORTED"
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.login.qr
|
||||
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
|
||||
data class QrCodeLoginViewState(
|
||||
val loginType: QrCodeLoginType,
|
||||
val connectionStatus: QrCodeLoginConnectionStatus? = null,
|
||||
val generatedQrCodeData: String? = null,
|
||||
) : MavericksState {
|
||||
|
||||
constructor(args: QrCodeLoginArgs) : this(
|
||||
loginType = args.loginType,
|
||||
)
|
||||
}
|
|
@ -71,8 +71,6 @@ import im.vector.app.features.location.live.map.LiveLocationMapViewActivity
|
|||
import im.vector.app.features.location.live.map.LiveLocationMapViewArgs
|
||||
import im.vector.app.features.login.LoginActivity
|
||||
import im.vector.app.features.login.LoginConfig
|
||||
import im.vector.app.features.login.qr.QrCodeLoginActivity
|
||||
import im.vector.app.features.login.qr.QrCodeLoginArgs
|
||||
import im.vector.app.features.matrixto.MatrixToBottomSheet
|
||||
import im.vector.app.features.matrixto.OriginOfMatrixTo
|
||||
import im.vector.app.features.media.AttachmentData
|
||||
|
@ -614,14 +612,6 @@ class DefaultNavigator @Inject constructor(
|
|||
activityResultLauncher.launch(screenCaptureIntent)
|
||||
}
|
||||
|
||||
override fun openLoginWithQrCode(context: Context, qrCodeLoginArgs: QrCodeLoginArgs) {
|
||||
QrCodeLoginActivity
|
||||
.getIntent(context, qrCodeLoginArgs)
|
||||
.also {
|
||||
context.startActivity(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Intent.start(context: Context) {
|
||||
context.startActivity(this)
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
|
|||
import im.vector.app.features.location.LocationData
|
||||
import im.vector.app.features.location.LocationSharingMode
|
||||
import im.vector.app.features.login.LoginConfig
|
||||
import im.vector.app.features.login.qr.QrCodeLoginArgs
|
||||
import im.vector.app.features.matrixto.OriginOfMatrixTo
|
||||
import im.vector.app.features.media.AttachmentData
|
||||
import im.vector.app.features.pin.PinMode
|
||||
|
@ -202,9 +201,4 @@ interface Navigator {
|
|||
screenCaptureIntent: Intent,
|
||||
activityResultLauncher: ActivityResultLauncher<Intent>
|
||||
)
|
||||
|
||||
fun openLoginWithQrCode(
|
||||
context: Context,
|
||||
qrCodeLoginArgs: QrCodeLoginArgs,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -121,29 +121,6 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun checkQrCodeLoginCapability() {
|
||||
if (!vectorFeatures.isQrCodeLoginEnabled()) {
|
||||
setState {
|
||||
copy(
|
||||
canLoginWithQrCode = false
|
||||
)
|
||||
}
|
||||
} else if (vectorFeatures.isQrCodeLoginForAllServers()) {
|
||||
// allow for all servers
|
||||
setState {
|
||||
copy(
|
||||
canLoginWithQrCode = true
|
||||
)
|
||||
}
|
||||
} else {
|
||||
setState {
|
||||
copy(
|
||||
canLoginWithQrCode = selectedHomeserver.isLoginWithQrSupported
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val matrixOrgUrl = stringProvider.getString(im.vector.app.config.R.string.matrix_org_server_url).ensureTrailingSlash()
|
||||
private val defaultHomeserverUrl = mdmService.getData(MdmData.DefaultHomeserverUrl, matrixOrgUrl)
|
||||
|
||||
|
@ -710,7 +687,6 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
||||
} else {
|
||||
startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride, suspend {
|
||||
checkQrCodeLoginCapability()
|
||||
postAction()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -59,8 +59,6 @@ data class OnboardingViewState(
|
|||
|
||||
@PersistState
|
||||
val personalizationState: PersonalizationState = PersonalizationState(),
|
||||
|
||||
val canLoginWithQrCode: Boolean = false,
|
||||
) : MavericksState
|
||||
|
||||
enum class OnboardingFlow {
|
||||
|
|
|
@ -40,8 +40,6 @@ import im.vector.app.features.VectorFeatures
|
|||
import im.vector.app.features.login.LoginMode
|
||||
import im.vector.app.features.login.SSORedirectRouterActivity
|
||||
import im.vector.app.features.login.SocialLoginButtonsView
|
||||
import im.vector.app.features.login.qr.QrCodeLoginArgs
|
||||
import im.vector.app.features.login.qr.QrCodeLoginType
|
||||
import im.vector.app.features.login.render
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.onboarding.OnboardingViewEvents
|
||||
|
@ -75,26 +73,6 @@ class FtueAuthCombinedLoginFragment :
|
|||
viewModel.handle(OnboardingAction.UserNameEnteredAction.Login(views.loginInput.content()))
|
||||
}
|
||||
views.loginForgotPassword.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnForgetPasswordClicked)) }
|
||||
|
||||
viewModel.onEach(OnboardingViewState::canLoginWithQrCode) {
|
||||
configureQrCodeLoginButtonVisibility(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureQrCodeLoginButtonVisibility(canLoginWithQrCode: Boolean) {
|
||||
views.loginWithQrCode.isVisible = canLoginWithQrCode
|
||||
if (canLoginWithQrCode) {
|
||||
views.loginWithQrCode.debouncedClicks {
|
||||
navigator
|
||||
.openLoginWithQrCode(
|
||||
requireActivity(),
|
||||
QrCodeLoginArgs(
|
||||
loginType = QrCodeLoginType.LOGIN,
|
||||
showQrCodeImmediately = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSubmitButton() {
|
||||
|
|
|
@ -41,8 +41,6 @@ import im.vector.app.databinding.FragmentSettingsDevicesBinding
|
|||
import im.vector.app.features.VectorFeatures
|
||||
import im.vector.app.features.auth.ReAuthActivity
|
||||
import im.vector.app.features.crypto.recover.SetupMode
|
||||
import im.vector.app.features.login.qr.QrCodeLoginArgs
|
||||
import im.vector.app.features.login.qr.QrCodeLoginType
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import im.vector.app.features.settings.devices.v2.list.NUMBER_OF_OTHER_DEVICES_TO_RENDER
|
||||
import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
|
||||
|
@ -106,7 +104,6 @@ class VectorSettingsDevicesFragment :
|
|||
initOtherSessionsHeaderView()
|
||||
initOtherSessionsView()
|
||||
initSecurityRecommendationsView()
|
||||
initQrLoginView()
|
||||
observeViewEvents()
|
||||
}
|
||||
|
||||
|
@ -240,38 +237,6 @@ class VectorSettingsDevicesFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun initQrLoginView() {
|
||||
if (!vectorFeatures.isReciprocateQrCodeLogin()) {
|
||||
views.deviceListHeaderSignInWithQrCode.isVisible = false
|
||||
views.deviceListHeaderScanQrCodeButton.isVisible = false
|
||||
views.deviceListHeaderShowQrCodeButton.isVisible = false
|
||||
return
|
||||
}
|
||||
|
||||
views.deviceListHeaderSignInWithQrCode.isVisible = true
|
||||
views.deviceListHeaderScanQrCodeButton.isVisible = true
|
||||
views.deviceListHeaderShowQrCodeButton.isVisible = true
|
||||
|
||||
views.deviceListHeaderScanQrCodeButton.debouncedClicks {
|
||||
navigateToQrCodeScreen(showQrCodeImmediately = false)
|
||||
}
|
||||
|
||||
views.deviceListHeaderShowQrCodeButton.debouncedClicks {
|
||||
navigateToQrCodeScreen(showQrCodeImmediately = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToQrCodeScreen(showQrCodeImmediately: Boolean) {
|
||||
navigator
|
||||
.openLoginWithQrCode(
|
||||
requireActivity(),
|
||||
QrCodeLoginArgs(
|
||||
loginType = QrCodeLoginType.LINK_A_DEVICE,
|
||||
showQrCodeImmediately = showQrCodeImmediately,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
cleanUpLearnMoreButtonsListeners()
|
||||
super.onDestroyView()
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="ring"
|
||||
android:innerRadius="0dp"
|
||||
android:thicknessRatio="2"
|
||||
android:useLevel="false">
|
||||
|
||||
<solid android:color="?android:colorBackground" />
|
||||
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="?colorPrimary" />
|
||||
|
||||
</shape>
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="13dp"
|
||||
android:height="12dp"
|
||||
android:viewportWidth="13"
|
||||
android:viewportHeight="12">
|
||||
<path
|
||||
android:pathData="M7.167,12V10.667H8.5V12H7.167ZM5.833,10.667V7.333H7.167V10.667H5.833ZM11.167,8.667V6H12.5V8.667H11.167ZM9.833,6V4.667H11.167V6H9.833ZM1.833,7.333V6H3.167V7.333H1.833ZM0.5,6V4.667H1.833V6H0.5ZM6.5,1.333V0H7.833V1.333H6.5ZM1.333,3.167H3.667V0.833H1.333V3.167ZM1,4C0.856,4 0.736,3.953 0.642,3.858C0.547,3.764 0.5,3.644 0.5,3.5V0.5C0.5,0.356 0.547,0.236 0.642,0.142C0.736,0.047 0.856,0 1,0H4C4.144,0 4.264,0.047 4.358,0.142C4.453,0.236 4.5,0.356 4.5,0.5V3.5C4.5,3.644 4.453,3.764 4.358,3.858C4.264,3.953 4.144,4 4,4H1ZM1.333,11.167H3.667V8.833H1.333V11.167ZM1,12C0.856,12 0.736,11.953 0.642,11.858C0.547,11.764 0.5,11.644 0.5,11.5V8.5C0.5,8.356 0.547,8.236 0.642,8.142C0.736,8.047 0.856,8 1,8H4C4.144,8 4.264,8.047 4.358,8.142C4.453,8.236 4.5,8.356 4.5,8.5V11.5C4.5,11.644 4.453,11.764 4.358,11.858C4.264,11.953 4.144,12 4,12H1ZM9.333,3.167H11.667V0.833H9.333V3.167ZM9,4C8.856,4 8.736,3.953 8.642,3.858C8.547,3.764 8.5,3.644 8.5,3.5V0.5C8.5,0.356 8.547,0.236 8.642,0.142C8.736,0.047 8.856,0 9,0H12C12.144,0 12.264,0.047 12.358,0.142C12.453,0.236 12.5,0.356 12.5,0.5V3.5C12.5,3.644 12.453,3.764 12.358,3.858C12.264,3.953 12.144,4 12,4H9ZM9.833,12V10H8.5V8.667H11.167V10.667H12.5V12H9.833ZM7.167,7.333V6H9.833V7.333H7.167ZM4.5,7.333V6H3.167V4.667H7.167V6H5.833V7.333H4.5ZM5.167,4V1.333H6.5V2.667H7.833V4H5.167ZM2,2.5V1.5H3V2.5H2ZM2,10.5V9.5H3V10.5H2ZM10,2.5V1.5H11V2.5H10Z"
|
||||
android:fillColor="#0DBD8B"/>
|
||||
</vector>
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="54dp"
|
||||
android:height="38dp"
|
||||
android:viewportWidth="54"
|
||||
android:viewportHeight="38">
|
||||
<path
|
||||
android:pathData="M53.667,19C53.667,17.533 52.467,16.333 51,16.333H45.48C44.173,7.293 36.413,0.333 27,0.333C17.587,0.333 9.827,7.293 8.52,16.333H3C1.533,16.333 0.333,17.533 0.333,19C0.333,20.467 1.533,21.667 3,21.667H8.52C9.827,30.707 17.587,37.667 27,37.667C36.413,37.667 44.173,30.707 45.48,21.667H51C52.467,21.667 53.667,20.467 53.667,19ZM35,25.667C35,27.133 33.8,28.333 32.333,28.333H21.667C20.2,28.333 19,27.133 19,25.667V17.667C19,16.2 20.2,15 21.667,15V12.333C21.667,9.107 24.547,6.52 27.907,7.08C30.52,7.507 32.333,9.96 32.333,12.627V15C33.8,15 35,16.2 35,17.667V25.667ZM29,21.667C29,22.76 28.093,23.667 27,23.667C25.907,23.667 25,22.76 25,21.667C25,20.573 25.907,19.667 27,19.667C28.093,19.667 29,20.573 29,21.667ZM29.667,12.333V15H24.333V12.333C24.333,10.867 25.533,9.667 27,9.667C28.467,9.667 29.667,10.867 29.667,12.333Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
|
@ -1,10 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M20,40C31.046,40 40,31.046 40,20C40,8.954 31.046,0 20,0C8.954,0 0,8.954 0,20C0,31.046 8.954,40 20,40ZM18.084,14.688C18.007,13.809 18.655,13.037 19.535,12.976C20.399,12.914 21.17,13.562 21.263,14.441V14.688L20.769,20.86C20.723,21.431 20.244,21.863 19.674,21.863H19.581C19.041,21.816 18.624,21.4 18.578,20.86L18.084,14.688ZM21.015,24.887C21.015,25.637 20.407,26.244 19.657,26.244C18.907,26.244 18.3,25.637 18.3,24.887C18.3,24.137 18.907,23.529 19.657,23.529C20.407,23.529 21.015,24.137 21.015,24.887Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
|
@ -244,20 +244,6 @@
|
|||
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginSubmit" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/loginWithQrCode"
|
||||
style="@style/Widget.Vector.Button.Outlined.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/login_scan_qr_code"
|
||||
android:visibility="gone"
|
||||
app:drawableLeftCompat="@drawable/ic_qr_code"
|
||||
app:layout_constraintEnd_toEndOf="@id/loginGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/ssoButtonsHeader"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<im.vector.app.features.login.SocialLoginButtonsView
|
||||
android:id="@+id/ssoButtons"
|
||||
android:layout_width="0dp"
|
||||
|
@ -266,7 +252,7 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/loginGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginWithQrCode"
|
||||
app:layout_constraintTop_toBottomOf="@id/ssoButtonsHeader"
|
||||
tools:signMode="signup" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<im.vector.app.features.login.qr.QrCodeLoginHeaderView
|
||||
android:id="@+id/qrCodeLoginInstructionsHeaderView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:qrCodeLoginHeaderDescription="@string/qr_code_login_header_scan_qr_code_description"
|
||||
app:qrCodeLoginHeaderImageBackgroundTint="?colorPrimary"
|
||||
app:qrCodeLoginHeaderImageResource="@drawable/ic_camera"
|
||||
app:qrCodeLoginHeaderTitle="@string/qr_code_login_header_scan_qr_code_title" />
|
||||
|
||||
<im.vector.app.features.login.qr.QrCodeLoginInstructionsView
|
||||
android:id="@+id/qrCodeLoginInstructionsView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginInstructionsHeaderView"
|
||||
app:qrCodeLoginInstruction1="@string/qr_code_login_new_device_instruction_1"
|
||||
app:qrCodeLoginInstruction2="@string/qr_code_login_new_device_instruction_2"
|
||||
app:qrCodeLoginInstruction3="@string/qr_code_login_new_device_instruction_3" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/qrCodeLoginInstructionsShowQrCodeButton"
|
||||
style="@style/Widget.Vector.Button.Outlined.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:text="@string/qr_code_login_show_qr_code_button"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/qrCodeLoginInstructionsAlternativeLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@id/qrCodeLoginInstructionsShowQrCodeButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="@drawable/divider_horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginInstructionsAlternativeTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:colorBackground"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:text="@string/qr_code_login_signing_in_a_mobile_device"
|
||||
app:drawableLeftCompat="@drawable/divider_horizontal"
|
||||
app:drawableTint="@color/alert_default_error_background" />
|
||||
</FrameLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/qrCodeLoginInstructionsScanQrCodeButton"
|
||||
style="@style/Widget.Vector.Button.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:text="@string/qr_code_login_scan_qr_code_button"
|
||||
android:textAllCaps="false"
|
||||
app:layout_constraintBottom_toTopOf="@id/qrCodeLoginInstructionsAlternativeLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,53 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:colorBackground">
|
||||
|
||||
<im.vector.app.features.login.qr.QrCodeLoginHeaderView
|
||||
android:id="@+id/qrCodeLoginShowQrCodeHeaderView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:qrCodeLoginHeaderDescription="@string/qr_code_login_header_show_qr_code_link_a_device_description"
|
||||
app:qrCodeLoginHeaderImageBackgroundTint="?colorPrimary"
|
||||
app:qrCodeLoginHeaderImageResource="@drawable/ic_camera"
|
||||
app:qrCodeLoginHeaderTitle="@string/qr_code_login_header_show_qr_code_title" />
|
||||
|
||||
<im.vector.app.features.login.qr.QrCodeLoginInstructionsView
|
||||
android:id="@+id/qrCodeLoginShowQrCodeInstructionsView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginShowQrCodeHeaderView"
|
||||
app:qrCodeLoginInstruction1="@string/qr_code_login_new_device_instruction_1"
|
||||
app:qrCodeLoginInstruction2="@string/qr_code_login_new_device_instruction_2"
|
||||
app:qrCodeLoginInstruction3="@string/qr_code_login_new_device_instruction_3" />
|
||||
|
||||
<im.vector.app.core.ui.views.QrCodeImageView
|
||||
android:id="@+id/qrCodeLoginSHowQrCodeImageView"
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="240dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginShowQrCodeInstructionsView"
|
||||
app:layout_constraintBottom_toTopOf="@id/qrCodeLoginShowQrCodeCancelButton"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/qrCodeLoginShowQrCodeCancelButton"
|
||||
style="@style/Widget.Vector.Button.Outlined.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:text="@string/action_cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,155 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:colorBackground"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/qrCodeLoginStatusLoadingLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="100dp"
|
||||
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_toTopOf="parent"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginStatusLoadingTextView"
|
||||
style="@style/TextAppearance.Vector.Subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="@string/qr_code_login_connecting_to_device" />
|
||||
|
||||
<include layout="@layout/item_loading" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<im.vector.app.features.login.qr.QrCodeLoginHeaderView
|
||||
android:id="@+id/qrCodeLoginStatusHeaderView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:qrCodeLoginHeaderDescription="@string/qr_code_login_header_connected_description"
|
||||
app:qrCodeLoginHeaderImageBackgroundTint="?colorPrimary"
|
||||
app:qrCodeLoginHeaderImageResource="@drawable/ic_qr_code_login_connected"
|
||||
app:qrCodeLoginHeaderTitle="@string/qr_code_login_header_connected_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginStatusSecurityCode"
|
||||
style="@style/TextAppearance.Vector.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="80dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginStatusHeaderView"
|
||||
tools:text="28E-1B9-D0F-896"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/qrCodeLoginStatusNoMatchLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/qrCodeLoginStatusCancelButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="@drawable/divider_horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginStatusNoMatchTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:colorBackground"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:text="@string/qr_code_login_status_no_match"
|
||||
app:drawableLeftCompat="@drawable/divider_horizontal"
|
||||
app:drawableTint="@color/alert_default_error_background" />
|
||||
</FrameLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/qrCodeLoginStatusTryAgainButton"
|
||||
style="@style/Widget.Vector.Button.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="@string/qr_code_login_try_again"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@id/qrCodeLoginStatusNoMatchLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/qrCodeLoginConfirmSecurityCodeLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@id/qrCodeLoginStatusCancelButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qrCodeLoginConfirmSecurityCodeImageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/ic_info"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginConfirmSecurityCodeTextView"
|
||||
style="@style/TextAppearance.Vector.Body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="@string/qr_code_login_confirm_security_code_description"
|
||||
app:layout_constraintStart_toEndOf="@id/qrCodeLoginConfirmSecurityCodeImageView"
|
||||
app:layout_constraintTop_toTopOf="@id/qrCodeLoginConfirmSecurityCodeImageView" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/qrCodeLoginConfirmSecurityCodeButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/qr_code_login_confirm_security_code"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginConfirmSecurityCodeTextView" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/qrCodeLoginStatusCancelButton"
|
||||
style="@style/Widget.Vector.Button.Outlined.Login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:text="@string/action_cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
@ -111,47 +110,6 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderOtherSessions" />
|
||||
|
||||
<im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView
|
||||
android:id="@+id/deviceListHeaderSignInWithQrCode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/deviceListOtherSessions"
|
||||
app:sessionsListHeaderDescription="@string/device_manager_sessions_sign_in_with_qr_code_description"
|
||||
app:sessionsListHeaderHasLearnMoreLink="false"
|
||||
app:sessionsListHeaderTitle="@string/device_manager_sessions_sign_in_with_qr_code_title"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/deviceListHeaderScanQrCodeButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/qr_code_login_scan_qr_code_button"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderSignInWithQrCode"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/deviceListHeaderShowQrCodeButton"
|
||||
style="@style/Widget.Vector.Button.Outlined"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="@string/qr_code_login_show_qr_code_button"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderScanQrCodeButton"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<include
|
||||
android:id="@+id/waiting_view"
|
||||
layout="@layout/merge_overlay_waiting_view"
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qrCodeLoginHeaderImageView"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:background="@drawable/circle"
|
||||
android:contentDescription="@string/qr_code_login_header_scan_qr_code_title"
|
||||
android:padding="12dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?vctr_system"
|
||||
tools:backgroundTint="?colorPrimary"
|
||||
tools:src="@drawable/ic_camera" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginHeaderTitleTextView"
|
||||
style="@style/TextAppearance.Vector.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginHeaderImageView"
|
||||
tools:text="@string/qr_code_login_header_scan_qr_code_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrCodeLoginHeaderDescriptionTextView"
|
||||
style="@style/TextAppearance.Vector.Subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/qrCodeLoginHeaderTitleTextView"
|
||||
tools:text="@string/qr_code_login_header_scan_qr_code_description" />
|
||||
|
||||
</merge>
|
|
@ -1,101 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/instructions1Layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Vector.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/circle_qr_code_login_instruction_with_border"
|
||||
android:padding="6dp"
|
||||
android:text="@string/one"
|
||||
android:textColor="?colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/instruction1TextView"
|
||||
style="@style/TextAppearance.Vector.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
tools:text="@string/qr_code_login_new_device_instruction_1" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/instructions2Layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/instructions1Layout"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Vector.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/circle_qr_code_login_instruction_with_border"
|
||||
android:padding="6dp"
|
||||
android:text="@string/two"
|
||||
android:textColor="?colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/instruction2TextView"
|
||||
style="@style/TextAppearance.Vector.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
tools:text="@string/qr_code_login_new_device_instruction_2" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/instructions3Layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/instructions2Layout"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Vector.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/circle_qr_code_login_instruction_with_border"
|
||||
android:padding="6dp"
|
||||
android:text="@string/three"
|
||||
android:textColor="?colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/instruction3TextView"
|
||||
style="@style/TextAppearance.Vector.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
tools:text="@string/qr_code_login_new_device_instruction_3" />
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
|
@ -162,28 +162,6 @@ class OnboardingViewModelTest {
|
|||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given combined login enabled, when handling sign in splash action, then emits OpenCombinedLogin with default homeserver qrCode supported`() = runTest {
|
||||
val test = viewModel.test()
|
||||
fakeVectorFeatures.givenCombinedLoginEnabled()
|
||||
givenCanSuccessfullyUpdateHomeserver(A_DEFAULT_HOMESERVER_URL, DEFAULT_SELECTED_HOMESERVER_STATE_WITH_QR_SUPPORTED)
|
||||
|
||||
viewModel.handle(OnboardingAction.SplashAction.OnIAlreadyHaveAnAccount(OnboardingFlow.SignIn))
|
||||
|
||||
test
|
||||
.assertStatesChanges(
|
||||
initialState,
|
||||
{ copy(onboardingFlow = OnboardingFlow.SignIn) },
|
||||
{ copy(isLoading = true) },
|
||||
{ copy(selectedHomeserver = DEFAULT_SELECTED_HOMESERVER_STATE_WITH_QR_SUPPORTED) },
|
||||
{ copy(signMode = SignMode.SignIn) },
|
||||
{ copy(canLoginWithQrCode = true) },
|
||||
{ copy(isLoading = false) }
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.OpenCombinedLogin)
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given can successfully login in with token, when logging in with token, then emits AccountSignedIn`() = runTest {
|
||||
val test = viewModel.test()
|
||||
|
|
Loading…
Reference in a new issue