Basic Incoming request toast + cleaning

This commit is contained in:
Valere 2020-01-03 14:54:10 +01:00
parent 8400ab6efe
commit 8749e49e80
25 changed files with 189 additions and 1355 deletions

View file

@ -44,6 +44,7 @@ interface SasVerificationService {
fun getExistingVerificationRequest(otherUser: String, tid: String?): PendingVerificationRequest? fun getExistingVerificationRequest(otherUser: String, tid: String?): PendingVerificationRequest?
fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest?
/** /**
* Shortcut for KeyVerificationStart.VERIF_METHOD_SAS * Shortcut for KeyVerificationStart.VERIF_METHOD_SAS
* @see beginKeyVerification * @see beginKeyVerification

View file

@ -104,6 +104,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
// The verification is started from another device // The verification is started from another device
Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ") Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ")
it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) } it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
params.sasVerificationService.onRoomRequestHandledByOtherDevice(event)
} }
} }
} else if (EventType.KEY_VERIFICATION_READY == event.type) { } else if (EventType.KEY_VERIFICATION_READY == event.type) {
@ -112,11 +113,13 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
// The verification is started from another device // The verification is started from another device
Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ") Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ")
it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) } it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
params.sasVerificationService.onRoomRequestHandledByOtherDevice(event)
} }
} }
} else if (EventType.KEY_VERIFICATION_CANCEL == event.type || EventType.KEY_VERIFICATION_DONE == event.type) { } else if (EventType.KEY_VERIFICATION_CANCEL == event.type || EventType.KEY_VERIFICATION_DONE == event.type) {
event.getClearContent().toModel<MessageRelationContent>()?.relatesTo?.eventId?.let { event.getClearContent().toModel<MessageRelationContent>()?.relatesTo?.eventId?.let {
transactionsHandledByOtherDevice.remove(it) transactionsHandledByOtherDevice.remove(it)
params.sasVerificationService.onRoomRequestHandledByOtherDevice(event)
} }
} }

View file

@ -216,7 +216,20 @@ internal class DefaultSasVerificationService @Inject constructor(
} }
} }
fun onRoomRequestReceived(event: Event) { fun onRoomRequestHandledByOtherDevice(event: Event) {
val requestInfo = event.getClearContent().toModel<MessageRelationContent>()
?: return
val requestId = requestInfo.relatesTo?.eventId ?: return
getExistingVerificationRequestInRoom(event.roomId ?: "", requestId)?.let {
updatePendingRequest(
it.copy(
handledByOtherSession = true
)
)
}
}
suspend fun onRoomRequestReceived(event: Event) {
Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}") Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}")
val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>() val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>()
?: return ?: return
@ -245,6 +258,7 @@ internal class DefaultSasVerificationService @Inject constructor(
ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(), ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
isIncoming = true, isIncoming = true,
otherUserId = senderId, // requestInfo.toUserId, otherUserId = senderId, // requestInfo.toUserId,
roomId = event.roomId,
transactionId = event.eventId, transactionId = event.eventId,
requestInfo = requestInfo requestInfo = requestInfo
) )
@ -647,6 +661,16 @@ internal class DefaultSasVerificationService @Inject constructor(
} }
} }
override fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest? {
synchronized(lock = pendingRequests) {
return tid?.let { tid ->
pendingRequests.flatMap { entry ->
entry.value.filter { it.roomId == roomId && it.transactionId == tid }
}.firstOrNull()
}
}
}
private fun getExistingTransactionsForUser(otherUser: String): Collection<VerificationTransaction>? { private fun getExistingTransactionsForUser(otherUser: String): Collection<VerificationTransaction>? {
synchronized(txMap) { synchronized(txMap) {
return txMap[otherUser]?.values return txMap[otherUser]?.values
@ -723,6 +747,7 @@ internal class DefaultSasVerificationService @Inject constructor(
val verificationRequest = PendingVerificationRequest( val verificationRequest = PendingVerificationRequest(
ageLocalTs = System.currentTimeMillis(), ageLocalTs = System.currentTimeMillis(),
isIncoming = false, isIncoming = false,
roomId = roomId,
localID = localID, localID = localID,
otherUserId = userId otherUserId = userId
) )

View file

@ -27,11 +27,13 @@ data class PendingVerificationRequest(
val isIncoming: Boolean = false, val isIncoming: Boolean = false,
val localID: String = UUID.randomUUID().toString(), val localID: String = UUID.randomUUID().toString(),
val otherUserId: String, val otherUserId: String,
val roomId: String?,
val transactionId: String? = null, val transactionId: String? = null,
val requestInfo: MessageVerificationRequestContent? = null, val requestInfo: MessageVerificationRequestContent? = null,
val readyInfo: VerificationInfoReady? = null, val readyInfo: VerificationInfoReady? = null,
val cancelConclusion: CancelCode? = null, val cancelConclusion: CancelCode? = null,
val isSuccessful : Boolean = false val isSuccessful : Boolean = false,
val handledByOtherSession : Boolean = false
) { ) {

View file

@ -47,9 +47,6 @@
android:label="@string/title_activity_settings" android:label="@string/title_activity_settings"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity android:name=".features.media.VideoMediaViewerActivity" /> <activity android:name=".features.media.VideoMediaViewerActivity" />
<activity
android:name=".features.crypto.verification.SASVerificationActivity"
android:label="@string/title_activity_verify_device" />
<activity <activity
android:name=".features.crypto.keysbackup.restore.KeysBackupRestoreActivity" android:name=".features.crypto.keysbackup.restore.KeysBackupRestoreActivity"
android:label="@string/title_activity_keys_backup_setup" /> android:label="@string/title_activity_keys_backup_setup" />

View file

@ -234,31 +234,6 @@ interface FragmentModule {
@FragmentKey(VectorSettingsIgnoredUsersFragment::class) @FragmentKey(VectorSettingsIgnoredUsersFragment::class)
fun bindVectorSettingsIgnoredUsersFragment(fragment: VectorSettingsIgnoredUsersFragment): Fragment fun bindVectorSettingsIgnoredUsersFragment(fragment: VectorSettingsIgnoredUsersFragment): Fragment
@Binds
@IntoMap
@FragmentKey(VectorSettingsDevicesFragment::class)
fun bindVectorSettingsDevicesFragment(fragment: VectorSettingsDevicesFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SASVerificationIncomingFragment::class)
fun bindSASVerificationIncomingFragment(fragment: SASVerificationIncomingFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SASVerificationShortCodeFragment::class)
fun bindSASVerificationShortCodeFragment(fragment: SASVerificationShortCodeFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SASVerificationVerifiedFragment::class)
fun bindSASVerificationVerifiedFragment(fragment: SASVerificationVerifiedFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SASVerificationStartFragment::class)
fun bindSASVerificationStartFragment(fragment: SASVerificationStartFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(PublicRoomsFragment::class) @FragmentKey(PublicRoomsFragment::class)

View file

@ -27,7 +27,6 @@ import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromK
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
import im.vector.riotx.features.home.HomeSharedActionViewModel import im.vector.riotx.features.home.HomeSharedActionViewModel
import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel
import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel
@ -61,11 +60,6 @@ interface ViewModelModule {
@ViewModelKey(EmojiChooserViewModel::class) @ViewModelKey(EmojiChooserViewModel::class)
fun bindEmojiChooserViewModel(viewModel: EmojiChooserViewModel): ViewModel fun bindEmojiChooserViewModel(viewModel: EmojiChooserViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(SasVerificationViewModel::class)
fun bindSasVerificationViewModel(viewModel: SasVerificationViewModel): ViewModel
@Binds @Binds
@IntoMap @IntoMap
@ViewModelKey(KeysBackupRestoreFromKeyViewModel::class) @ViewModelKey(KeysBackupRestoreFromKeyViewModel::class)

View file

@ -101,10 +101,12 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
private lateinit var configurationViewModel: ConfigurationViewModel private lateinit var configurationViewModel: ConfigurationViewModel
private lateinit var sessionListener: SessionListener private lateinit var sessionListener: SessionListener
protected lateinit var bugReporter: BugReporter protected lateinit var bugReporter: BugReporter
lateinit var rageShake: RageShake private lateinit var rageShake: RageShake
lateinit var navigator: Navigator
private set private set
protected lateinit var navigator: Navigator
private lateinit var fragmentFactory: FragmentFactory private lateinit var fragmentFactory: FragmentFactory
private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var activeSessionHolder: ActiveSessionHolder
private lateinit var vectorPreferences: VectorPreferences private lateinit var vectorPreferences: VectorPreferences
@ -210,8 +212,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
handleInvalidToken(globalError) handleInvalidToken(globalError)
is GlobalError.ConsentNotGivenError -> is GlobalError.ConsentNotGivenError ->
consentNotGivenHelper.displayDialog(globalError.consentUri, consentNotGivenHelper.displayDialog(globalError.consentUri,
activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host
?: "") ?: "")
} }
} }

View file

@ -33,13 +33,11 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.features.crypto.verification.SASVerificationActivity
import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.popup.PopupAlertManager
import timber.log.Timber import timber.log.Timber
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.*
import java.util.Date
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -195,18 +193,19 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
denyAllRequests(mappingKey) denyAllRequests(mappingKey)
} }
alert.addButton( // TODO send to the new profile page
context.getString(R.string.start_verification_short_label), // alert.addButton(
Runnable { // context.getString(R.string.start_verification_short_label),
alert.weakCurrentActivity?.get()?.let { // Runnable {
val intent = SASVerificationActivity.outgoingIntent(it, // alert.weakCurrentActivity?.get()?.let {
session?.myUserId ?: "", // val intent = SASVerificationActivity.outgoingIntent(it,
userId, deviceId) // session?.myUserId ?: "",
it.startActivity(intent) // userId, deviceId)
} // it.startActivity(intent)
}, // }
false // },
) // false
// )
alert.addButton(context.getString(R.string.share_without_verifying_short_label), Runnable { alert.addButton(context.getString(R.string.share_without_verifying_short_label), Runnable {
shareAllSessions(mappingKey) shareAllSessions(mappingKey)

View file

@ -20,8 +20,13 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.popup.PopupAlertManager
import im.vector.riotx.features.themes.ThemeUtils
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -48,46 +53,46 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
override fun transactionUpdated(tx: SasVerificationTransaction) { override fun transactionUpdated(tx: SasVerificationTransaction) {
when (tx.state) { when (tx.state) {
SasVerificationTxState.OnStarted -> { SasVerificationTxState.OnStarted -> {
// Add a notification for every incoming request // // Add a notification for every incoming request
val name = session?.getUser(tx.otherUserId)?.displayName // val name = session?.getUser(tx.otherUserId)?.displayName
?: tx.otherUserId // ?: tx.otherUserId
//
val alert = PopupAlertManager.VectorAlert( // val alert = PopupAlertManager.VectorAlert(
"kvr_${tx.transactionId}", // "kvr_${tx.transactionId}",
context.getString(R.string.sas_incoming_request_notif_title), // context.getString(R.string.sas_incoming_request_notif_title),
context.getString(R.string.sas_incoming_request_notif_content, name), // context.getString(R.string.sas_incoming_request_notif_content, name),
R.drawable.shield) // R.drawable.shield)
.apply { // .apply {
contentAction = Runnable { // contentAction = Runnable {
val intent = SASVerificationActivity.incomingIntent(context, // val intent = SASVerificationActivity.incomingIntent(context,
session?.myUserId ?: "", // session?.myUserId ?: "",
tx.otherUserId, // tx.otherUserId,
tx.transactionId) // tx.transactionId)
weakCurrentActivity?.get()?.startActivity(intent) // weakCurrentActivity?.get()?.startActivity(intent)
} // }
dismissedAction = Runnable { // dismissedAction = Runnable {
tx.cancel() // tx.cancel()
} // }
addButton( // addButton(
context.getString(R.string.ignore), // context.getString(R.string.ignore),
Runnable { // Runnable {
tx.cancel() // tx.cancel()
} // }
) // )
addButton( // addButton(
context.getString(R.string.action_open), // context.getString(R.string.action_open),
Runnable { // Runnable {
val intent = SASVerificationActivity.incomingIntent(context, // val intent = SASVerificationActivity.incomingIntent(context,
session?.myUserId ?: "", // session?.myUserId ?: "",
tx.otherUserId, // tx.otherUserId,
tx.transactionId) // tx.transactionId)
weakCurrentActivity?.get()?.startActivity(intent) // weakCurrentActivity?.get()?.startActivity(intent)
} // }
) // )
// 10mn expiration // // 10mn expiration
expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L) // expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L)
} // }
PopupAlertManager.postVectorAlert(alert) // PopupAlertManager.postVectorAlert(alert)
} }
SasVerificationTxState.Cancelled, SasVerificationTxState.Cancelled,
SasVerificationTxState.OnCancelled, SasVerificationTxState.OnCancelled,
@ -101,4 +106,54 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
override fun markedAsManuallyVerified(userId: String, deviceId: String) { override fun markedAsManuallyVerified(userId: String, deviceId: String) {
} }
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// For incoming request we should prompt (if not in activity where this request apply)
if (pr.isIncoming) {
val name = session?.getUser(pr.otherUserId)?.displayName
?: pr.otherUserId
val alert = PopupAlertManager.VectorAlert(
uniqueIdForVerificationRequest(pr),
context.getString(R.string.sas_incoming_request_notif_title),
"$name(${pr.otherUserId})",
R.drawable.ic_shield_black,
shouldBeDisplayedIn = { activity ->
if (activity is RoomDetailActivity) {
activity.intent?.extras?.getParcelable<RoomDetailArgs>(RoomDetailActivity.EXTRA_ROOM_DETAIL_ARGS)?.let {
it.roomId != pr.roomId
} ?: true
} else true
})
.apply {
contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
it.navigator.openRoom(it, pr.roomId ?: "", pr.transactionId)
}
}
dismissedAction = Runnable {
session?.getSasVerificationService()?.declineVerificationRequestInDMs(pr.otherUserId,
pr.requestInfo?.fromDevice ?: "",
pr.transactionId ?: "",
pr.roomId ?: ""
)
}
colorInt = ThemeUtils.getColor(context, R.attr.vctr_notice_secondary)
// 5mn expiration
expirationTimestamp = System.currentTimeMillis() + (5 * 60 * 1000L)
}
PopupAlertManager.postVectorAlert(alert)
}
}
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// If an incoming request is readied (by another device?) we should discard the alert
if (pr.isIncoming && (pr.isReady || pr.handledByOtherSession)) {
PopupAlertManager.cancelAlert(uniqueIdForVerificationRequest(pr))
}
super.verificationRequestUpdated(pr)
}
private fun uniqueIdForVerificationRequest(pr: PendingVerificationRequest) =
"verificationRequest_${pr.transactionId}"
} }

View file

@ -1,245 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.crypto.verification
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.Observer
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.riotx.R
import im.vector.riotx.core.extensions.commitTransaction
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.platform.SimpleFragmentActivity
import im.vector.riotx.core.platform.WaitingViewData
// TODO Deprecated("replaced by bottomsheet UX")
class SASVerificationActivity : SimpleFragmentActivity() {
companion object {
private const val EXTRA_MATRIX_ID = "EXTRA_MATRIX_ID"
private const val EXTRA_TRANSACTION_ID = "EXTRA_TRANSACTION_ID"
private const val EXTRA_OTHER_USER_ID = "EXTRA_OTHER_USER_ID"
private const val EXTRA_OTHER_DEVICE_ID = "EXTRA_OTHER_DEVICE_ID"
private const val EXTRA_IS_INCOMING = "EXTRA_IS_INCOMING"
/* ==========================================================================================
* INPUT
* ========================================================================================== */
fun incomingIntent(context: Context, matrixID: String, otherUserId: String, transactionID: String): Intent {
val intent = Intent(context, SASVerificationActivity::class.java)
intent.putExtra(EXTRA_MATRIX_ID, matrixID)
intent.putExtra(EXTRA_TRANSACTION_ID, transactionID)
intent.putExtra(EXTRA_OTHER_USER_ID, otherUserId)
intent.putExtra(EXTRA_IS_INCOMING, true)
return intent
}
fun outgoingIntent(context: Context, matrixID: String, otherUserId: String, otherDeviceId: String): Intent {
val intent = Intent(context, SASVerificationActivity::class.java)
intent.putExtra(EXTRA_MATRIX_ID, matrixID)
intent.putExtra(EXTRA_OTHER_DEVICE_ID, otherDeviceId)
intent.putExtra(EXTRA_OTHER_USER_ID, otherUserId)
intent.putExtra(EXTRA_IS_INCOMING, false)
return intent
}
/* ==========================================================================================
* OUTPUT
* ========================================================================================== */
fun getOtherUserId(intent: Intent?): String? {
return intent?.getStringExtra(EXTRA_OTHER_USER_ID)
}
fun getOtherDeviceId(intent: Intent?): String? {
return intent?.getStringExtra(EXTRA_OTHER_DEVICE_ID)
}
}
override fun getTitleRes() = R.string.title_activity_verify_device
private lateinit var viewModel: SasVerificationViewModel
override fun initUiAndData() {
super.initUiAndData()
viewModel = viewModelProvider.get(SasVerificationViewModel::class.java)
val transactionID: String? = intent.getStringExtra(EXTRA_TRANSACTION_ID)
if (isFirstCreation()) {
val isIncoming = intent.getBooleanExtra(EXTRA_IS_INCOMING, false)
if (isIncoming) {
// incoming always have a transaction id
viewModel.initIncoming(session, intent.getStringExtra(EXTRA_OTHER_USER_ID), transactionID)
} else {
viewModel.initOutgoing(session, intent.getStringExtra(EXTRA_OTHER_USER_ID), intent.getStringExtra(EXTRA_OTHER_DEVICE_ID))
}
if (isIncoming) {
val incoming = viewModel.transaction as? IncomingSasVerificationTransaction
when (incoming?.uxState) {
null,
IncomingSasVerificationTransaction.UxState.UNKNOWN,
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT,
IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT -> {
supportActionBar?.setTitle(R.string.sas_incoming_request_title)
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationIncomingFragment::class.java, null)
}
}
IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION,
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationShortCodeFragment::class.java, null)
}
}
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationVerifiedFragment::class.java, null)
}
}
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME,
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER -> {
viewModel.navigateCancel()
}
}
} else {
val outgoing = viewModel.transaction as? OutgoingSasVerificationRequest
// transaction can be null, as not yet created
when (outgoing?.uxState) {
null,
OutgoingSasVerificationRequest.UxState.UNKNOWN,
OutgoingSasVerificationRequest.UxState.WAIT_FOR_START,
OutgoingSasVerificationRequest.UxState.WAIT_FOR_KEY_AGREEMENT -> {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationStartFragment::class.java, null)
}
}
OutgoingSasVerificationRequest.UxState.SHOW_SAS,
OutgoingSasVerificationRequest.UxState.WAIT_FOR_VERIFICATION -> {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationShortCodeFragment::class.java, null)
}
}
OutgoingSasVerificationRequest.UxState.VERIFIED -> {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationVerifiedFragment::class.java, null)
}
}
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME,
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER -> {
viewModel.navigateCancel()
}
}
}
}
viewModel.navigateEvent.observeEvent(this) { uxStateEvent ->
when (uxStateEvent) {
SasVerificationViewModel.NAVIGATE_FINISH -> {
finish()
}
SasVerificationViewModel.NAVIGATE_FINISH_SUCCESS -> {
val dataResult = Intent()
dataResult.putExtra(EXTRA_OTHER_DEVICE_ID, viewModel.otherDeviceId)
dataResult.putExtra(EXTRA_OTHER_USER_ID, viewModel.otherUserId)
setResult(Activity.RESULT_OK, dataResult)
finish()
}
SasVerificationViewModel.NAVIGATE_SAS_DISPLAY -> {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.enter_from_right, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationShortCodeFragment::class.java, null)
}
}
SasVerificationViewModel.NAVIGATE_SUCCESS -> {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.enter_from_right, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationVerifiedFragment::class.java, null)
}
}
SasVerificationViewModel.NAVIGATE_CANCELLED -> {
val isCancelledByMe = viewModel.transaction?.state == SasVerificationTxState.Cancelled
val humanReadableReason = when (viewModel.transaction?.cancelledReason) {
CancelCode.User -> getString(R.string.sas_error_m_user)
CancelCode.Timeout -> getString(R.string.sas_error_m_timeout)
CancelCode.UnknownTransaction -> getString(R.string.sas_error_m_unknown_transaction)
CancelCode.UnknownMethod -> getString(R.string.sas_error_m_unknown_method)
CancelCode.MismatchedCommitment -> getString(R.string.sas_error_m_mismatched_commitment)
CancelCode.MismatchedSas -> getString(R.string.sas_error_m_mismatched_sas)
CancelCode.UnexpectedMessage -> getString(R.string.sas_error_m_unexpected_message)
CancelCode.InvalidMessage -> getString(R.string.sas_error_m_invalid_message)
CancelCode.MismatchedKeys -> getString(R.string.sas_error_m_key_mismatch)
// Use user error
CancelCode.UserMismatchError -> getString(R.string.sas_error_m_user_error)
null -> getString(R.string.sas_error_unknown)
}
val message =
if (isCancelledByMe) getString(R.string.sas_cancelled_by_me, humanReadableReason)
else getString(R.string.sas_cancelled_by_other, humanReadableReason)
// Show a dialog
if (!this.isFinishing) {
AlertDialog.Builder(this)
.setTitle(R.string.sas_cancelled_dialog_title)
.setMessage(message)
.setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ ->
// nop
finish()
}
.show()
}
}
}
}
viewModel.loadingLiveEvent.observe(this, Observer {
if (it == null) {
hideWaitingView()
} else {
val status = if (it == -1) "" else getString(it)
updateWaitingView(WaitingViewData(status, isIndeterminate = true))
}
})
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
// we want to cancel the transaction
viewModel.cancelTransaction()
}
return super.onOptionsItemSelected(item)
}
override fun onBackPressed() {
// we want to cancel the transaction
viewModel.cancelTransaction()
}
}

