mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-26 19:36:08 +03:00
commit
e47791f290
48 changed files with 996 additions and 145 deletions
|
@ -1161,7 +1161,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||||
assertFalse(keysBackup2.isEnabled)
|
assertFalse(keysBackup2.isEnabled)
|
||||||
|
|
||||||
// - Validate the old device from the new one
|
// - Validate the old device from the new one
|
||||||
aliceSession2.setDeviceVerification(DeviceTrustLevel(false, true), oldDeviceId, aliceSession2.myUserId)
|
aliceSession2.setDeviceVerification(DeviceTrustLevel(false, true), aliceSession2.myUserId, oldDeviceId)
|
||||||
|
|
||||||
// -> Backup should automatically enable on the new device
|
// -> Backup should automatically enable on the new device
|
||||||
val latch4 = CountDownLatch(1)
|
val latch4 = CountDownLatch(1)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto.verification.qrcode
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.amshove.kluent.shouldNotBeEqualTo
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class SharedSecretTest : InstrumentedTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSharedSecretLengthCase() {
|
||||||
|
repeat(100) {
|
||||||
|
generateSharedSecret().length shouldBe 43
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSharedDiffCase() {
|
||||||
|
val sharedSecret1 = generateSharedSecret()
|
||||||
|
val sharedSecret2 = generateSharedSecret()
|
||||||
|
|
||||||
|
sharedSecret1 shouldNotBeEqualTo sharedSecret2
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.api.permalinks
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Useful methods to create Matrix permalink.
|
* Useful methods to create Matrix permalink (matrix.to links).
|
||||||
*/
|
*/
|
||||||
object PermalinkFactory {
|
object PermalinkFactory {
|
||||||
|
|
||||||
|
@ -84,7 +84,17 @@ object PermalinkFactory {
|
||||||
* @param id the id to escape
|
* @param id the id to escape
|
||||||
* @return the escaped id
|
* @return the escaped id
|
||||||
*/
|
*/
|
||||||
private fun escape(id: String): String {
|
internal fun escape(id: String): String {
|
||||||
return id.replace("/", "%2F")
|
return id.replace("/", "%2F")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unescape '/' in id
|
||||||
|
*
|
||||||
|
* @param id the id to escape
|
||||||
|
* @return the escaped id
|
||||||
|
*/
|
||||||
|
internal fun unescape(id: String): String {
|
||||||
|
return id.replace("%2F", "/")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ interface CryptoService {
|
||||||
|
|
||||||
fun setWarnOnUnknownDevices(warn: Boolean)
|
fun setWarnOnUnknownDevices(warn: Boolean)
|
||||||
|
|
||||||
fun setDeviceVerification(trustLevel: DeviceTrustLevel, deviceId: String, userId: String)
|
fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String)
|
||||||
|
|
||||||
fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo>
|
fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo>
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.crypto.verification.PendingVerification
|
||||||
* SAS verification is intended to be a highly interactive process for users,
|
* SAS verification is intended to be a highly interactive process for users,
|
||||||
* and as such exposes verification methods which are easier for users to use.
|
* and as such exposes verification methods which are easier for users to use.
|
||||||
*/
|
*/
|
||||||
|
// TODO Rename to VerificationService and reorganize packages?
|
||||||
interface SasVerificationService {
|
interface SasVerificationService {
|
||||||
|
|
||||||
fun addListener(listener: SasVerificationListener)
|
fun addListener(listener: SasVerificationListener)
|
||||||
|
@ -69,6 +70,7 @@ interface SasVerificationService {
|
||||||
|
|
||||||
// fun transactionUpdated(tx: SasVerificationTransaction)
|
// fun transactionUpdated(tx: SasVerificationTransaction)
|
||||||
|
|
||||||
|
// TODO Rename to VerificationListener
|
||||||
interface SasVerificationListener {
|
interface SasVerificationListener {
|
||||||
fun transactionCreated(tx: SasVerificationTransaction)
|
fun transactionCreated(tx: SasVerificationTransaction)
|
||||||
fun transactionUpdated(tx: SasVerificationTransaction)
|
fun transactionUpdated(tx: SasVerificationTransaction)
|
||||||
|
|
|
@ -51,4 +51,7 @@ interface SasVerificationTransaction {
|
||||||
fun shortCodeDoesNotMatch()
|
fun shortCodeDoesNotMatch()
|
||||||
|
|
||||||
fun isToDeviceTransport(): Boolean
|
fun isToDeviceTransport(): Boolean
|
||||||
|
|
||||||
|
// TODO Not sure this is the right place to add this, because it is not Sas
|
||||||
|
fun userHasScannedRemoteQrCode(scannedData: String)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,13 @@
|
||||||
package im.vector.matrix.android.api.session.crypto.sas
|
package im.vector.matrix.android.api.session.crypto.sas
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verification methods supported (or to be supported) by the matrix SDK
|
* Verification methods
|
||||||
*/
|
*/
|
||||||
enum class VerificationMethod {
|
enum class VerificationMethod {
|
||||||
|
// Use it when your application supports the SAS verification method
|
||||||
SAS,
|
SAS,
|
||||||
// Not supported yet
|
// Use it if your application is able to display QR codes
|
||||||
SCAN
|
QR_CODE_SHOW,
|
||||||
|
// Use it if your application is able to scan QR codes
|
||||||
|
QR_CODE_SCAN
|
||||||
}
|
}
|
||||||
|
|
|
@ -438,12 +438,12 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Update the blocked/verified state of the given device.
|
* Update the blocked/verified state of the given device.
|
||||||
*
|
*
|
||||||
* @param verificationStatus the new verification status
|
* @param trustLevel the new trust level
|
||||||
* @param deviceId the unique identifier for the device.
|
|
||||||
* @param userId the owner of the device
|
* @param userId the owner of the device
|
||||||
|
* @param deviceId the unique identifier for the device.
|
||||||
*/
|
*/
|
||||||
override fun setDeviceVerification(trustLevel: DeviceTrustLevel, deviceId: String, userId: String) {
|
override fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
|
||||||
setDeviceVerificationAction.handle(trustLevel, deviceId, userId)
|
setDeviceVerificationAction.handle(trustLevel, userId, deviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -28,7 +28,7 @@ internal class SetDeviceVerificationAction @Inject constructor(
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
private val keysBackup: KeysBackup) {
|
private val keysBackup: KeysBackup) {
|
||||||
|
|
||||||
fun handle(trustLevel: DeviceTrustLevel, deviceId: String, userId: String) {
|
fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
|
||||||
val device = cryptoStore.getUserDevice(userId, deviceId)
|
val device = cryptoStore.getUserDevice(userId, deviceId)
|
||||||
|
|
||||||
// Sanity check
|
// Sanity check
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.crosssigning
|
package im.vector.matrix.android.internal.crypto.crosssigning
|
||||||
|
|
||||||
import android.util.Base64
|
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
@ -83,11 +82,12 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
Timber.i("## CrossSigning - Found Existing self signed keys")
|
Timber.i("## CrossSigning - Found Existing self signed keys")
|
||||||
Timber.i("## CrossSigning - Checking if private keys are known")
|
Timber.i("## CrossSigning - Checking if private keys are known")
|
||||||
|
|
||||||
cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeyinfo ->
|
cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeysInfo ->
|
||||||
privateKeyinfo.master?.let { privateKey ->
|
privateKeysInfo.master
|
||||||
val keySeed = Base64.decode(privateKey, Base64.NO_PADDING)
|
?.fromBase64NoPadding()
|
||||||
|
?.let { privateKeySeed ->
|
||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
if (pkSigning.initWithSeed(keySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
||||||
masterPkSigning = pkSigning
|
masterPkSigning = pkSigning
|
||||||
Timber.i("## CrossSigning - Loading master key success")
|
Timber.i("## CrossSigning - Loading master key success")
|
||||||
} else {
|
} else {
|
||||||
|
@ -95,10 +95,11 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
// TODO untrust
|
// TODO untrust
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
privateKeyinfo.user?.let { privateKey ->
|
privateKeysInfo.user
|
||||||
val keySeed = Base64.decode(privateKey, Base64.NO_PADDING)
|
?.fromBase64NoPadding()
|
||||||
|
?.let { privateKeySeed ->
|
||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
if (pkSigning.initWithSeed(keySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
||||||
userPkSigning = pkSigning
|
userPkSigning = pkSigning
|
||||||
Timber.i("## CrossSigning - Loading User Signing key success")
|
Timber.i("## CrossSigning - Loading User Signing key success")
|
||||||
} else {
|
} else {
|
||||||
|
@ -106,10 +107,11 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
// TODO untrust
|
// TODO untrust
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
privateKeyinfo.selfSigned?.let { privateKey ->
|
privateKeysInfo.selfSigned
|
||||||
val keySeed = Base64.decode(privateKey, Base64.NO_PADDING)
|
?.fromBase64NoPadding()
|
||||||
|
?.let { privateKeySeed ->
|
||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
if (pkSigning.initWithSeed(keySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
||||||
selfSigningPkSigning = pkSigning
|
selfSigningPkSigning = pkSigning
|
||||||
Timber.i("## CrossSigning - Loading Self Signing key success")
|
Timber.i("## CrossSigning - Loading Self Signing key success")
|
||||||
} else {
|
} else {
|
||||||
|
@ -369,7 +371,9 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
|
|
||||||
// Is the master key trusted
|
// Is the master key trusted
|
||||||
// 1) check if I know the private key
|
// 1) check if I know the private key
|
||||||
val masterPrivateKey = cryptoStore.getCrossSigningPrivateKeys()?.master
|
val masterPrivateKey = cryptoStore.getCrossSigningPrivateKeys()
|
||||||
|
?.master
|
||||||
|
?.fromBase64NoPadding()
|
||||||
|
|
||||||
var isMaterKeyTrusted = false
|
var isMaterKeyTrusted = false
|
||||||
if (masterPrivateKey != null) {
|
if (masterPrivateKey != null) {
|
||||||
|
@ -377,11 +381,12 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
var olmPkSigning: OlmPkSigning? = null
|
var olmPkSigning: OlmPkSigning? = null
|
||||||
try {
|
try {
|
||||||
olmPkSigning = OlmPkSigning()
|
olmPkSigning = OlmPkSigning()
|
||||||
val expectedPK = olmPkSigning.initWithSeed(Base64.decode(masterPrivateKey, Base64.NO_PADDING))
|
val expectedPK = olmPkSigning.initWithSeed(masterPrivateKey)
|
||||||
isMaterKeyTrusted = myMasterKey.unpaddedBase64PublicKey == expectedPK
|
isMaterKeyTrusted = myMasterKey.unpaddedBase64PublicKey == expectedPK
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
olmPkSigning?.releaseSigning()
|
Timber.e(failure)
|
||||||
}
|
}
|
||||||
|
olmPkSigning?.releaseSigning()
|
||||||
} else {
|
} else {
|
||||||
// Maybe it's signed by a locally trusted device?
|
// Maybe it's signed by a locally trusted device?
|
||||||
myMasterKey.signatures?.get(myUserId)?.forEach { (key, value) ->
|
myMasterKey.signatures?.get(myUserId)?.forEach { (key, value) ->
|
||||||
|
|
|
@ -28,6 +28,10 @@ fun CryptoCrossSigningKey.canonicalSignable(): String {
|
||||||
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ByteArray.toBase64NoPadding() : String? {
|
fun ByteArray.toBase64NoPadding(): String {
|
||||||
return Base64.encodeToString(this, Base64.NO_PADDING)
|
return Base64.encodeToString(this, Base64.NO_PADDING or Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.fromBase64NoPadding(): ByteArray {
|
||||||
|
return Base64.decode(this, Base64.NO_PADDING or Base64.NO_WRAP)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,25 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
||||||
|
|
||||||
internal const val VERIFICATION_METHOD_SAS = "m.sas.v1"
|
internal const val VERIFICATION_METHOD_SAS = "m.sas.v1"
|
||||||
internal const val VERIFICATION_METHOD_SCAN = "m.qr_code.scan.v1"
|
|
||||||
|
// Qr code
|
||||||
|
// Ref: https://github.com/uhoreg/matrix-doc/blob/qr_key_verification/proposals/1543-qr_code_key_verification.md#verification-methods
|
||||||
|
internal const val VERIFICATION_METHOD_QR_CODE_SHOW = "m.qr_code.show.v1"
|
||||||
|
internal const val VERIFICATION_METHOD_QR_CODE_SCAN = "m.qr_code.scan.v1"
|
||||||
|
internal const val VERIFICATION_METHOD_RECIPROCATE = "m.reciprocate.v1"
|
||||||
|
|
||||||
internal fun VerificationMethod.toValue(): String {
|
internal fun VerificationMethod.toValue(): String {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
VerificationMethod.SAS -> VERIFICATION_METHOD_SAS
|
VerificationMethod.SAS -> VERIFICATION_METHOD_SAS
|
||||||
VerificationMethod.SCAN -> VERIFICATION_METHOD_SCAN
|
VerificationMethod.QR_CODE_SCAN -> VERIFICATION_METHOD_QR_CODE_SCAN
|
||||||
|
VerificationMethod.QR_CODE_SHOW -> VERIFICATION_METHOD_QR_CODE_SHOW
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Add SCAN
|
internal val supportedVerificationMethods =
|
||||||
internal val supportedVerificationMethods = listOf(VERIFICATION_METHOD_SAS)
|
listOf(
|
||||||
|
VERIFICATION_METHOD_SAS,
|
||||||
|
VERIFICATION_METHOD_QR_CODE_SHOW,
|
||||||
|
VERIFICATION_METHOD_QR_CODE_SCAN,
|
||||||
|
VERIFICATION_METHOD_RECIPROCATE
|
||||||
|
)
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.store
|
package im.vector.matrix.android.internal.crypto.store
|
||||||
|
|
||||||
data class PrivateKeysInfo(
|
data class PrivateKeysInfo(
|
||||||
|
|
|
@ -41,6 +41,7 @@ import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.internal.toImmutableList
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -204,8 +205,8 @@ internal class DefaultSasVerificationService @Inject constructor(
|
||||||
|
|
||||||
override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
|
override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
|
||||||
setDeviceVerificationAction.handle(DeviceTrustLevel(false, true),
|
setDeviceVerificationAction.handle(DeviceTrustLevel(false, true),
|
||||||
deviceID,
|
userId,
|
||||||
userId)
|
deviceID)
|
||||||
|
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
try {
|
try {
|
||||||
|
@ -726,10 +727,11 @@ internal class DefaultSasVerificationService @Inject constructor(
|
||||||
val transport = sasTransportRoomMessageFactory.createTransport(roomId, null)
|
val transport = sasTransportRoomMessageFactory.createTransport(roomId, null)
|
||||||
|
|
||||||
// Cancel existing pending requests?
|
// Cancel existing pending requests?
|
||||||
requestsForUser.forEach { existingRequest ->
|
requestsForUser.toImmutableList().forEach { existingRequest ->
|
||||||
existingRequest.transactionId?.let { tid ->
|
existingRequest.transactionId?.let { tid ->
|
||||||
if (!existingRequest.isFinished) {
|
if (!existingRequest.isFinished) {
|
||||||
Timber.d("## SAS, cancelling pending requests to start a new one")
|
Timber.d("## SAS, cancelling pending requests to start a new one")
|
||||||
|
updatePendingRequest(existingRequest.copy(cancelConclusion = CancelCode.User))
|
||||||
transport.cancelTransaction(tid, existingRequest.otherUserId, "", CancelCode.User)
|
transport.cancelTransaction(tid, existingRequest.otherUserId, "", CancelCode.User)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -819,6 +821,7 @@ internal class DefaultSasVerificationService @Inject constructor(
|
||||||
if (existingRequest != null) {
|
if (existingRequest != null) {
|
||||||
// we need to send a ready event, with matching methods
|
// we need to send a ready event, with matching methods
|
||||||
val transport = sasTransportRoomMessageFactory.createTransport(roomId, null)
|
val transport = sasTransportRoomMessageFactory.createTransport(roomId, null)
|
||||||
|
// TODO We should not use supportedVerificationMethods here, because it depends on the client implementation
|
||||||
val methods = existingRequest.requestInfo?.methods?.intersect(supportedVerificationMethods)?.toList()
|
val methods = existingRequest.requestInfo?.methods?.intersect(supportedVerificationMethods)?.toList()
|
||||||
if (methods.isNullOrEmpty()) {
|
if (methods.isNullOrEmpty()) {
|
||||||
Timber.i("Cannot ready this request, no common methods found txId:$transactionId")
|
Timber.i("Cannot ready this request, no common methods found txId:$transactionId")
|
||||||
|
|
|
@ -18,9 +18,10 @@ package im.vector.matrix.android.internal.crypto.verification
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SCAN
|
import java.util.UUID
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores current pending verification requests
|
* Stores current pending verification requests
|
||||||
|
@ -47,7 +48,8 @@ data class PendingVerificationRequest(
|
||||||
fun hasMethod(method: VerificationMethod): Boolean? {
|
fun hasMethod(method: VerificationMethod): Boolean? {
|
||||||
return when (method) {
|
return when (method) {
|
||||||
VerificationMethod.SAS -> readyInfo?.methods?.contains(VERIFICATION_METHOD_SAS)
|
VerificationMethod.SAS -> readyInfo?.methods?.contains(VERIFICATION_METHOD_SAS)
|
||||||
VerificationMethod.SCAN -> readyInfo?.methods?.contains(VERIFICATION_METHOD_SCAN)
|
VerificationMethod.QR_CODE_SHOW -> readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW)
|
||||||
|
VerificationMethod.QR_CODE_SCAN -> readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,6 +228,10 @@ internal abstract class SASVerificationTransaction(
|
||||||
|
|
||||||
abstract fun onKeyVerificationMac(vKey: VerificationInfoMac)
|
abstract fun onKeyVerificationMac(vKey: VerificationInfoMac)
|
||||||
|
|
||||||
|
override fun userHasScannedRemoteQrCode(scannedData: String) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
protected fun verifyMacs() {
|
protected fun verifyMacs() {
|
||||||
Timber.v("## SAS verifying macs for id:$transactionId")
|
Timber.v("## SAS verifying macs for id:$transactionId")
|
||||||
state = SasVerificationTxState.Verifying
|
state = SasVerificationTxState.Verifying
|
||||||
|
@ -328,17 +332,17 @@ internal abstract class SASVerificationTransaction(
|
||||||
|
|
||||||
// TODO what if the otherDevice is not in this list? and should we
|
// TODO what if the otherDevice is not in this list? and should we
|
||||||
verifiedDevices.forEach {
|
verifiedDevices.forEach {
|
||||||
setDeviceVerified(it, otherUserId)
|
setDeviceVerified(otherUserId, it)
|
||||||
}
|
}
|
||||||
transport.done(transactionId)
|
transport.done(transactionId)
|
||||||
state = SasVerificationTxState.Verified
|
state = SasVerificationTxState.Verified
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setDeviceVerified(deviceId: String, userId: String) {
|
private fun setDeviceVerified(userId: String, deviceId: String) {
|
||||||
// TODO should not override cross sign status
|
// TODO should not override cross sign status
|
||||||
setDeviceVerificationAction.handle(DeviceTrustLevel(false, true),
|
setDeviceVerificationAction.handle(DeviceTrustLevel(false, true),
|
||||||
deviceId,
|
userId,
|
||||||
userId)
|
deviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancel() {
|
override fun cancel() {
|
||||||
|
|
|
@ -37,8 +37,8 @@ internal interface SasTransport {
|
||||||
fun sendVerificationRequest(supportedMethods: List<String>,
|
fun sendVerificationRequest(supportedMethods: List<String>,
|
||||||
localID: String,
|
localID: String,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
roomId: String, callback:
|
roomId: String,
|
||||||
(String?, MessageVerificationRequestContent?) -> Unit)
|
callback: (String?, MessageVerificationRequestContent?) -> Unit)
|
||||||
|
|
||||||
fun cancelTransaction(transactionId: String,
|
fun cancelTransaction(transactionId: String,
|
||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto.verification.qrcode
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixPatterns
|
||||||
|
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an URL to generate a QR code of the form:
|
||||||
|
* <pre>
|
||||||
|
* https://matrix.to/#/<user-id>?
|
||||||
|
* request=<event-id>
|
||||||
|
* &action=verify
|
||||||
|
* &key_<keyid>=<key-in-base64>...
|
||||||
|
* &secret=<shared_secret>
|
||||||
|
* &other_user_key=<master-key-in-base64>
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
fun QrCodeData.toUrl(): String {
|
||||||
|
return buildString {
|
||||||
|
append(PermalinkFactory.createPermalink(userId))
|
||||||
|
append("?request=")
|
||||||
|
append(PermalinkFactory.escape(requestEventId))
|
||||||
|
append("&action=")
|
||||||
|
append(PermalinkFactory.escape(action))
|
||||||
|
|
||||||
|
for ((keyId, key) in keys) {
|
||||||
|
append("&key_")
|
||||||
|
append(PermalinkFactory.escape(keyId))
|
||||||
|
append("=")
|
||||||
|
append(PermalinkFactory.escape(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
append("&secret=")
|
||||||
|
append(PermalinkFactory.escape(sharedSecret))
|
||||||
|
append("&other_user_key=")
|
||||||
|
append(PermalinkFactory.escape(otherUserKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.toQrCodeData(): QrCodeData? {
|
||||||
|
if (!startsWith(PermalinkFactory.MATRIX_TO_URL_BASE)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val fragment = substringAfter("#")
|
||||||
|
if (fragment.isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val safeFragment = fragment.substringBefore("?")
|
||||||
|
|
||||||
|
// we are limiting to 2 params
|
||||||
|
val params = safeFragment
|
||||||
|
.split(MatrixPatterns.SEP_REGEX.toRegex())
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
|
||||||
|
if (params.size != 1) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val userId = params.getOrNull(0)
|
||||||
|
?.let { PermalinkFactory.unescape(it) }
|
||||||
|
?.takeIf { MatrixPatterns.isUserId(it) } ?: return null
|
||||||
|
|
||||||
|
val urlParams = fragment.substringAfter("?")
|
||||||
|
.split("&".toRegex())
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
|
||||||
|
val keyValues = urlParams.map {
|
||||||
|
(it.substringBefore("=") to it.substringAfter("=").let { value -> PermalinkFactory.unescape(value) })
|
||||||
|
}.toMap()
|
||||||
|
|
||||||
|
val action = keyValues["action"] ?: return null
|
||||||
|
|
||||||
|
val requestEventId = keyValues["request"]?.takeIf { MatrixPatterns.isEventId(it) } ?: return null
|
||||||
|
val sharedSecret = keyValues["secret"] ?: return null
|
||||||
|
val otherUserKey = keyValues["other_user_key"] ?: return null
|
||||||
|
|
||||||
|
val keys = keyValues.keys
|
||||||
|
.filter { it.startsWith("key_") }
|
||||||
|
.map {
|
||||||
|
it.substringAfter("key_") to (keyValues[it] ?: return null)
|
||||||
|
}
|
||||||
|
.toMap()
|
||||||
|
|
||||||
|
return QrCodeData(
|
||||||
|
userId,
|
||||||
|
requestEventId,
|
||||||
|
action,
|
||||||
|
keys,
|
||||||
|
sharedSecret,
|
||||||
|
otherUserKey
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto.verification.qrcode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://github.com/uhoreg/matrix-doc/blob/qr_key_verification/proposals/1543-qr_code_key_verification.md#qr-code-format
|
||||||
|
*/
|
||||||
|
data class QrCodeData(
|
||||||
|
val userId: String,
|
||||||
|
// the event ID of the associated verification request event.
|
||||||
|
val requestEventId: String,
|
||||||
|
// The action
|
||||||
|
val action: String,
|
||||||
|
// key_<key_id>: each key that the user wants verified will have an entry of this form, where the value is the key in unpadded base64.
|
||||||
|
// The QR code should contain at least the user's master cross-signing key.
|
||||||
|
val keys: Map<String, String>,
|
||||||
|
// random single-use shared secret in unpadded base64. It must be at least 256-bits long (43 characters when base64-encoded).
|
||||||
|
val sharedSecret: String,
|
||||||
|
// the other user's master cross-signing key, in unpadded base64. In other words, if Alice is displaying the QR code,
|
||||||
|
// this would be the copy of Bob's master cross-signing key that Alice has.
|
||||||
|
val otherUserKey: String
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val ACTION_VERIFY = "verify"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto.verification.qrcode
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
|
import java.security.SecureRandom
|
||||||
|
|
||||||
|
fun generateSharedSecret(): String {
|
||||||
|
val secureRandom = SecureRandom()
|
||||||
|
|
||||||
|
// 256 bits long
|
||||||
|
val secretBytes = ByteArray(32)
|
||||||
|
secureRandom.nextBytes(secretBytes)
|
||||||
|
return secretBytes.toBase64NoPadding()
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto.verification.qrcode
|
||||||
|
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.amshove.kluent.shouldBeNull
|
||||||
|
import org.amshove.kluent.shouldNotBeNull
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
|
||||||
|
@Suppress("SpellCheckingInspection")
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class QrCodeTest {
|
||||||
|
|
||||||
|
private val basicQrCodeData = QrCodeData(
|
||||||
|
userId = "@benoit:matrix.org",
|
||||||
|
requestEventId = "\$azertyazerty",
|
||||||
|
action = QrCodeData.ACTION_VERIFY,
|
||||||
|
keys = mapOf(
|
||||||
|
"1" to "abcdef",
|
||||||
|
"2" to "ghijql"
|
||||||
|
),
|
||||||
|
sharedSecret = "sharedSecret",
|
||||||
|
otherUserKey = "otherUserKey"
|
||||||
|
)
|
||||||
|
|
||||||
|
private val basicUrl = "https://matrix.to/#/@benoit:matrix.org?request=\$azertyazerty&action=verify&key_1=abcdef&key_2=ghijql&secret=sharedSecret&other_user_key=otherUserKey"
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNominalCase() {
|
||||||
|
val url = basicQrCodeData.toUrl()
|
||||||
|
|
||||||
|
url shouldBeEqualTo basicUrl
|
||||||
|
|
||||||
|
val decodedData = url.toQrCodeData()
|
||||||
|
|
||||||
|
decodedData.shouldNotBeNull()
|
||||||
|
|
||||||
|
decodedData.userId shouldBeEqualTo "@benoit:matrix.org"
|
||||||
|
decodedData.requestEventId shouldBeEqualTo "\$azertyazerty"
|
||||||
|
decodedData.keys["1"]?.shouldBeEqualTo("abcdef")
|
||||||
|
decodedData.keys["2"]?.shouldBeEqualTo("ghijql")
|
||||||
|
decodedData.sharedSecret shouldBeEqualTo "sharedSecret"
|
||||||
|
decodedData.otherUserKey shouldBeEqualTo "otherUserKey"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSlashCase() {
|
||||||
|
val url = basicQrCodeData
|
||||||
|
.copy(
|
||||||
|
userId = "@benoit/foo:matrix.org",
|
||||||
|
requestEventId = "\$azertyazerty/bar"
|
||||||
|
)
|
||||||
|
.toUrl()
|
||||||
|
|
||||||
|
url shouldBeEqualTo basicUrl
|
||||||
|
.replace("@benoit", "@benoit%2Ffoo")
|
||||||
|
.replace("azertyazerty", "azertyazerty%2Fbar")
|
||||||
|
|
||||||
|
val decodedData = url.toQrCodeData()
|
||||||
|
|
||||||
|
decodedData.shouldNotBeNull()
|
||||||
|
|
||||||
|
decodedData.userId shouldBeEqualTo "@benoit/foo:matrix.org"
|
||||||
|
decodedData.requestEventId shouldBeEqualTo "\$azertyazerty/bar"
|
||||||
|
decodedData.keys["1"]?.shouldBeEqualTo("abcdef")
|
||||||
|
decodedData.keys["2"]?.shouldBeEqualTo("ghijql")
|
||||||
|
decodedData.sharedSecret shouldBeEqualTo "sharedSecret"
|
||||||
|
decodedData.otherUserKey shouldBeEqualTo "otherUserKey"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMissingActionCase() {
|
||||||
|
basicUrl.replace("&action=verify", "")
|
||||||
|
.toQrCodeData()
|
||||||
|
.shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOtherActionCase() {
|
||||||
|
basicUrl.replace("&action=verify", "&action=confirm")
|
||||||
|
.toQrCodeData()
|
||||||
|
?.action
|
||||||
|
?.shouldBeEqualTo("confirm")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBadRequestEventId() {
|
||||||
|
basicUrl.replace("\$azertyazerty", "@azertyazerty")
|
||||||
|
.toQrCodeData()
|
||||||
|
.shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMissingUserId() {
|
||||||
|
basicUrl.replace("@benoit:matrix.org", "")
|
||||||
|
.toQrCodeData()
|
||||||
|
.shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBadUserId() {
|
||||||
|
basicUrl.replace("@benoit:matrix.org", "@benoit")
|
||||||
|
.toQrCodeData()
|
||||||
|
.shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMissingSecret() {
|
||||||
|
basicUrl.replace("&secret=sharedSecret", "")
|
||||||
|
.toQrCodeData()
|
||||||
|
.shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMissingOtherUserKey() {
|
||||||
|
basicUrl.replace("&other_user_key=otherUserKey", "")
|
||||||
|
.toQrCodeData()
|
||||||
|
.shouldBeNull()
|
||||||
|
}
|
||||||
|
}
|
|
@ -351,6 +351,10 @@ dependencies {
|
||||||
|
|
||||||
implementation "androidx.emoji:emoji-appcompat:1.0.0"
|
implementation "androidx.emoji:emoji-appcompat:1.0.0"
|
||||||
|
|
||||||
|
// QR-code
|
||||||
|
implementation 'com.google.zxing:core:3.4.0'
|
||||||
|
implementation 'me.dm7.barcodescanner:zxing:1.9.13'
|
||||||
|
|
||||||
// TESTS
|
// TESTS
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.riotx.features.debug
|
package im.vector.riotx.features.debug
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -37,7 +38,15 @@ import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||||
|
import im.vector.riotx.core.qrcode.toQrCode
|
||||||
|
import im.vector.riotx.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||||
|
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
|
||||||
|
import im.vector.riotx.core.utils.allGranted
|
||||||
|
import im.vector.riotx.core.utils.checkPermissions
|
||||||
|
import im.vector.riotx.core.utils.toast
|
||||||
import im.vector.riotx.features.debug.sas.DebugSasEmojiActivity
|
import im.vector.riotx.features.debug.sas.DebugSasEmojiActivity
|
||||||
|
import im.vector.riotx.features.qrcode.QrCodeScannerActivity
|
||||||
|
import kotlinx.android.synthetic.debug.activity_debug_menu.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class DebugMenuActivity : VectorBaseActivity() {
|
class DebugMenuActivity : VectorBaseActivity() {
|
||||||
|
@ -51,6 +60,15 @@ class DebugMenuActivity : VectorBaseActivity() {
|
||||||
injector.inject(this)
|
injector.inject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun initUiAndData() {
|
||||||
|
renderQrCode("https://www.example.org")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderQrCode(text: String) {
|
||||||
|
val qrBitmap = text.toQrCode(200, 200)
|
||||||
|
debug_qr_code.setImageBitmap(qrBitmap)
|
||||||
|
}
|
||||||
|
|
||||||
@OnClick(R.id.debug_test_text_view_link)
|
@OnClick(R.id.debug_test_text_view_link)
|
||||||
fun testTextViewLink() {
|
fun testTextViewLink() {
|
||||||
startActivity(Intent(this, TestLinkifyActivity::class.java))
|
startActivity(Intent(this, TestLinkifyActivity::class.java))
|
||||||
|
@ -214,4 +232,37 @@ class DebugMenuActivity : VectorBaseActivity() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.debug_scan_qr_code)
|
||||||
|
fun scanQRCode() {
|
||||||
|
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
|
||||||
|
doScanQRCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
|
||||||
|
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && allGranted(grantResults)) {
|
||||||
|
doScanQRCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doScanQRCode() {
|
||||||
|
QrCodeScannerActivity.startForResult(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
when (requestCode) {
|
||||||
|
QrCodeScannerActivity.QR_CODE_SCANNER_REQUEST_CODE -> {
|
||||||
|
toast("QrCode: " + QrCodeScannerActivity.getResultText(data) + " is QRCode: " + QrCodeScannerActivity.getResultIsQrCode(data))
|
||||||
|
|
||||||
|
// Also update the current QR Code (reverse operation)
|
||||||
|
renderQrCode(QrCodeScannerActivity.getResultText(data) ?: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,19 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Initialize XSigning" />
|
android:text="Initialize XSigning" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/debug_scan_qr_code"
|
||||||
|
style="@style/VectorButtonStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Scan QR-code" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/debug_qr_code"
|
||||||
|
android:layout_width="200dp"
|
||||||
|
android:layout_height="200dp"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".VectorApplication"
|
android:name=".VectorApplication"
|
||||||
|
@ -123,13 +124,16 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".features.roommemberprofile.RoomMemberProfileActivity"
|
<activity
|
||||||
|
android:name=".features.roommemberprofile.RoomMemberProfileActivity"
|
||||||
android:parentActivityName=".features.home.HomeActivity">
|
android:parentActivityName=".features.home.HomeActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".features.home.HomeActivity" />
|
android:value=".features.home.HomeActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".features.qrcode.QrCodeScannerActivity" />
|
||||||
|
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -364,6 +364,16 @@ SOFTWARE.
|
||||||
<br/>
|
<br/>
|
||||||
Copyright 2017 Sergiy Kovalchuk
|
Copyright 2017 Sergiy Kovalchuk
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>ZXing</b>
|
||||||
|
<br/>
|
||||||
|
Copyright 2007 ZXing authors
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>barcodescanner</b>
|
||||||
|
<br/>
|
||||||
|
Copyright (c) 2014 Dushyanth Maguluru
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<pre>
|
<pre>
|
||||||
Apache License
|
Apache License
|
||||||
|
|
|
@ -22,32 +22,50 @@ import androidx.fragment.app.FragmentFactory
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.multibindings.IntoMap
|
import dagger.multibindings.IntoMap
|
||||||
|
import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment
|
||||||
|
import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
|
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
|
||||||
import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment
|
import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment
|
||||||
import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment
|
import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment
|
||||||
import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment
|
import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment
|
||||||
import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment
|
import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment
|
||||||
|
import im.vector.riotx.features.grouplist.GroupListFragment
|
||||||
import im.vector.riotx.features.home.HomeDetailFragment
|
import im.vector.riotx.features.home.HomeDetailFragment
|
||||||
import im.vector.riotx.features.home.HomeDrawerFragment
|
import im.vector.riotx.features.home.HomeDrawerFragment
|
||||||
import im.vector.riotx.features.home.LoadingFragment
|
import im.vector.riotx.features.home.LoadingFragment
|
||||||
import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment
|
|
||||||
import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment
|
|
||||||
import im.vector.riotx.features.grouplist.GroupListFragment
|
|
||||||
import im.vector.riotx.features.home.room.breadcrumbs.BreadcrumbsFragment
|
import im.vector.riotx.features.home.room.breadcrumbs.BreadcrumbsFragment
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
||||||
import im.vector.riotx.features.home.room.list.RoomListFragment
|
import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||||
import im.vector.riotx.features.login.*
|
import im.vector.riotx.features.login.LoginCaptchaFragment
|
||||||
|
import im.vector.riotx.features.login.LoginFragment
|
||||||
|
import im.vector.riotx.features.login.LoginGenericTextInputFormFragment
|
||||||
|
import im.vector.riotx.features.login.LoginResetPasswordFragment
|
||||||
|
import im.vector.riotx.features.login.LoginResetPasswordMailConfirmationFragment
|
||||||
|
import im.vector.riotx.features.login.LoginResetPasswordSuccessFragment
|
||||||
|
import im.vector.riotx.features.login.LoginServerSelectionFragment
|
||||||
|
import im.vector.riotx.features.login.LoginServerUrlFormFragment
|
||||||
|
import im.vector.riotx.features.login.LoginSignUpSignInSelectionFragment
|
||||||
|
import im.vector.riotx.features.login.LoginSplashFragment
|
||||||
|
import im.vector.riotx.features.login.LoginWaitForEmailFragment
|
||||||
|
import im.vector.riotx.features.login.LoginWebFragment
|
||||||
import im.vector.riotx.features.login.terms.LoginTermsFragment
|
import im.vector.riotx.features.login.terms.LoginTermsFragment
|
||||||
import im.vector.riotx.features.roommemberprofile.RoomMemberProfileFragment
|
import im.vector.riotx.features.qrcode.QrCodeScannerFragment
|
||||||
import im.vector.riotx.features.reactions.EmojiChooserFragment
|
import im.vector.riotx.features.reactions.EmojiChooserFragment
|
||||||
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
|
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
|
||||||
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
|
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
|
||||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
|
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
|
||||||
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
|
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
|
||||||
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
|
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
|
||||||
|
import im.vector.riotx.features.roommemberprofile.RoomMemberProfileFragment
|
||||||
import im.vector.riotx.features.roomprofile.RoomProfileFragment
|
import im.vector.riotx.features.roomprofile.RoomProfileFragment
|
||||||
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
|
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
|
||||||
import im.vector.riotx.features.settings.*
|
import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment
|
||||||
|
import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment
|
||||||
|
import im.vector.riotx.features.settings.VectorSettingsLabsFragment
|
||||||
|
import im.vector.riotx.features.settings.VectorSettingsNotificationPreferenceFragment
|
||||||
|
import im.vector.riotx.features.settings.VectorSettingsNotificationsTroubleshootFragment
|
||||||
|
import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment
|
||||||
|
import im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment
|
||||||
import im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment
|
import im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment
|
||||||
import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
|
import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
|
||||||
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
||||||
|
@ -296,4 +314,9 @@ interface FragmentModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(VerificationConclusionFragment::class)
|
@FragmentKey(VerificationConclusionFragment::class)
|
||||||
fun bindVerificationConclusionFragment(fragment: VerificationConclusionFragment): Fragment
|
fun bindVerificationConclusionFragment(fragment: VerificationConclusionFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(QrCodeScannerFragment::class)
|
||||||
|
fun bindQrCodeScannerFragment(fragment: QrCodeScannerFragment): Fragment
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,12 +24,12 @@ import dagger.Component
|
||||||
import im.vector.riotx.core.error.ErrorFormatter
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import im.vector.riotx.core.preference.UserAvatarPreference
|
import im.vector.riotx.core.preference.UserAvatarPreference
|
||||||
import im.vector.riotx.features.MainActivity
|
import im.vector.riotx.features.MainActivity
|
||||||
|
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||||
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
|
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
|
||||||
import im.vector.riotx.features.debug.DebugMenuActivity
|
import im.vector.riotx.features.debug.DebugMenuActivity
|
||||||
import im.vector.riotx.features.home.HomeActivity
|
import im.vector.riotx.features.home.HomeActivity
|
||||||
import im.vector.riotx.features.home.HomeModule
|
import im.vector.riotx.features.home.HomeModule
|
||||||
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
|
|
||||||
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
|
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
|
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
|
||||||
|
@ -44,6 +44,7 @@ import im.vector.riotx.features.media.ImageMediaViewerActivity
|
||||||
import im.vector.riotx.features.media.VideoMediaViewerActivity
|
import im.vector.riotx.features.media.VideoMediaViewerActivity
|
||||||
import im.vector.riotx.features.navigation.Navigator
|
import im.vector.riotx.features.navigation.Navigator
|
||||||
import im.vector.riotx.features.permalink.PermalinkHandlerActivity
|
import im.vector.riotx.features.permalink.PermalinkHandlerActivity
|
||||||
|
import im.vector.riotx.features.qrcode.QrCodeScannerActivity
|
||||||
import im.vector.riotx.features.rageshake.BugReportActivity
|
import im.vector.riotx.features.rageshake.BugReportActivity
|
||||||
import im.vector.riotx.features.rageshake.BugReporter
|
import im.vector.riotx.features.rageshake.BugReporter
|
||||||
import im.vector.riotx.features.rageshake.RageShake
|
import im.vector.riotx.features.rageshake.RageShake
|
||||||
|
@ -141,6 +142,8 @@ interface ScreenComponent {
|
||||||
|
|
||||||
fun inject(permalinkHandlerActivity: PermalinkHandlerActivity)
|
fun inject(permalinkHandlerActivity: PermalinkHandlerActivity)
|
||||||
|
|
||||||
|
fun inject(activity: QrCodeScannerActivity)
|
||||||
|
|
||||||
fun inject(activity: DebugMenuActivity)
|
fun inject(activity: DebugMenuActivity)
|
||||||
|
|
||||||
fun inject(deviceVerificationInfoBottomSheet: DeviceVerificationInfoBottomSheet)
|
fun inject(deviceVerificationInfoBottomSheet: DeviceVerificationInfoBottomSheet)
|
||||||
|
|
50
vector/src/main/java/im/vector/riotx/core/qrcode/QrCode.kt
Normal file
50
vector/src/main/java/im/vector/riotx/core/qrcode/QrCode.kt
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.core.qrcode
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Color
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import com.google.zxing.BarcodeFormat
|
||||||
|
import com.google.zxing.common.BitMatrix
|
||||||
|
import com.google.zxing.qrcode.QRCodeWriter
|
||||||
|
|
||||||
|
fun String.toQrCode(width: Int,
|
||||||
|
height: Int,
|
||||||
|
@ColorInt backgroundColor: Int = Color.WHITE,
|
||||||
|
@ColorInt foregroundColor: Int = Color.BLACK): Bitmap {
|
||||||
|
return QRCodeWriter().encode(
|
||||||
|
this,
|
||||||
|
BarcodeFormat.QR_CODE,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
).toBitmap(backgroundColor, foregroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BitMatrix.toBitmap(@ColorInt backgroundColor: Int = Color.WHITE,
|
||||||
|
@ColorInt foregroundColor: Int = Color.BLACK): Bitmap {
|
||||||
|
val height: Int = height
|
||||||
|
val width: Int = width
|
||||||
|
val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
|
for (x in 0 until width) {
|
||||||
|
for (y in 0 until height) {
|
||||||
|
bmp.setPixel(x, y, if (get(x, y)) foregroundColor else backgroundColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bmp
|
||||||
|
}
|
|
@ -89,7 +89,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do we already have alerts for this user/device
|
// Do we already have alerts for this user/device
|
||||||
val mappingKey = keyForMap(deviceId, userId)
|
val mappingKey = keyForMap(userId, deviceId)
|
||||||
if (alertsToRequests.containsKey(mappingKey)) {
|
if (alertsToRequests.containsKey(mappingKey)) {
|
||||||
// just add the request, there is already an alert for this
|
// just add the request, there is already an alert for this
|
||||||
alertsToRequests[mappingKey]?.add(request)
|
alertsToRequests[mappingKey]?.add(request)
|
||||||
|
@ -110,7 +110,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deviceInfo.isUnknown) {
|
if (deviceInfo.isUnknown) {
|
||||||
session?.setDeviceVerification(DeviceTrustLevel(false, false), deviceId, userId)
|
session?.setDeviceVerification(DeviceTrustLevel(false, false), userId, deviceId)
|
||||||
|
|
||||||
deviceInfo.trustLevel = DeviceTrustLevel(false, false)
|
deviceInfo.trustLevel = DeviceTrustLevel(false, false)
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
val alert = PopupAlertManager.VectorAlert(
|
val alert = PopupAlertManager.VectorAlert(
|
||||||
alertManagerId(deviceId, userId),
|
alertManagerId(userId, deviceId),
|
||||||
context.getString(R.string.key_share_request),
|
context.getString(R.string.key_share_request),
|
||||||
dialogText,
|
dialogText,
|
||||||
R.drawable.key_small
|
R.drawable.key_small
|
||||||
|
@ -189,7 +189,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
||||||
|
|
||||||
alert.colorRes = R.color.key_share_req_accent_color
|
alert.colorRes = R.color.key_share_req_accent_color
|
||||||
|
|
||||||
val mappingKey = keyForMap(deviceId, userId)
|
val mappingKey = keyForMap(userId, deviceId)
|
||||||
alert.dismissedAction = Runnable {
|
alert.dismissedAction = Runnable {
|
||||||
denyAllRequests(mappingKey)
|
denyAllRequests(mappingKey)
|
||||||
}
|
}
|
||||||
|
@ -249,7 +249,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val alertMgrUniqueKey = alertManagerId(deviceId, userId)
|
val alertMgrUniqueKey = alertManagerId(userId, deviceId)
|
||||||
alertsToRequests[alertMgrUniqueKey]?.removeAll {
|
alertsToRequests[alertMgrUniqueKey]?.removeAll {
|
||||||
it.deviceId == request.deviceId
|
it.deviceId == request.deviceId
|
||||||
&& it.userId == request.userId
|
&& it.userId == request.userId
|
||||||
|
@ -257,7 +257,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
||||||
}
|
}
|
||||||
if (alertsToRequests[alertMgrUniqueKey]?.isEmpty() == true) {
|
if (alertsToRequests[alertMgrUniqueKey]?.isEmpty() == true) {
|
||||||
PopupAlertManager.cancelAlert(alertMgrUniqueKey)
|
PopupAlertManager.cancelAlert(alertMgrUniqueKey)
|
||||||
alertsToRequests.remove(keyForMap(deviceId, userId))
|
alertsToRequests.remove(keyForMap(userId, deviceId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,11 +275,11 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
||||||
|
|
||||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {
|
override fun markedAsManuallyVerified(userId: String, deviceId: String) {
|
||||||
// accept related requests
|
// accept related requests
|
||||||
shareAllSessions(keyForMap(deviceId, userId))
|
shareAllSessions(keyForMap(userId, deviceId))
|
||||||
PopupAlertManager.cancelAlert(alertManagerId(deviceId, userId))
|
PopupAlertManager.cancelAlert(alertManagerId(userId, deviceId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun keyForMap(deviceId: String, userId: String) = "$deviceId$userId"
|
private fun keyForMap(userId: String, deviceId: String) = "$deviceId$userId"
|
||||||
|
|
||||||
private fun alertManagerId(deviceId: String, userId: String) = "ikr_$deviceId$userId"
|
private fun alertManagerId(userId: String, deviceId: String) = "ikr_$deviceId$userId"
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,5 +18,12 @@ package im.vector.riotx.features.crypto.verification
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
|
||||||
|
|
||||||
// TODO Add support for SCAN (QR code)
|
val supportedVerificationMethods =
|
||||||
val supportedVerificationMethods = listOf(VerificationMethod.SAS)
|
listOf(
|
||||||
|
// RiotX supports SAS verification
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
// RiotX is able to show QR codes
|
||||||
|
VerificationMethod.QR_CODE_SHOW,
|
||||||
|
// RiotX is able to scan QR codes
|
||||||
|
VerificationMethod.QR_CODE_SCAN
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.crypto.verification
|
||||||
|
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
|
sealed class VerificationAction : VectorViewModelAction {
|
||||||
|
data class RequestVerificationByDM(val userID: String, val roomId: String?) : VerificationAction()
|
||||||
|
data class StartSASVerification(val userID: String, val pendingRequestTransactionId: String) : VerificationAction()
|
||||||
|
data class RemoteQrCodeScanned(val userID: String, val sasTransactionId: String, val scannedData: String) : VerificationAction()
|
||||||
|
data class SASMatchAction(val userID: String, val sasTransactionId: String) : VerificationAction()
|
||||||
|
data class SASDoNotMatchAction(val userID: String, val sasTransactionId: String) : VerificationAction()
|
||||||
|
object GotItConclusion : VerificationAction()
|
||||||
|
}
|
|
@ -131,7 +131,9 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
showFragment(VerificationEmojiCodeFragment::class, Bundle().apply {
|
showFragment(VerificationEmojiCodeFragment::class, Bundle().apply {
|
||||||
putParcelable(MvRx.KEY_ARG, VerificationArgs(
|
putParcelable(MvRx.KEY_ARG, VerificationArgs(
|
||||||
it.otherUserMxItem?.id ?: "",
|
it.otherUserMxItem?.id ?: "",
|
||||||
it.transactionId))
|
// If it was outgoing it.transaction id would be null, but the pending request
|
||||||
|
// would be updated (from localID to txId)
|
||||||
|
it.pendingRequest?.transactionId ?: it.transactionId))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
SasVerificationTxState.Verified,
|
SasVerificationTxState.Verified,
|
||||||
|
|
|
@ -17,17 +17,25 @@ package im.vector.riotx.features.crypto.verification
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.airbnb.mvrx.*
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.*
|
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
|
||||||
|
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.crypto.sas.VerificationMethod
|
||||||
import im.vector.matrix.android.api.util.MatrixItem
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||||
import im.vector.riotx.core.di.HasScreenInjector
|
import im.vector.riotx.core.di.HasScreenInjector
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
|
||||||
import im.vector.riotx.core.utils.LiveEvent
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
|
|
||||||
data class VerificationBottomSheetViewState(
|
data class VerificationBottomSheetViewState(
|
||||||
|
@ -39,14 +47,6 @@ data class VerificationBottomSheetViewState(
|
||||||
val cancelCode: CancelCode? = null
|
val cancelCode: CancelCode? = null
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
||||||
sealed class VerificationAction : VectorViewModelAction {
|
|
||||||
data class RequestVerificationByDM(val userID: String, val roomId: String?) : VerificationAction()
|
|
||||||
data class StartSASVerification(val userID: String, val pendingRequestTransactionId: String) : VerificationAction()
|
|
||||||
data class SASMatchAction(val userID: String, val sasTransactionId: String) : VerificationAction()
|
|
||||||
data class SASDoNotMatchAction(val userID: String, val sasTransactionId: String) : VerificationAction()
|
|
||||||
object GotItConclusion : VerificationAction()
|
|
||||||
}
|
|
||||||
|
|
||||||
class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: VerificationBottomSheetViewState,
|
class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: VerificationBottomSheetViewState,
|
||||||
private val session: Session)
|
private val session: Session)
|
||||||
: VectorViewModel<VerificationBottomSheetViewState, VerificationAction>(initialState),
|
: VectorViewModel<VerificationBottomSheetViewState, VerificationAction>(initialState),
|
||||||
|
@ -122,6 +122,12 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
||||||
callback = null
|
callback = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is VerificationAction.RemoteQrCodeScanned -> {
|
||||||
|
// TODO Use session.getCrossSigningService()?
|
||||||
|
session.getSasVerificationService()
|
||||||
|
.getExistingTransaction(action.userID, action.sasTransactionId)
|
||||||
|
?.userHasScannedRemoteQrCode(action.scannedData)
|
||||||
|
}
|
||||||
is VerificationAction.SASMatchAction -> {
|
is VerificationAction.SASMatchAction -> {
|
||||||
session.getSasVerificationService()
|
session.getSasVerificationService()
|
||||||
.getExistingTransaction(action.userID, action.sasTransactionId)
|
.getExistingTransaction(action.userID, action.sasTransactionId)
|
||||||
|
|
|
@ -19,8 +19,10 @@ package im.vector.riotx.features.crypto.verification.choose
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.dividerItem
|
import im.vector.riotx.core.epoxy.dividerItem
|
||||||
|
import im.vector.riotx.core.qrcode.toQrCode
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.core.utils.DimensionConverter
|
||||||
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
|
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
|
||||||
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
|
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
|
||||||
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
||||||
|
@ -28,7 +30,8 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class VerificationChooseMethodController @Inject constructor(
|
class VerificationChooseMethodController @Inject constructor(
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val colorProvider: ColorProvider
|
private val colorProvider: ColorProvider,
|
||||||
|
private val dimensionConverter: DimensionConverter
|
||||||
) : EpoxyController() {
|
) : EpoxyController() {
|
||||||
|
|
||||||
var listener: Listener? = null
|
var listener: Listener? = null
|
||||||
|
@ -43,22 +46,28 @@ class VerificationChooseMethodController @Inject constructor(
|
||||||
override fun buildModels() {
|
override fun buildModels() {
|
||||||
val state = viewState ?: return
|
val state = viewState ?: return
|
||||||
|
|
||||||
if (state.QRModeAvailable) {
|
if (state.otherCanScanQrCode || state.otherCanShowQrCode) {
|
||||||
bottomSheetVerificationNoticeItem {
|
bottomSheetVerificationNoticeItem {
|
||||||
id("notice")
|
id("notice")
|
||||||
notice(stringProvider.getString(R.string.verification_scan_notice))
|
notice(stringProvider.getString(R.string.verification_scan_notice))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Generate the QR code
|
if (state.otherCanScanQrCode && !state.QRtext.isNullOrBlank()) {
|
||||||
|
// Generate the QR code
|
||||||
|
val size = dimensionConverter.dpToPx(180)
|
||||||
|
val qrCodeBitmap = state.QRtext.toQrCode(size, size)
|
||||||
|
|
||||||
bottomSheetVerificationBigImageItem {
|
bottomSheetVerificationBigImageItem {
|
||||||
id("qr")
|
id("qr")
|
||||||
imageRes(R.drawable.riotx_logo)
|
imageBitmap(qrCodeBitmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
dividerItem {
|
dividerItem {
|
||||||
id("sep0")
|
id("sep0")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.otherCanShowQrCode) {
|
||||||
bottomSheetVerificationActionItem {
|
bottomSheetVerificationActionItem {
|
||||||
id("openCamera")
|
id("openCamera")
|
||||||
title(stringProvider.getString(R.string.verification_scan_their_code))
|
title(stringProvider.getString(R.string.verification_scan_their_code))
|
||||||
|
@ -71,6 +80,7 @@ class VerificationChooseMethodController @Inject constructor(
|
||||||
dividerItem {
|
dividerItem {
|
||||||
id("sep1")
|
id("sep1")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bottomSheetVerificationActionItem {
|
bottomSheetVerificationActionItem {
|
||||||
id("openEmoji")
|
id("openEmoji")
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.riotx.features.crypto.verification.choose
|
package im.vector.riotx.features.crypto.verification.choose
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
@ -24,9 +26,15 @@ import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.cleanup
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
import im.vector.riotx.core.extensions.configureWith
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.riotx.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||||
|
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
|
||||||
|
import im.vector.riotx.core.utils.allGranted
|
||||||
|
import im.vector.riotx.core.utils.checkPermissions
|
||||||
import im.vector.riotx.features.crypto.verification.VerificationAction
|
import im.vector.riotx.features.crypto.verification.VerificationAction
|
||||||
import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel
|
import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel
|
||||||
|
import im.vector.riotx.features.qrcode.QrCodeScannerActivity
|
||||||
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
|
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class VerificationChooseMethodFragment @Inject constructor(
|
class VerificationChooseMethodFragment @Inject constructor(
|
||||||
|
@ -68,6 +76,47 @@ class VerificationChooseMethodFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openCamera() {
|
override fun openCamera() {
|
||||||
// TODO
|
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
|
||||||
|
doOpenQRCodeScanner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
|
||||||
|
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && allGranted(grantResults)) {
|
||||||
|
doOpenQRCodeScanner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doOpenQRCodeScanner() {
|
||||||
|
QrCodeScannerActivity.startForResult(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
when (requestCode) {
|
||||||
|
QrCodeScannerActivity.QR_CODE_SCANNER_REQUEST_CODE -> {
|
||||||
|
val scannedQrCode = QrCodeScannerActivity.getResultText(data)
|
||||||
|
val wasQrCode = QrCodeScannerActivity.getResultIsQrCode(data)
|
||||||
|
|
||||||
|
if (wasQrCode && !scannedQrCode.isNullOrBlank()) {
|
||||||
|
onRemoteQrCodeScanned(scannedQrCode)
|
||||||
|
} else {
|
||||||
|
Timber.w("It was not a QR code, or empty result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onRemoteQrCodeScanned(remoteQrCode: String) = withState(sharedViewModel) {
|
||||||
|
sharedViewModel.handle(VerificationAction.RemoteQrCodeScanned(
|
||||||
|
it.otherUserMxItem?.id ?: "",
|
||||||
|
it.pendingRequest?.transactionId ?: "",
|
||||||
|
remoteQrCode
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,9 @@ import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
|
||||||
data class VerificationChooseMethodViewState(
|
data class VerificationChooseMethodViewState(
|
||||||
val otherUserId: String = "",
|
val otherUserId: String = "",
|
||||||
val transactionId: String = "",
|
val transactionId: String = "",
|
||||||
val QRModeAvailable: Boolean = false,
|
val otherCanShowQrCode: Boolean = false,
|
||||||
|
val otherCanScanQrCode: Boolean = false,
|
||||||
|
val QRtext: String? = null,
|
||||||
val SASModeAvailable: Boolean = false
|
val SASModeAvailable: Boolean = false
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
||||||
|
@ -49,13 +51,14 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state ->
|
override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state ->
|
||||||
val pvr = session.getSasVerificationService().getExistingVerificationRequest(state.otherUserId, state.transactionId)
|
val pvr = session.getSasVerificationService().getExistingVerificationRequest(state.otherUserId, state.transactionId)
|
||||||
val qrAvailable = pvr?.hasMethod(VerificationMethod.SCAN) ?: false
|
|
||||||
val emojiAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false
|
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
QRModeAvailable = qrAvailable,
|
otherCanShowQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SHOW) ?: false,
|
||||||
SASModeAvailable = emojiAvailable
|
otherCanScanQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SCAN) ?: false,
|
||||||
|
// TODO
|
||||||
|
QRtext = "https://www.example.org",
|
||||||
|
SASModeAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,13 +87,14 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
|
||||||
val args: VerificationBottomSheet.VerificationArgs = viewModelContext.args()
|
val args: VerificationBottomSheet.VerificationArgs = viewModelContext.args()
|
||||||
val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession()
|
val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession()
|
||||||
val pvr = session.getSasVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId)
|
val pvr = session.getSasVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId)
|
||||||
val qrAvailable = pvr?.hasMethod(VerificationMethod.SCAN) ?: false
|
|
||||||
val emojiAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false
|
|
||||||
|
|
||||||
return VerificationChooseMethodViewState(otherUserId = args.otherUserId,
|
return VerificationChooseMethodViewState(otherUserId = args.otherUserId,
|
||||||
transactionId = args.verificationId ?: "",
|
transactionId = args.verificationId ?: "",
|
||||||
QRModeAvailable = qrAvailable,
|
otherCanShowQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SHOW) ?: false,
|
||||||
SASModeAvailable = emojiAvailable
|
otherCanScanQrCode = pvr?.hasMethod(VerificationMethod.QR_CODE_SCAN) ?: false,
|
||||||
|
// TODO
|
||||||
|
QRtext = "https://www.example.org",
|
||||||
|
SASModeAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.riotx.features.crypto.verification.epoxy
|
package im.vector.riotx.features.crypto.verification.epoxy
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
@ -33,11 +34,18 @@ abstract class BottomSheetVerificationBigImageItem : VectorEpoxyModel<BottomShee
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var imageRes: Int = 0
|
var imageRes: Int = 0
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var imageBitmap: Bitmap? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var contentDescription: String? = null
|
var contentDescription: String? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
|
imageBitmap?.let {
|
||||||
|
holder.image.setImageBitmap(it)
|
||||||
|
} ?: run {
|
||||||
holder.image.setImageResource(imageRes)
|
holder.image.setImageResource(imageRes)
|
||||||
|
}
|
||||||
|
|
||||||
if (contentDescription == null) {
|
if (contentDescription == null) {
|
||||||
ViewCompat.setImportantForAccessibility(holder.image, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO)
|
ViewCompat.setImportantForAccessibility(holder.image, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO)
|
||||||
|
|
|
@ -1027,7 +1027,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAvatarClicked(informationData: MessageInformationData) {
|
override fun onAvatarClicked(informationData: MessageInformationData) {
|
||||||
openRoomMemberProfile(informationData.senderId)
|
// DO NOT COMMIT openRoomMemberProfile(informationData.senderId)
|
||||||
|
roomDetailViewModel.handle(RoomDetailAction.RequestVerification(informationData.senderId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openRoomMemberProfile(userId: String) {
|
private fun openRoomMemberProfile(userId: String) {
|
||||||
|
|
|
@ -823,6 +823,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) {
|
private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) {
|
||||||
|
if (action.userId == session.myUserId) return
|
||||||
_requestLiveData.postValue(LiveEvent(Success(action)))
|
_requestLiveData.postValue(LiveEvent(Success(action)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.riotx.core.epoxy.EmptyItem_
|
import im.vector.riotx.core.epoxy.EmptyItem_
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.riotx.core.resources.UserPreferencesProvider
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -29,7 +30,8 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||||
private val noticeItemFactory: NoticeItemFactory,
|
private val noticeItemFactory: NoticeItemFactory,
|
||||||
private val defaultItemFactory: DefaultItemFactory,
|
private val defaultItemFactory: DefaultItemFactory,
|
||||||
private val roomCreateItemFactory: RoomCreateItemFactory,
|
private val roomCreateItemFactory: RoomCreateItemFactory,
|
||||||
private val verificationConclusionItemFactory: VerificationItemFactory) {
|
private val verificationConclusionItemFactory: VerificationItemFactory,
|
||||||
|
private val userPreferencesProvider: UserPreferencesProvider ) {
|
||||||
|
|
||||||
fun create(event: TimelineEvent,
|
fun create(event: TimelineEvent,
|
||||||
nextEvent: TimelineEvent?,
|
nextEvent: TimelineEvent?,
|
||||||
|
@ -73,9 +75,11 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||||
EventType.KEY_VERIFICATION_KEY,
|
EventType.KEY_VERIFICATION_KEY,
|
||||||
EventType.KEY_VERIFICATION_READY,
|
EventType.KEY_VERIFICATION_READY,
|
||||||
EventType.KEY_VERIFICATION_MAC -> {
|
EventType.KEY_VERIFICATION_MAC -> {
|
||||||
// These events are filtered from timeline in normal case
|
// TODO These are not filtered out by timeline when encrypted
|
||||||
// Only visible in developer mode
|
// For now manually ignore
|
||||||
|
if (userPreferencesProvider.shouldShowHiddenEvents()) {
|
||||||
noticeItemFactory.create(event, highlight, callback)
|
noticeItemFactory.create(event, highlight, callback)
|
||||||
|
} else null
|
||||||
}
|
}
|
||||||
EventType.KEY_VERIFICATION_CANCEL,
|
EventType.KEY_VERIFICATION_CANCEL,
|
||||||
EventType.KEY_VERIFICATION_DONE -> {
|
EventType.KEY_VERIFICATION_DONE -> {
|
||||||
|
|
|
@ -108,7 +108,7 @@ class VerificationItemFactory @Inject constructor(
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
}
|
}
|
||||||
else -> ignoredConclusion(event, highlight, callback)
|
else -> return ignoredConclusion(event, highlight, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventType.KEY_VERIFICATION_DONE -> {
|
EventType.KEY_VERIFICATION_DONE -> {
|
||||||
|
|
|
@ -120,6 +120,8 @@ object PopupAlertManager {
|
||||||
}
|
}
|
||||||
currentAlerter = next
|
currentAlerter = next
|
||||||
next?.let {
|
next?.let {
|
||||||
|
|
||||||
|
if (next.shouldBeDisplayedIn?.invoke(currentActivity) == false) return
|
||||||
val currentTime = System.currentTimeMillis()
|
val currentTime = System.currentTimeMillis()
|
||||||
if (next.expirationTimestamp != null && currentTime > next.expirationTimestamp!!) {
|
if (next.expirationTimestamp != null && currentTime > next.expirationTimestamp!!) {
|
||||||
// skip
|
// skip
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.qrcode
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.google.zxing.BarcodeFormat
|
||||||
|
import com.google.zxing.Result
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
import im.vector.riotx.core.extensions.replaceFragment
|
||||||
|
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||||
|
|
||||||
|
class QrCodeScannerActivity : VectorBaseActivity() {
|
||||||
|
|
||||||
|
override fun getLayoutRes() = R.layout.activity_simple
|
||||||
|
|
||||||
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
|
injector.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
if (isFirstCreation()) {
|
||||||
|
replaceFragment(R.id.simpleFragmentContainer, QrCodeScannerFragment::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setResultAndFinish(result: Result?) {
|
||||||
|
result?.let {
|
||||||
|
setResult(RESULT_OK, Intent().apply {
|
||||||
|
putExtra(EXTRA_OUT_TEXT, it.text)
|
||||||
|
putExtra(EXTRA_OUT_IS_QR_CODE, it.barcodeFormat == BarcodeFormat.QR_CODE)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val EXTRA_OUT_TEXT = "EXTRA_OUT_TEXT"
|
||||||
|
private const val EXTRA_OUT_IS_QR_CODE = "EXTRA_OUT_IS_QR_CODE"
|
||||||
|
|
||||||
|
const val QR_CODE_SCANNER_REQUEST_CODE = 429
|
||||||
|
|
||||||
|
// For test only
|
||||||
|
fun startForResult(activity: Activity, requestCode: Int = QR_CODE_SCANNER_REQUEST_CODE) {
|
||||||
|
activity.startActivityForResult(Intent(activity, QrCodeScannerActivity::class.java), requestCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startForResult(fragment: Fragment, requestCode: Int = QR_CODE_SCANNER_REQUEST_CODE) {
|
||||||
|
fragment.startActivityForResult(Intent(fragment.requireActivity(), QrCodeScannerActivity::class.java), requestCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getResultText(data: Intent?): String? {
|
||||||
|
return data?.getStringExtra(EXTRA_OUT_TEXT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getResultIsQrCode(data: Intent?): Boolean {
|
||||||
|
return data?.getBooleanExtra(EXTRA_OUT_IS_QR_CODE, false) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.qrcode
|
||||||
|
|
||||||
|
import com.google.zxing.Result
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import kotlinx.android.synthetic.main.fragment_qr_code_scanner.*
|
||||||
|
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class QrCodeScannerFragment @Inject constructor()
|
||||||
|
: VectorBaseFragment(),
|
||||||
|
ZXingScannerView.ResultHandler {
|
||||||
|
|
||||||
|
override fun getLayoutResId() = R.layout.fragment_qr_code_scanner
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
// Register ourselves as a handler for scan results.
|
||||||
|
scannerView.setResultHandler(this)
|
||||||
|
// Start camera on resume
|
||||||
|
scannerView.startCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
// Stop camera on pause
|
||||||
|
scannerView.stopCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleResult(rawResult: Result?) {
|
||||||
|
// Do something with the result here
|
||||||
|
// This is not intended to be used outside of QrCodeScannerActivity for the moment
|
||||||
|
(requireActivity() as? QrCodeScannerActivity)?.setResultAndFinish(rawResult)
|
||||||
|
}
|
||||||
|
}
|
18
vector/src/main/res/layout/fragment_qr_code_scanner.xml
Normal file
18
vector/src/main/res/layout/fragment_qr_code_scanner.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?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">
|
||||||
|
|
||||||
|
<me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||||
|
android:id="@+id/scannerView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<!-- TODO In the future we could add a toggle to switch the flash, and other possible settings -->
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,7 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/itemVerificationBigImage"
|
android:id="@+id/itemVerificationBigImage"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="180dp"
|
android:layout_height="180dp"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
android:src="@drawable/ic_shield_trusted" />
|
tools:src="@drawable/ic_shield_trusted" />
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
<string name="verification_scan_emoji_title">Can\'t scan</string>
|
<string name="verification_scan_emoji_title">Can\'t scan</string>
|
||||||
<string name="verification_scan_emoji_subtitle">If you\'re not in person, compare emoji instead</string>
|
<string name="verification_scan_emoji_subtitle">If you\'re not in person, compare emoji instead</string>
|
||||||
|
|
||||||
<string name="verification_no_scan_emoji_title">Continue</string>
|
<string name="verification_no_scan_emoji_title">Verify by comparing emojis</string>
|
||||||
|
|
||||||
<string name="verify_by_emoji_title">Verify by Emoji</string>
|
<string name="verify_by_emoji_title">Verify by Emoji</string>
|
||||||
<string name="verify_by_emoji_description">If you can’t scan the code above, verify by comparing a short, unique selection of emoji.</string>
|
<string name="verify_by_emoji_description">If you can’t scan the code above, verify by comparing a short, unique selection of emoji.</string>
|
||||||
|
|
Loading…
Reference in a new issue