View file

@ -1,100 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.crypto.verification
import android.os.Bundle
import android.widget.ImageView
import android.widget.TextView
import androidx.lifecycle.Observer
import butterknife.BindView
import butterknife.OnClick
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
// TODO Deprecated("replaced by bottomsheet UX")
class SASVerificationIncomingFragment @Inject constructor(
private var avatarRenderer: AvatarRenderer
) : VectorBaseFragment() {
@BindView(R.id.sas_incoming_request_user_display_name)
lateinit var otherUserDisplayNameTextView: TextView
@BindView(R.id.sas_incoming_request_user_id)
lateinit var otherUserIdTextView: TextView
@BindView(R.id.sas_incoming_request_user_device)
lateinit var otherDeviceTextView: TextView
@BindView(R.id.sas_incoming_request_user_avatar)
lateinit var avatarImageView: ImageView
override fun getLayoutResId() = R.layout.fragment_sas_verification_incoming_request
private lateinit var viewModel: SasVerificationViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = activityViewModelProvider.get(SasVerificationViewModel::class.java)
otherUserDisplayNameTextView.text = viewModel.otherUser?.displayName ?: viewModel.otherUserId
otherUserIdTextView.text = viewModel.otherUserId
otherDeviceTextView.text = viewModel.otherDeviceId
viewModel.otherUser?.let {
avatarRenderer.render(it.toMatrixItem(), avatarImageView)
} ?: run {
// Fallback to what we know
avatarRenderer.render(MatrixItem.UserItem(viewModel.otherUserId ?: "", viewModel.otherUserId), avatarImageView)
}
viewModel.transactionState.observe(viewLifecycleOwner, Observer {
val uxState = (viewModel.transaction as? IncomingSasVerificationTransaction)?.uxState
when (uxState) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
viewModel.loadingLiveEvent.value = null
}
IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT -> {
viewModel.loadingLiveEvent.value = R.string.sas_waiting_for_partner
}
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
viewModel.shortCodeReady()
}
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME,
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER -> {
viewModel.loadingLiveEvent.value = null
viewModel.navigateCancel()
}
else -> Unit
}
})
}
@OnClick(R.id.sas_request_continue_button)
fun didAccept() {
viewModel.acceptTransaction()
}
@OnClick(R.id.sas_request_cancel_button)
fun didCancel() {
viewModel.cancelTransaction()
}
}

View file

@ -1,170 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.crypto.verification
import android.os.Bundle
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import butterknife.BindView
import butterknife.OnClick
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
import javax.inject.Inject
// TODO Deprecated("replaced by bottomsheet UX")
class SASVerificationShortCodeFragment @Inject constructor(): VectorBaseFragment() {
private lateinit var viewModel: SasVerificationViewModel
@BindView(R.id.sas_decimal_code)
lateinit var decimalTextView: TextView
@BindView(R.id.sas_emoji_description)
lateinit var descriptionTextView: TextView
@BindView(R.id.sas_emoji_grid)
lateinit var emojiGrid: ViewGroup
@BindView(R.id.emoji0)
lateinit var emoji0View: ViewGroup
@BindView(R.id.emoji1)
lateinit var emoji1View: ViewGroup
@BindView(R.id.emoji2)
lateinit var emoji2View: ViewGroup
@BindView(R.id.emoji3)
lateinit var emoji3View: ViewGroup
@BindView(R.id.emoji4)
lateinit var emoji4View: ViewGroup
@BindView(R.id.emoji5)
lateinit var emoji5View: ViewGroup
@BindView(R.id.emoji6)
lateinit var emoji6View: ViewGroup
override fun getLayoutResId() = R.layout.fragment_sas_verification_display_code
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = activityViewModelProvider.get(SasVerificationViewModel::class.java)
viewModel.transaction?.let {
if (it.supportsEmoji()) {
val emojicodes = it.getEmojiCodeRepresentation()
emojicodes.forEachIndexed { index, emojiRepresentation ->
when (index) {
0 -> {
emoji0View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji0View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
1 -> {
emoji1View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji1View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
2 -> {
emoji2View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji2View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
3 -> {
emoji3View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji3View.findViewById<TextView>(R.id.item_emoji_name_tv)?.setText(emojiRepresentation.nameResId)
}
4 -> {
emoji4View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji4View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
5 -> {
emoji5View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji5View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
6 -> {
emoji6View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji6View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
}
}
}
// decimal is at least supported
decimalTextView.text = it.getDecimalCodeRepresentation()
if (it.supportsEmoji()) {
descriptionTextView.text = getString(R.string.sas_emoji_description)
decimalTextView.isVisible = false
emojiGrid.isVisible = true
} else {
descriptionTextView.text = getString(R.string.sas_decimal_description)
decimalTextView.isVisible = true
emojiGrid.isInvisible = true
}
}
viewModel.transactionState.observe(viewLifecycleOwner, Observer {
if (viewModel.transaction is IncomingSasVerificationTransaction) {
val uxState = (viewModel.transaction as IncomingSasVerificationTransaction).uxState
when (uxState) {
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
viewModel.loadingLiveEvent.value = null
}
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
viewModel.loadingLiveEvent.value = null
viewModel.deviceIsVerified()
}
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME,
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER -> {
viewModel.loadingLiveEvent.value = null
viewModel.navigateCancel()
}
else -> {
viewModel.loadingLiveEvent.value = R.string.sas_waiting_for_partner
}
}
} else if (viewModel.transaction is OutgoingSasVerificationRequest) {
val uxState = (viewModel.transaction as OutgoingSasVerificationRequest).uxState
when (uxState) {
OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
viewModel.loadingLiveEvent.value = null
}
OutgoingSasVerificationRequest.UxState.VERIFIED -> {
viewModel.loadingLiveEvent.value = null
viewModel.deviceIsVerified()
}
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME,
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER -> {
viewModel.loadingLiveEvent.value = null
viewModel.navigateCancel()
}
else -> {
viewModel.loadingLiveEvent.value = R.string.sas_waiting_for_partner
}
}
}
})
}
@OnClick(R.id.sas_request_continue_button)
fun didAccept() {
viewModel.confirmEmojiSame()
}
@OnClick(R.id.sas_request_cancel_button)
fun didCancel() {
viewModel.cancelTransaction()
}
}

View file

@ -1,120 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.crypto.verification
import android.os.Bundle
import android.view.ViewGroup
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.transition.TransitionManager
import butterknife.BindView
import butterknife.OnClick
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.platform.VectorBaseFragment
import javax.inject.Inject
// TODO Deprecated("replaced by bottomsheet UX")
class SASVerificationStartFragment @Inject constructor(): VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_sas_verification_start
private lateinit var viewModel: SasVerificationViewModel
@BindView(R.id.rootLayout)
lateinit var rootLayout: ViewGroup
@BindView(R.id.sas_start_button)
lateinit var startButton: Button
@BindView(R.id.sas_start_button_loading)
lateinit var startButtonLoading: ProgressBar
@BindView(R.id.sas_verifying_keys)
lateinit var loadingText: TextView
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = activityViewModelProvider.get(SasVerificationViewModel::class.java)
viewModel.transactionState.observe(viewLifecycleOwner, Observer {
val uxState = (viewModel.transaction as? OutgoingSasVerificationRequest)?.uxState
when (uxState) {
OutgoingSasVerificationRequest.UxState.WAIT_FOR_KEY_AGREEMENT -> {
// display loading
TransitionManager.beginDelayedTransition(this.rootLayout)
this.loadingText.isVisible = true
this.startButton.isInvisible = true
this.startButtonLoading.isVisible = true
this.startButtonLoading.animate()
}
OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
viewModel.shortCodeReady()
}
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME,
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER -> {
viewModel.navigateCancel()
}
else -> {
TransitionManager.beginDelayedTransition(this.rootLayout)
this.loadingText.isVisible = false
this.startButton.isVisible = true
this.startButtonLoading.isVisible = false
}
}
})
}
@OnClick(R.id.sas_start_button)
fun doStart() {
viewModel.beginSasKeyVerification()
}
@OnClick(R.id.sas_legacy_verification)
fun doLegacy() {
(requireActivity() as VectorBaseActivity).notImplemented()
/*
viewModel.session.crypto?.getDeviceInfo(viewModel.otherUserMxItem ?: "", viewModel.otherDeviceId
?: "", object : SimpleApiCallback<MXDeviceInfo>() {
override fun onSuccess(info: MXDeviceInfo?) {
info?.let {
CommonActivityUtils.displayDeviceVerificationDialogLegacy(it, it.userId, viewModel.session, activity, object : YesNoListener {
override fun yes() {
viewModel.manuallyVerified()
}
override fun no() {
}
})
}
}
})
*/
}
@OnClick(R.id.sas_cancel_button)
fun doCancel() {
// Transaction may be started, or not
viewModel.cancelTransaction()
}
}

View file

@ -1,41 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.crypto.verification
import android.os.Bundle
import butterknife.OnClick
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
import javax.inject.Inject
// TODO Deprecated("replaced by bottomsheet UX")
class SASVerificationVerifiedFragment @Inject constructor() : VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_sas_verification_verified
private lateinit var viewModel: SasVerificationViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = activityViewModelProvider.get(SasVerificationViewModel::class.java)
}
@OnClick(R.id.sas_verification_verified_done_button)
fun onDone() {
viewModel.finishSuccess()
}
}

View file

@ -1,153 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.crypto.verification
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotx.core.utils.LiveEvent
import javax.inject.Inject
// TODO Deprecated("replaced by bottomsheet UX")
class SasVerificationViewModel @Inject constructor() : ViewModel(),
SasVerificationService.SasVerificationListener {
companion object {
const val NAVIGATE_FINISH = "NAVIGATE_FINISH"
const val NAVIGATE_FINISH_SUCCESS = "NAVIGATE_FINISH_SUCCESS"
const val NAVIGATE_SAS_DISPLAY = "NAVIGATE_SAS_DISPLAY"
const val NAVIGATE_SUCCESS = "NAVIGATE_SUCCESS"
const val NAVIGATE_CANCELLED = "NAVIGATE_CANCELLED"
}
private lateinit var sasVerificationService: SasVerificationService
var otherUserId: String? = null
var otherDeviceId: String? = null
var otherUser: User? = null
var transaction: SasVerificationTransaction? = null
var transactionState: MutableLiveData<SasVerificationTxState> = MutableLiveData()
init {
// Force a first observe
transactionState.value = null
}
private var _navigateEvent: MutableLiveData<LiveEvent<String>> = MutableLiveData()
val navigateEvent: LiveData<LiveEvent<String>>
get() = _navigateEvent
var loadingLiveEvent: MutableLiveData<Int> = MutableLiveData()
var transactionID: String? = null
set(value) {
if (value != null) {
transaction = sasVerificationService.getExistingTransaction(otherUserId!!, value)
transactionState.value = transaction?.state
otherDeviceId = transaction?.otherDeviceId
}
field = value
}
fun initIncoming(session: Session, otherUserId: String, transactionID: String?) {
this.sasVerificationService = session.getSasVerificationService()
this.otherUserId = otherUserId
this.transactionID = transactionID
this.sasVerificationService.addListener(this)
this.otherUser = session.getUser(otherUserId)
if (transactionID == null || transaction == null) {
// sanity, this transaction is not known anymore
_navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
}
}
fun initOutgoing(session: Session, otherUserId: String, otherDeviceId: String) {
this.sasVerificationService = session.getSasVerificationService()
this.otherUserId = otherUserId
this.otherDeviceId = otherDeviceId
this.sasVerificationService.addListener(this)
this.otherUser = session.getUser(otherUserId)
}
fun beginSasKeyVerification() {
val verificationSAS = sasVerificationService.beginKeyVerificationSAS(otherUserId!!, otherDeviceId!!)
this.transactionID = verificationSAS
}
override fun transactionCreated(tx: SasVerificationTransaction) {
}
override fun transactionUpdated(tx: SasVerificationTransaction) {
if (transactionID == tx.transactionId) {
transactionState.value = tx.state
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {
}
fun cancelTransaction() {
transaction?.cancel()
_navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
}
fun finishSuccess() {
_navigateEvent.value = LiveEvent(NAVIGATE_FINISH_SUCCESS)
}
fun manuallyVerified() {
if (otherUserId != null && otherDeviceId != null) {
sasVerificationService.markedLocallyAsManuallyVerified(otherUserId!!, otherDeviceId!!)
}
_navigateEvent.value = LiveEvent(NAVIGATE_FINISH_SUCCESS)
}
fun acceptTransaction() {
(transaction as? IncomingSasVerificationTransaction)?.performAccept()
}
fun confirmEmojiSame() {
transaction?.userHasVerifiedShortCode()
}
fun shortCodeReady() {
loadingLiveEvent.value = null
_navigateEvent.value = LiveEvent(NAVIGATE_SAS_DISPLAY)
}
fun deviceIsVerified() {
loadingLiveEvent.value = null
_navigateEvent.value = LiveEvent(NAVIGATE_SUCCESS)
}
fun navigateCancel() {
_navigateEvent.value = LiveEvent(NAVIGATE_CANCELLED)
}
override fun onCleared() {
super.onCleared()
if (::sasVerificationService.isInitialized) {
sasVerificationService.removeListener(this)
}
}
}

View file

@ -67,6 +67,6 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction() data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction()
data class DeclineVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction() data class DeclineVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction()
data class RequestVerification(val userId: String) : RoomDetailAction() data class RequestVerification(val userId: String) : RoomDetailAction()
data class ResumeVerification(val transactionId: String, val otherUserId: String? = null, val otherdDeviceId: String? = null) : RoomDetailAction()
} }

View file

@ -109,7 +109,7 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
companion object { companion object {
private const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS" const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS"
fun newIntent(context: Context, roomDetailArgs: RoomDetailArgs): Intent { fun newIntent(context: Context, roomDetailArgs: RoomDetailArgs): Intent {
return Intent(context, RoomDetailActivity::class.java).apply { return Intent(context, RoomDetailActivity::class.java).apply {

View file

@ -849,6 +849,15 @@ class RoomDetailFragment @Inject constructor(
data.transactionId data.transactionId
).show(parentFragmentManager, "REQ") ).show(parentFragmentManager, "REQ")
} }
is RoomDetailAction.ResumeVerification -> {
val otherUserId = data.otherUserId ?: return
VerificationBottomSheet().apply {
arguments = Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs(
otherUserId, data.transactionId, roomId = roomDetailArgs.roomId))
}
}.show(parentFragmentManager, "REQ")
}
} }
} }
} }
@ -998,6 +1007,9 @@ class RoomDetailFragment @Inject constructor(
} }
override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) { override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) {
if (messageContent is MessageVerificationRequestContent) {
roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId))
}
} }
override fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean { override fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean {

View file

@ -187,6 +187,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
is RoomDetailAction.RequestVerification -> handleRequestVerification(action) is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
} }
} }
@ -824,6 +825,18 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
_requestLiveData.postValue(LiveEvent(Success(action))) _requestLiveData.postValue(LiveEvent(Success(action)))
} }
private fun handleResumeRequestVerification(action: RoomDetailAction.ResumeVerification) {
// Check if this request is still active and handled by me
session.getSasVerificationService().getExistingVerificationRequestInRoom(room.roomId, action.transactionId)?.let {
if (it.handledByOtherSession) return
if (!it.isFinished) {
_requestLiveData.postValue(LiveEvent(Success(action.copy(
otherUserId = it.otherUserId
))))
}
}
}
private fun observeSyncState() { private fun observeSyncState() {
session.rx() session.rx()
.liveSyncState() .liveSyncState()

View file

@ -20,12 +20,12 @@ import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.view.View import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import com.tapadoo.alerter.Alerter import com.tapadoo.alerter.Alerter
import com.tapadoo.alerter.OnHideAlertListener import com.tapadoo.alerter.OnHideAlertListener
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.features.crypto.verification.SASVerificationActivity
import timber.log.Timber import timber.log.Timber
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@ -78,8 +78,7 @@ object PopupAlertManager {
setLightStatusBar() setLightStatusBar()
} }
} }
if (currentAlerter?.shouldBeDisplayedIn?.invoke(activity) == false) {
if (shouldIgnoreActivity(activity)) {
return return
} }
@ -108,8 +107,6 @@ object PopupAlertManager {
} }
} }
private fun shouldIgnoreActivity(activity: Activity) = activity is SASVerificationActivity
private fun displayNextIfPossible() { private fun displayNextIfPossible() {
val currentActivity = weakCurrentActivity?.get() val currentActivity = weakCurrentActivity?.get()
if (Alerter.isShowing || currentActivity == null) { if (Alerter.isShowing || currentActivity == null) {
@ -209,7 +206,13 @@ object PopupAlertManager {
}) })
.enableSwipeToDismiss() .enableSwipeToDismiss()
.enableInfiniteDuration(true) .enableInfiniteDuration(true)
.setBackgroundColorRes(alert.colorRes ?: R.color.notification_accent_color) .apply {
if (alert.colorInt != null) {
setBackgroundColorInt(alert.colorInt!!)
} else {
setBackgroundColorRes(alert.colorRes ?: R.color.notification_accent_color)
}
}
.show() .show()
} }
@ -229,7 +232,8 @@ object PopupAlertManager {
data class VectorAlert(val uid: String, data class VectorAlert(val uid: String,
val title: String, val title: String,
val description: String, val description: String,
@DrawableRes val iconId: Int?) { @DrawableRes val iconId: Int?,
val shouldBeDisplayedIn: ((Activity) -> Boolean)? = null) {
data class Button(val title: String, val action: Runnable, val autoClose: Boolean) data class Button(val title: String, val action: Runnable, val autoClose: Boolean)
@ -250,5 +254,8 @@ object PopupAlertManager {
@ColorRes @ColorRes
var colorRes: Int? = null var colorRes: Int? = null
@ColorInt
var colorInt: Int? = null
} }
} }

View file

@ -1,132 +0,0 @@
<?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"
android:background="?android:colorBackground">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/sas_emoji_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_horizontal_margin"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:gravity="center"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/sas_emoji_description" />
<TextView
android:id="@+id/sas_emoji_description_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:gravity="center"
android:text="@string/sas_security_advise"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_emoji_description" />
<TextView
android:id="@+id/sas_decimal_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="?riotx_text_primary"
android:textSize="28sp"
android:textStyle="bold"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/sas_emoji_grid"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/sas_emoji_grid"
tools:text="1234-4320-3905"
tools:visibility="visible" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/sas_emoji_grid"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:visibility="invisible"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sas_emoji_description_2"
tools:visibility="visible">
<include
android:id="@+id/emoji0"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji1"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji2"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji3"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji4"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji5"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji6"
layout="@layout/item_emoji_verif" />
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/sas_emoji_grid_flow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:constraint_referenced_ids="emoji0,emoji1,emoji2,emoji3,emoji4,emoji5,emoji6"
app:flow_horizontalBias="0.5"
app:flow_horizontalGap="16dp"
app:flow_horizontalStyle="packed"
app:flow_verticalBias="0"
app:flow_verticalGap="8dp"
app:flow_wrapMode="chain"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_request_continue_button"
style="@style/VectorButtonStyle"
android:layout_margin="@dimen/layout_vertical_margin"
android:minWidth="160dp"
android:text="@string/_continue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sas_emoji_grid" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_request_cancel_button"
style="@style/VectorButtonStyleText"
android:layout_margin="@dimen/layout_vertical_margin"
android:text="@string/cancel"
app:layout_constraintEnd_toStartOf="@+id/sas_request_continue_button"
app:layout_constraintTop_toBottomOf="@+id/sas_emoji_grid" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

@ -1,132 +0,0 @@
<?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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/sas_incoming_request_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:text="@string/sas_incoming_request_title"
android:textAlignment="center"
android:textColor="?riotx_text_primary"
android:textSize="17sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/sas_incoming_request_user_avatar"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_marginTop="@dimen/layout_vertical_margin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sas_incoming_request_title"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/sas_incoming_request_user_display_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAlignment="center"
android:textColor="?riotx_text_primary"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_user_avatar"
tools:text="User name" />
<TextView
android:id="@+id/sas_incoming_request_user_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_user_display_name"
tools:text="\@foo:matrix.org" />
<TextView
android:id="@+id/sas_incoming_request_user_device"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_user_id"
tools:text="Device: Mobile" />
<TextView
android:id="@+id/sas_incoming_request_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:text="@string/sas_incoming_request_description"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_user_device" />
<TextView
android:id="@+id/sas_incoming_request_description_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:text="@string/sas_incoming_request_description_2"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_description" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_request_continue_button"
style="@style/VectorButtonStyle"
android:layout_marginTop="@dimen/layout_vertical_margin_big"
android:layout_marginEnd="@dimen/layout_vertical_margin"
android:layout_marginRight="@dimen/layout_vertical_margin"
android:layout_marginBottom="@dimen/layout_vertical_margin"
android:minWidth="160dp"
android:text="@string/_continue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sas_incoming_request_description_2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_request_cancel_button"
style="@style/VectorButtonStyleText"
android:layout_gravity="end"
android:layout_marginEnd="@dimen/layout_vertical_margin"
android:layout_marginRight="@dimen/layout_vertical_margin"
android:text="@string/cancel"
app:layout_constraintEnd_toStartOf="@+id/sas_request_continue_button"
app:layout_constraintTop_toTopOf="@+id/sas_request_continue_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

@ -1,89 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/sas_verification_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/sas_verify_title"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/sas_verification_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/sas_security_advise"
android:textColor="?riotx_text_secondary" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_cancel_button"
style="@style/VectorButtonStyleText"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="@string/cancel" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginRight="@dimen/layout_horizontal_margin">
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_start_button"
style="@style/VectorButtonStyle"
android:minWidth="160dp"
android:text="@string/sas_verify_start_button_title" />
<ProgressBar
android:id="@+id/sas_start_button_loading"
android:layout_width="19dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_legacy_verification"
style="@style/VectorButtonStyleText"
android:layout_gravity="end"
android:layout_margin="@dimen/layout_horizontal_margin"
android:text="@string/sas_legacy_verification_button_title"
android:visibility="visible" />
<TextView
android:id="@+id/sas_verifying_keys"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/sas_verifying_keys"
android:textColor="?riotx_text_secondary"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
</ScrollView>

View file

@ -1,69 +0,0 @@
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:colorBackground">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/sas_verification_verified_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="16dp"
android:text="@string/sas_verified"
android:textColor="?riotx_text_primary"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/sas_verification_verified_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:gravity="center"
android:text="@string/sas_verified_successful"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_verification_verified_title" />
<TextView
android:id="@+id/sas_verification_verified_description_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:gravity="center"
android:text="@string/sas_verified_successful_description"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_verification_verified_description" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_verification_verified_done_button"
style="@style/VectorButtonStyle"
android:layout_marginTop="@dimen/layout_vertical_margin_big"
android:layout_marginEnd="@dimen/layout_vertical_margin"
android:layout_marginRight="@dimen/layout_vertical_margin"
android:layout_marginBottom="@dimen/layout_vertical_margin"
android:minWidth="160dp"
android:text="@string/sas_got_it"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sas_verification_verified_description_2" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>