Merge branch 'develop' into feature/bma/posthog

This commit is contained in:
Benoit Marty 2021-12-07 14:26:31 +01:00
commit 673cbb29de
307 changed files with 1837 additions and 1850 deletions

View file

@ -6,7 +6,7 @@ on:
jobs:
move_needs_info_issues:
name: Move X-Needs-Info issues to Need info on triage board
name: X-Needs-Info issues to Need info column on triage board
runs-on: ubuntu-latest
steps:
- uses: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338
@ -17,15 +17,17 @@ jobs:
label-name: "X-Needs-Info"
add_priority_design_issues_to_project:
name: Move priority X-Needs-Design issues to Design project board
name: P1 X-Needs-Design to Design project board
runs-on: ubuntu-latest
if: >
contains(github.event.issue.labels.*.name, 'X-Needs-Design') &&
(contains(github.event.issue.labels.*.name, 'O-Frequent') ||
contains(github.event.issue.labels.*.name, 'O-Occasional')) &&
(contains(github.event.issue.labels.*.name, 'S-Critical') ||
contains(github.event.issue.labels.*.name, 'S-Major') ||
contains(github.event.issue.labels.*.name, 'S-Minor'))
(contains(github.event.issue.labels.*.name, 'S-Critical') &&
(contains(github.event.issue.labels.*.name, 'O-Frequent') ||
contains(github.event.issue.labels.*.name, 'O-Occasional')) ||
contains(github.event.issue.labels.*.name, 'S-Major') &&
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
contains(github.event.issue.labels.*.name, 'A11y') &&
contains(github.event.issue.labels.*.name, 'O-Frequent'))
steps:
- uses: octokit/graphql-action@v2.x
id: add_to_project
@ -45,8 +47,8 @@ jobs:
PROJECT_ID: "PN_kwDOAM0swc0sUA"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_spaces_issues:
name: Move Spaces issues to Delight project board
spaces_issues_to_old_board:
name: Spaces issues to old Delight project board
runs-on: ubuntu-latest
if: >
contains(github.event.issue.labels.*.name, 'A-Spaces') ||
@ -59,8 +61,16 @@ jobs:
project-url: "https://github.com/orgs/vector-im/projects/6"
column-name: "📥 Inbox"
label-name: "A-Spaces"
spaces_issues_to_new_board:
name: Spaces issues to new Delight project board
runs-on: ubuntu-latest
if: >
contains(github.event.issue.labels.*.name, 'A-Spaces') ||
contains(github.event.issue.labels.*.name, 'A-Space-Settings') ||
contains(github.event.issue.labels.*.name, 'A-Subspaces')
steps:
- uses: octokit/graphql-action@v2.x
id: add_to_delight2
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
@ -78,7 +88,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_voice-message_issues:
name: Move A-Voice Messages to Voice message board
name: A-Voice Messages to voice message board
runs-on: ubuntu-latest
if: >
contains(github.event.issue.labels.*.name, 'A-Voice Messages')
@ -101,7 +111,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_threads_issues:
name: Move A-Threads to Thread board
name: A-Threads to Thread board
runs-on: ubuntu-latest
if: >
contains(github.event.issue.labels.*.name, 'A-Threads')

View file

@ -1,4 +1,4 @@
name: Move P1 issues into the P1 column for the App Team and Crypto team
name: Move P1 bugs to boards
on:
issues:

1
changelog.d/4324.bugfix Normal file
View file

@ -0,0 +1 @@
Fixes message menu showing when copying message urls

1
changelog.d/4546.bugfix Normal file
View file

@ -0,0 +1 @@
Fix lots of integration tests by introducing TestMatrix class and MatrixWorkerFactory.

1
changelog.d/4604.misc Normal file
View file

@ -0,0 +1 @@
Cleanup the layout files

1
changelog.d/4617.misc Normal file
View file

@ -0,0 +1 @@
Improve issue automation workflows

1
changelog.d/4621.bugfix Normal file
View file

@ -0,0 +1 @@
Fix for outgoing voip call via sip bridge failing after 1 minute.

1
changelog.d/4626.misc Normal file
View file

@ -0,0 +1 @@
Introducing feature flagging to the login and notification settings flows

1
changelog.d/4636.bugfix Normal file
View file

@ -0,0 +1 @@
Update log warning for call selection during voip calls.

1
changelog.d/4645.misc Normal file
View file

@ -0,0 +1 @@
Debounce some clicks

View file

@ -11,7 +11,7 @@ def gradle = "7.0.3"
// Ref: https://kotlinlang.org/releases.html
def kotlin = "1.5.31"
def kotlinCoroutines = "1.5.2"
def dagger = "2.40.3"
def dagger = "2.40.5"
def retrofit = "2.9.0"
def arrow = "0.8.2"
def markwon = "4.6.2"

View file

@ -34,7 +34,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout">
app:layout_constraintTop_toBottomOf="@id/appBarLayout">
<LinearLayout
android:layout_width="match_parent"
@ -334,7 +334,6 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent"

View file

@ -5,7 +5,6 @@
<item name="android:visibility">visible</item>
</style>
<style name="Theme.Debug.Light" parent="Theme.MaterialComponents.Light.NoActionBar">
<!-- Keep all default value -->
</style>

View file

@ -11,5 +11,3 @@
<changeImageTransform />
</transitionSet>

View file

@ -32,7 +32,6 @@
<dimen name="call_pip_width">88dp</dimen>
<dimen name="call_pip_radius">8dp</dimen>
<dimen name="item_form_min_height">76dp</dimen>
<!-- Max width for some buttons -->
@ -40,5 +39,4 @@
<!-- Navigation Drawer -->
<dimen name="navigation_drawer_max_width">320dp</dimen>
</resources>

View file

@ -20,7 +20,6 @@
<color name="palette_prune">#5C56F5</color>
<color name="palette_links">#0086E6</color>
<!-- For light themes -->
<color name="palette_gray_25">#F4F6FA</color>
<color name="palette_gray_50">#E3E8F0</color>

View file

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BadgeFloatingActionButton">

View file

@ -49,7 +49,6 @@
<item name="android:backgroundTint">@android:color/black</item>
</style>
<style name="Widget.Vector.Button.Outlined.SocialLogin.Facebook">
<item name="icon">@drawable/ic_social_facebook</item>
</style>
@ -68,7 +67,6 @@
<item name="android:backgroundTint">#3877EA</item>
</style>
<style name="Widget.Vector.Button.Outlined.SocialLogin.Twitter">
<item name="icon">@drawable/ic_social_twitter</item>
</style>
@ -85,7 +83,6 @@
<item name="android:backgroundTint">#5D9EC9</item>
</style>
<style name="Widget.Vector.Button.Outlined.SocialLogin.Apple">
<item name="icon">@drawable/ic_social_apple</item>
</style>
@ -118,5 +115,4 @@
<item name="android:backgroundTint">@android:color/black</item>
</style>
</resources>

View file

@ -20,9 +20,10 @@ import android.content.Context
import android.net.Uri
import androidx.lifecycle.Observer
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
@ -30,7 +31,6 @@ import kotlinx.coroutines.withTimeout
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@ -45,7 +45,7 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.api.session.sync.SyncState
import java.util.ArrayList
import timber.log.Timber
import java.util.UUID
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@ -56,13 +56,14 @@ import java.util.concurrent.TimeUnit
*/
class CommonTestHelper(context: Context) {
val matrix: Matrix
internal val matrix: TestMatrix
val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestNetworkModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
init {
UiThreadStatement.runOnUiThread {
Matrix.initialize(
TestMatrix.initialize(
context,
MatrixConfiguration(
applicationFlavor = "TestFlavor",
@ -70,7 +71,7 @@ class CommonTestHelper(context: Context) {
)
)
}
matrix = Matrix.getInstance(context)
matrix = TestMatrix.getInstance(context)
}
fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session {
@ -95,33 +96,47 @@ class CommonTestHelper(context: Context) {
*
* @param session the session to sync
*/
@Suppress("EXPERIMENTAL_API_USAGE")
fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis) {
fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis * 10) {
val lock = CountDownLatch(1)
val job = GlobalScope.launch(Dispatchers.Main) {
session.open()
}
runBlocking { job.join() }
session.startSync(true)
val syncLiveData = runBlocking(Dispatchers.Main) {
session.getSyncStateLive()
}
val syncObserver = object : Observer<SyncState> {
override fun onChanged(t: SyncState?) {
if (session.hasAlreadySynced()) {
lock.countDown()
syncLiveData.removeObserver(this)
coroutineScope.launch {
session.startSync(true)
val syncLiveData = session.getSyncStateLive()
val syncObserver = object : Observer<SyncState> {
override fun onChanged(t: SyncState?) {
if (session.hasAlreadySynced()) {
lock.countDown()
syncLiveData.removeObserver(this)
}
}
}
syncLiveData.observeForever(syncObserver)
}
GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) }
await(lock, timeout)
}
/**
* This methods clear the cache and waits for initialSync
*
* @param session the session to sync
*/
fun clearCacheAndSync(session: Session, timeout: Long = TestConstants.timeOutMillis) {
waitWithLatch(timeout) { latch ->
session.clearCache()
val syncLiveData = session.getSyncStateLive()
val syncObserver = object : Observer<SyncState> {
override fun onChanged(t: SyncState?) {
if (session.hasAlreadySynced()) {
Timber.v("Clear cache and synced")
syncLiveData.removeObserver(this)
latch.countDown()
}
}
}
syncLiveData.observeForever(syncObserver)
session.startSync(true)
}
}
/**
* Sends text messages in a room
*
@ -130,46 +145,57 @@ class CommonTestHelper(context: Context) {
* @param nbOfMessages the number of time the message will be sent
*/
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
val timeline = room.createTimeline(null, TimelineSettings(10))
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
val latch = CountDownLatch(1)
val timelineListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) {
}
val timeline = room.createTimeline(null, TimelineSettings(10))
timeline.start()
waitWithLatch(timeout + 1_000L * nbOfMessages) { latch ->
val timelineListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) {
}
override fun onNewTimelineEvents(eventIds: List<String>) {
// noop
}
override fun onNewTimelineEvents(eventIds: List<String>) {
// noop
}
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
val newMessages = snapshot
.filter { it.root.sendState == SendState.SYNCED }
.filter { it.root.getClearType() == EventType.MESSAGE }
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
val newMessages = snapshot
.filter { it.root.sendState == SendState.SYNCED }
.filter { it.root.getClearType() == EventType.MESSAGE }
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
if (newMessages.size == nbOfMessages) {
sentEvents.addAll(newMessages)
// Remove listener now, if not at the next update sendEvents could change
timeline.removeListener(this)
latch.countDown()
Timber.v("New synced message size: ${newMessages.size}")
if (newMessages.size == nbOfMessages) {
sentEvents.addAll(newMessages)
// Remove listener now, if not at the next update sendEvents could change
timeline.removeListener(this)
latch.countDown()
}
}
}
timeline.addListener(timelineListener)
sendTextMessagesBatched(room, message, nbOfMessages)
}
timeline.start()
timeline.addListener(timelineListener)
for (i in 0 until nbOfMessages) {
room.sendTextMessage(message + " #" + (i + 1))
}
// Wait 3 second more per message
await(latch, timeout = timeout + 3_000L * nbOfMessages)
timeline.dispose()
// Check that all events has been created
assertEquals("Message number do not match $sentEvents", nbOfMessages.toLong(), sentEvents.size.toLong())
return sentEvents
}
/**
* Will send nb of messages provided by count parameter but waits a bit every 10 messages to avoid gap in sync
*/
private fun sendTextMessagesBatched(room: Room, message: String, count: Int) {
(1 until count + 1)
.map { "$message #$it" }
.chunked(10)
.forEach { batchedMessages ->
batchedMessages.forEach { formattedMessage ->
room.sendTextMessage(formattedMessage)
}
Thread.sleep(1_000L)
}
}
// PRIVATE METHODS *****************************************************************************
/**
@ -239,10 +265,10 @@ class CommonTestHelper(context: Context) {
assertTrue(registrationResult is RegistrationResult.Success)
val session = (registrationResult as RegistrationResult.Success).session
session.open()
if (sessionTestParams.withInitialSync) {
syncSession(session, 60_000)
}
return session
}
@ -267,7 +293,7 @@ class CommonTestHelper(context: Context) {
.getLoginWizard()
.login(userName, password, "myDevice")
}
session.open()
if (sessionTestParams.withInitialSync) {
syncSession(session)
}
@ -332,22 +358,21 @@ class CommonTestHelper(context: Context) {
assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
}
@Suppress("EXPERIMENTAL_API_USAGE")
fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
GlobalScope.launch {
while (true) {
delay(1000)
if (condition()) {
latch.countDown()
return@launch
}
suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
while (true) {
delay(1000)
if (condition()) {
latch.countDown()
return
}
}
}
fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, block: (CountDownLatch) -> Unit) {
fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, dispatcher: CoroutineDispatcher = Dispatchers.Main, block: suspend (CountDownLatch) -> Unit) {
val latch = CountDownLatch(1)
block(latch)
coroutineScope.launch(dispatcher) {
block(latch)
}
await(latch, timeout)
}

View file

@ -19,10 +19,6 @@ package org.matrix.android.sdk.common
import android.os.SystemClock
import android.util.Log
import androidx.lifecycle.Observer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
@ -31,6 +27,7 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
@ -44,16 +41,16 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
import java.util.UUID
import java.util.concurrent.CountDownLatch
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
class CryptoTestHelper(private val testHelper: CommonTestHelper) {
private val messagesFromAlice: List<String> = listOf("0 - Hello I'm Alice!", "4 - Go!")
private val messagesFromBob: List<String> = listOf("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.")
@ -64,27 +61,33 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
* @return alice session
*/
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
val roomId = mTestHelper.runBlockingTest {
val roomId = testHelper.runBlockingTest {
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
}
if (encryptedRoom) {
val room = aliceSession.getRoom(roomId)!!
mTestHelper.runBlockingTest {
testHelper.waitWithLatch { latch ->
val room = aliceSession.getRoom(roomId)!!
room.enableEncryption()
val roomSummaryLive = room.getRoomSummaryLive()
val roomSummaryObserver = object : Observer<Optional<RoomSummary>> {
override fun onChanged(roomSummary: Optional<RoomSummary>) {
if (roomSummary.getOrNull()?.isEncrypted.orFalse()) {
roomSummaryLive.removeObserver(this)
latch.countDown()
}
}
}
roomSummaryLive.observeForever(roomSummaryObserver)
}
}
return CryptoTestData(roomId, listOf(aliceSession))
}
/**
* @return alice and bob sessions
*/
@Suppress("EXPERIMENTAL_API_USAGE")
fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData {
val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom)
val aliceSession = cryptoTestData.firstSession
@ -92,54 +95,37 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
val aliceRoom = aliceSession.getRoom(aliceRoomId)!!
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
val bobSession = testHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
val lock1 = CountDownLatch(1)
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
}
val newRoomObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) {
if (t?.isNotEmpty() == true) {
lock1.countDown()
bobRoomSummariesLive.removeObserver(this)
testHelper.waitWithLatch { latch ->
val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
val newRoomObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) {
if (t?.isNotEmpty() == true) {
bobRoomSummariesLive.removeObserver(this)
latch.countDown()
}
}
}
}
GlobalScope.launch(Dispatchers.Main) {
bobRoomSummariesLive.observeForever(newRoomObserver)
}
mTestHelper.runBlockingTest {
aliceRoom.invite(bobSession.myUserId)
}
mTestHelper.await(lock1)
val lock = CountDownLatch(1)
val roomJoinedObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) {
if (bobSession.getRoom(aliceRoomId)
?.getRoomMember(aliceSession.myUserId)
?.membership == Membership.JOIN) {
lock.countDown()
bobRoomSummariesLive.removeObserver(this)
testHelper.waitWithLatch { latch ->
val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
val roomJoinedObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) {
if (bobSession.getRoom(aliceRoomId)
?.getRoomMember(bobSession.myUserId)
?.membership == Membership.JOIN) {
bobRoomSummariesLive.removeObserver(this)
latch.countDown()
}
}
}
}
GlobalScope.launch(Dispatchers.Main) {
bobRoomSummariesLive.observeForever(roomJoinedObserver)
bobSession.joinRoom(aliceRoomId)
}
mTestHelper.runBlockingTest { bobSession.joinRoom(aliceRoomId) }
mTestHelper.await(lock)
// Ensure bob can send messages to the room
// val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
// assertNotNull(roomFromBobPOV.powerLevels)
@ -171,13 +157,13 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
* @Return Sam session
*/
fun createSamAccountAndInviteToTheRoom(room: Room): Session {
val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
val samSession = testHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
room.invite(samSession.myUserId, null)
}
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
samSession.joinRoom(room.roomId, null, emptyList())
}
@ -194,23 +180,20 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
val bobSession = cryptoTestData.secondSession!!
bobSession.cryptoService().setWarnOnUnknownDevices(false)
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
// Alice sends a message
mTestHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1)
// roomFromAlicePOV.sendTextMessage(messagesFromAlice[0])
testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1)
// Bob send 3 messages
mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1)
mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1)
mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1)
testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1)
testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1)
testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1)
// Alice sends a message
mTestHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1)
testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1)
return cryptoTestData
}
@ -256,60 +239,44 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
)
}
@Suppress("EXPERIMENTAL_API_USAGE")
fun createDM(alice: Session, bob: Session): String {
val roomId = mTestHelper.runBlockingTest {
alice.createDirectRoom(bob.myUserId)
}
mTestHelper.waitWithLatch { latch ->
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
bob.getRoomSummariesLive(roomSummaryQueryParams { })
}
var roomId: String = ""
testHelper.waitWithLatch { latch ->
roomId = alice.createDirectRoom(bob.myUserId)
val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { })
val newRoomObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) {
val indexOfFirst = t?.indexOfFirst { it.roomId == roomId } ?: -1
if (indexOfFirst != -1) {
latch.countDown()
bobRoomSummariesLive.removeObserver(this)
latch.countDown()
}
}
}
GlobalScope.launch(Dispatchers.Main) {
bobRoomSummariesLive.observeForever(newRoomObserver)
}
bobRoomSummariesLive.observeForever(newRoomObserver)
}
mTestHelper.waitWithLatch { latch ->
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
bob.getRoomSummariesLive(roomSummaryQueryParams { })
}
testHelper.waitWithLatch { latch ->
val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { })
val newRoomObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) {
if (bob.getRoom(roomId)
?.getRoomMember(bob.myUserId)
?.membership == Membership.JOIN) {
latch.countDown()
bobRoomSummariesLive.removeObserver(this)
latch.countDown()
}
}
}
GlobalScope.launch(Dispatchers.Main) {
bobRoomSummariesLive.observeForever(newRoomObserver)
}
mTestHelper.runBlockingTest { bob.joinRoom(roomId) }
bobRoomSummariesLive.observeForever(newRoomObserver)
bob.joinRoom(roomId)
}
return roomId
}
fun initializeCrossSigning(session: Session) {
mTestHelper.doSync<Unit> {
testHelper.doSync<Unit> {
session.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
@ -346,8 +313,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
var bobPovTx: IncomingSasVerificationTransaction? = null
// wait for alice to get the ready
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
if (bobPovTx?.state == VerificationTxState.OnStarted) {
@ -359,16 +326,16 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
}
}
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? OutgoingSasVerificationTransaction
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}")
alicePovTx?.state == VerificationTxState.ShortCodeReady
}
}
// wait for alice to get the ready
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
if (bobPovTx?.state == VerificationTxState.OnStarted) {
@ -383,38 +350,38 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
bobPovTx!!.userHasVerifiedShortCode()
alicePovTx!!.userHasVerifiedShortCode()
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
}
}
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
}
}
}
fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
val roomId = mTestHelper.runBlockingTest {
val roomId = testHelper.runBlockingTest {
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
}
val room = aliceSession.getRoom(roomId)!!
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
room.enableEncryption()
}
val sessions = mutableListOf(aliceSession)
for (index in 1 until numberOfMembers) {
val session = mTestHelper.createAccount("User_$index", defaultSessionParams)
mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
val session = testHelper.createAccount("User_$index", defaultSessionParams)
testHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
println("TEST -> " + session.myUserId + " invited")
mTestHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) }
testHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) }
println("TEST -> " + session.myUserId + " joined")
sessions.add(session)
}

View file

@ -1,5 +1,5 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,13 +14,18 @@
* limitations under the License.
*/
package org.matrix.android.sdk.internal.worker
package org.matrix.android.sdk.common
import android.content.Context
import androidx.work.ListenableWorker
import androidx.work.WorkerParameters
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
interface DelegateWorkerFactory {
/**
* Force foreground for testing
*/
internal class TestBackgroundDetectionObserver : BackgroundDetectionObserver {
fun create(context: Context, params: WorkerParameters): ListenableWorker
override val isInBackground: Boolean = false
override fun register(listener: BackgroundDetectionObserver.Listener) = Unit
override fun unregister(listener: BackgroundDetectionObserver.Listener) = Unit
}

View file

@ -1,5 +1,5 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.matrix.android.sdk.api
package org.matrix.android.sdk.common
import android.content.Context
import android.os.Handler
@ -24,27 +24,27 @@ import androidx.work.Configuration
import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import org.matrix.android.sdk.api.network.ApiInterceptorListener
import org.matrix.android.sdk.api.network.ApiPath
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.common.DaggerTestMatrixComponent
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.network.ApiInterceptor
import org.matrix.android.sdk.internal.network.UserAgentHolder
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
import org.matrix.olm.OlmManager
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
/**
* This is the main entry point to the matrix sdk.
* To get the singleton instance, use getInstance static method.
* This mimics the Matrix class but using TestMatrixComponent internally instead of regular MatrixComponent.
*/
class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
internal class TestMatrix constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
@Inject internal lateinit var legacySessionImporter: LegacySessionImporter
@Inject internal lateinit var authenticationService: AuthenticationService
@ -55,15 +55,18 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
@Inject internal lateinit var sessionManager: SessionManager
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
@Inject internal lateinit var apiInterceptor: ApiInterceptor
@Inject internal lateinit var matrixWorkerFactory: MatrixWorkerFactory
private val uiHandler = Handler(Looper.getMainLooper())
init {
Monarchy.init(context)
DaggerTestMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
if (context.applicationContext !is Configuration.Provider) {
WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build())
}
val configuration = Configuration.Builder()
.setExecutor(Executors.newCachedThreadPool())
.setWorkerFactory(matrixWorkerFactory)
.build()
WorkManager.initialize(context, configuration)
uiHandler.post {
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
}
@ -93,21 +96,21 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
companion object {
private lateinit var instance: Matrix
private lateinit var instance: TestMatrix
private val isInit = AtomicBoolean(false)
fun initialize(context: Context, matrixConfiguration: MatrixConfiguration) {
if (isInit.compareAndSet(false, true)) {
instance = Matrix(context.applicationContext, matrixConfiguration)
instance = TestMatrix(context.applicationContext, matrixConfiguration)
}
}
fun getInstance(context: Context): Matrix {
fun getInstance(context: Context): TestMatrix {
if (isInit.compareAndSet(false, true)) {
val appContext = context.applicationContext
if (appContext is MatrixConfiguration.Provider) {
val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration()
instance = Matrix(appContext, matrixConfiguration)
instance = TestMatrix(appContext, matrixConfiguration)
} else {
throw IllegalStateException("Matrix is not initialized properly." +
" You should call Matrix.initialize or let your application implements MatrixConfiguration.Provider.")

View file

@ -34,12 +34,13 @@ import org.matrix.android.sdk.internal.util.system.SystemModule
NetworkModule::class,
AuthModule::class,
RawModule::class,
SystemModule::class,
TestNetworkModule::class
SystemModule::class
])
@MatrixScope
internal interface TestMatrixComponent : MatrixComponent {
fun inject(matrix: TestMatrix)
@Component.Factory
interface Factory {
fun create(@BindsInstance context: Context,

View file

@ -18,10 +18,39 @@ package org.matrix.android.sdk.common
import dagger.Binds
import dagger.Module
import dagger.Provides
import org.matrix.android.sdk.internal.di.MatrixComponent
import org.matrix.android.sdk.internal.di.MatrixScope
import org.matrix.android.sdk.internal.session.MockHttpInterceptor
import org.matrix.android.sdk.internal.session.TestInterceptor
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
@Module
internal abstract class TestModule {
@Binds
abstract fun providesMatrixComponent(testMatrixComponent: TestMatrixComponent): MatrixComponent
@Module
companion object {
val interceptors = ArrayList<TestInterceptor>()
fun interceptorForSession(sessionId: String): TestInterceptor? = interceptors.firstOrNull { it.sessionId == sessionId }
@Provides
@JvmStatic
@MockHttpInterceptor
fun providesTestInterceptor(): TestInterceptor? {
return MockOkHttpInterceptor().also {
interceptors.add(it)
}
}
@Provides
@JvmStatic
@MatrixScope
fun providesBackgroundDetectionObserver(): BackgroundDetectionObserver {
return TestBackgroundDetectionObserver()
}
}
}

View file

@ -1,39 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.common
import dagger.Module
import dagger.Provides
import org.matrix.android.sdk.internal.session.MockHttpInterceptor
import org.matrix.android.sdk.internal.session.TestInterceptor
@Module
internal object TestNetworkModule {
val interceptors = ArrayList<TestInterceptor>()
fun interceptorForSession(sessionId: String): TestInterceptor? = interceptors.firstOrNull { it.sessionId == sessionId }
@Provides
@JvmStatic
@MockHttpInterceptor
fun providesTestInterceptor(): TestInterceptor? {
return MockOkHttpInterceptor().also {
interceptors.add(it)
}
}
}

View file

@ -36,12 +36,12 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
@FixMethodOrder(MethodSorters.JVM)
class PreShareKeysTest : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
@Test
fun ensure_outbound_session_happy_path() {
val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val e2eRoomID = testData.roomId
val aliceSession = testData.firstSession
val bobSession = testData.secondSession!!
@ -58,12 +58,12 @@ class PreShareKeysTest : InstrumentedTest {
Log.d("#Test", "Room Key Received from alice $preShareCount")
// Force presharing of new outbound key
mTestHelper.doSync<Unit> {
testHelper.doSync<Unit> {
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID, it)
}
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val newGossipCount = bobSession.cryptoService().getGossipingEvents().count {
it.senderId == aliceSession.myUserId &&
it.getClearType() == EventType.ROOM_KEY
@ -88,16 +88,16 @@ class PreShareKeysTest : InstrumentedTest {
assertEquals("The session received by bob should match what alice sent", 0, sharedIndex)
// Just send a real message as test
val sentEvent = mTestHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first()
val sentEvent = testHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first()
assertEquals(megolmSessionId, sentEvent.root.content.toModel<EncryptedEventContent>()?.sessionId, "Unexpected megolm session")
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
bobSession.getRoom(e2eRoomID)?.getTimeLineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE
}
}
mTestHelper.signOutAndClose(aliceSession)
mTestHelper.signOutAndClose(bobSession)
testHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(bobSession)
}
}

View file

@ -62,8 +62,8 @@ import kotlin.coroutines.resume
class UnwedgingTest : InstrumentedTest {
private lateinit var messagesReceivedByBob: List<TimelineEvent>
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
@Before
fun init() {
@ -85,7 +85,7 @@ class UnwedgingTest : InstrumentedTest {
*/
@Test
fun testUnwedging() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val aliceRoomId = cryptoTestData.roomId
@ -133,7 +133,7 @@ class UnwedgingTest : InstrumentedTest {
roomFromAlicePOV.sendTextMessage("First message")
// Wait for the message to be received by Bob
mTestHelper.await(latch)
testHelper.await(latch)
bobTimeline.removeListener(bobEventsListener)
messagesReceivedByBob.size shouldBe 1
@ -161,7 +161,7 @@ class UnwedgingTest : InstrumentedTest {
roomFromAlicePOV.sendTextMessage("Second message")
// Wait for the message to be received by Bob
mTestHelper.await(latch)
testHelper.await(latch)
bobTimeline.removeListener(bobEventsListener)
messagesReceivedByBob.size shouldBe 2
@ -179,7 +179,7 @@ class UnwedgingTest : InstrumentedTest {
aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
// Wait for the message to be received by Bob
mTestHelper.waitWithLatch {
testHelper.waitWithLatch {
bobEventsListener = createEventListener(it, 3)
bobTimeline.addListener(bobEventsListener)
messagesReceivedByBob = emptyList()
@ -201,11 +201,11 @@ class UnwedgingTest : InstrumentedTest {
Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[1].root.getClearType())
Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[2].root.getClearType())
// Bob Should not be able to decrypt last message, because session could not be sent as the olm channel was wedged
mTestHelper.await(bobFinalLatch)
testHelper.await(bobFinalLatch)
bobTimeline.removeListener(bobHasThreeDecryptedEventsListener)
// It's a trick to force key request on fail to decrypt
mTestHelper.doSync<Unit> {
testHelper.doSync<Unit> {
bobSession.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
@ -222,8 +222,8 @@ class UnwedgingTest : InstrumentedTest {
}
// Wait until we received back the key
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
// we should get back the key and be able to decrypt
val result = tryOrNull {
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
@ -235,7 +235,7 @@ class UnwedgingTest : InstrumentedTest {
bobTimeline.dispose()
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
private fun createEventListener(latch: CountDownLatch, expectedNumberOfMessages: Int): Timeline.Listener {

View file

@ -45,14 +45,14 @@ import kotlin.coroutines.resume
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class XSigningTest : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
@Test
fun test_InitializeAndStoreKeys() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
mTestHelper.doSync<Unit> {
testHelper.doSync<Unit> {
aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
@ -79,12 +79,12 @@ class XSigningTest : InstrumentedTest {
assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified())
mTestHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(aliceSession)
}
@Test
fun test_CrossSigningCheckBobSeesTheKeys() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
@ -98,21 +98,21 @@ class XSigningTest : InstrumentedTest {
password = TestConstants.PASSWORD
)
mTestHelper.doSync<Unit> {
testHelper.doSync<Unit> {
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(aliceAuthParams)
}
}, it)
}
mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(bobAuthParams)
}
}, it) }
// Check that alice can see bob keys
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
@ -124,13 +124,13 @@ class XSigningTest : InstrumentedTest {
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
mTestHelper.signOutAndClose(aliceSession)
mTestHelper.signOutAndClose(bobSession)
testHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(bobSession)
}
@Test
fun test_CrossSigningTestAliceTrustBobNewDevice() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
@ -144,12 +144,12 @@ class XSigningTest : InstrumentedTest {
password = TestConstants.PASSWORD
)
mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(aliceAuthParams)
}
}, it) }
mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(bobAuthParams)
}
@ -157,21 +157,21 @@ class XSigningTest : InstrumentedTest {
// Check that alice can see bob keys
val bobUserId = bobSession.myUserId
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) }
testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) }
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) }
testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) }
// Now bobs logs in on a new device and verifies it
// We will want to test that in alice POV, this new device would be trusted by cross signing
val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true))
val bobSession2 = testHelper.logIntoAccount(bobUserId, SessionTestParams(true))
val bobSecondDeviceId = bobSession2.sessionParams.deviceId!!
// Check that bob first session sees the new login
val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
val data = testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
}
@ -183,12 +183,12 @@ class XSigningTest : InstrumentedTest {
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
// Manually mark it as trusted from first session
mTestHelper.doSync<Unit> {
testHelper.doSync<Unit> {
bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId, it)
}
// Now alice should cross trust bob's second device
val data2 = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
val data2 = testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
}
@ -200,8 +200,8 @@ class XSigningTest : InstrumentedTest {
val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
mTestHelper.signOutAndClose(aliceSession)
mTestHelper.signOutAndClose(bobSession)
mTestHelper.signOutAndClose(bobSession2)
testHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(bobSession)
testHelper.signOutAndClose(bobSession2)
}
}

View file

@ -40,8 +40,9 @@ import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class EncryptionTest : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
@Test
fun test_EncryptionEvent() {
@ -69,7 +70,7 @@ class EncryptionTest : InstrumentedTest {
}
private fun performTest(roomShouldBeEncrypted: Boolean, action: (Room) -> Unit) {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false)
val aliceSession = cryptoTestData.firstSession
val room = aliceSession.getRoom(cryptoTestData.roomId)!!
@ -101,12 +102,12 @@ class EncryptionTest : InstrumentedTest {
timeline.addListener(timelineListener)
action.invoke(room)
mTestHelper.await(latch)
testHelper.await(latch)
timeline.dispose()
room.isEncrypted() shouldBe roomShouldBeEncrypted
cryptoTestData.cleanUp(mTestHelper)
testHelper.waitWithLatch {
room.isEncrypted() shouldBe roomShouldBeEncrypted
it.countDown()
}
cryptoTestData.cleanUp(testHelper)
}
}

View file

@ -44,7 +44,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.TestConstants
import org.matrix.android.sdk.internal.crypto.GossipingRequestState
@ -55,7 +54,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import java.util.concurrent.CountDownLatch
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
@ -63,15 +61,14 @@ import kotlin.coroutines.resume
@FixMethodOrder(MethodSorters.JVM)
class KeyShareTests : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
private val commonTestHelper = CommonTestHelper(context())
@Test
fun test_DoNotSelfShareIfNotTrusted() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
// Create an encrypted room and add a message
val roomId = mTestHelper.runBlockingTest {
val roomId = commonTestHelper.runBlockingTest {
aliceSession.createRoom(
CreateRoomParams().apply {
visibility = RoomDirectoryVisibility.PRIVATE
@ -83,11 +80,11 @@ class KeyShareTests : InstrumentedTest {
assertNotNull(room)
Thread.sleep(4_000)
assertTrue(room?.isEncrypted() == true)
val sentEventId = mTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
val sentEventId = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
// Open a new sessionx
val aliceSession2 = mTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
val roomSecondSessionPOV = aliceSession2.getRoom(roomId)
@ -105,25 +102,24 @@ class KeyShareTests : InstrumentedTest {
// Try to request
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root)
val waitLatch = CountDownLatch(1)
val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId
var outGoingRequestId: String? = null
mTestHelper.retryPeriodicallyWithLatch(waitLatch) {
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
.filter { req ->
// filter out request that was known before
!outgoingRequestsBefore.any { req.requestId == it.requestId }
}
.let {
val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
outGoingRequestId = outgoing?.requestId
outgoing != null
}
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
.filter { req ->
// filter out request that was known before
!outgoingRequestsBefore.any { req.requestId == it.requestId }
}
.let {
val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
outGoingRequestId = outgoing?.requestId
outgoing != null
}
}
}
mTestHelper.await(waitLatch)
Log.v("TEST", "=======> Outgoing requet Id is $outGoingRequestId")
val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
@ -134,8 +130,8 @@ class KeyShareTests : InstrumentedTest {
// The first session should see an incoming request
// the request should be refused, because the device is not trusted
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
// DEBUG LOGS
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
@ -164,8 +160,8 @@ class KeyShareTests : InstrumentedTest {
// Re request
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
Log.v("TEST", "Incoming request Session 1")
Log.v("TEST", "=========================")
@ -180,8 +176,8 @@ class KeyShareTests : InstrumentedTest {
}
Thread.sleep(6_000)
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession2.cryptoService().getOutgoingRoomKeyRequests().let {
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED }
}
@ -194,15 +190,15 @@ class KeyShareTests : InstrumentedTest {
fail("should have been able to decrypt")
}
mTestHelper.signOutAndClose(aliceSession)
mTestHelper.signOutAndClose(aliceSession2)
commonTestHelper.signOutAndClose(aliceSession)
commonTestHelper.signOutAndClose(aliceSession2)
}
@Test
fun test_ShareSSSSSecret() {
val aliceSession1 = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val aliceSession1 = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
mTestHelper.doSync<Unit> {
commonTestHelper.doSync<Unit> {
aliceSession1.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
@ -218,25 +214,25 @@ class KeyShareTests : InstrumentedTest {
}
// Also bootstrap keybackup on first session
val creationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
val creationInfo = commonTestHelper.doSync<MegolmBackupCreationInfo> {
aliceSession1.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
}
val version = mTestHelper.doSync<KeysVersion> {
val version = commonTestHelper.doSync<KeysVersion> {
aliceSession1.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
}
// Save it for gossiping
aliceSession1.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
val aliceSession2 = mTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true))
val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true))
val aliceVerificationService1 = aliceSession1.cryptoService().verificationService()
val aliceVerificationService2 = aliceSession2.cryptoService().verificationService()
// force keys download
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
commonTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
aliceSession1.cryptoService().downloadKeys(listOf(aliceSession1.myUserId), true, it)
}
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
commonTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
aliceSession2.cryptoService().downloadKeys(listOf(aliceSession2.myUserId), true, it)
}
@ -276,8 +272,8 @@ class KeyShareTests : InstrumentedTest {
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
?: "", txId)
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
}
}
@ -290,31 +286,31 @@ class KeyShareTests : InstrumentedTest {
// SSK and USK private keys should have been shared
mTestHelper.waitWithLatch(60_000) { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
commonTestHelper.waitWithLatch(60_000) { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
Log.d("#TEST", "CAN XS :${aliceSession2.cryptoService().crossSigningService().getMyCrossSigningKeys()}")
aliceSession2.cryptoService().crossSigningService().canCrossSign()
}
}
// Test that key backup key has been shared to
mTestHelper.waitWithLatch(60_000) { latch ->
commonTestHelper.waitWithLatch(60_000) { latch ->
val keysBackupService = aliceSession2.cryptoService().keysBackupService()
mTestHelper.retryPeriodicallyWithLatch(latch) {
commonTestHelper.retryPeriodicallyWithLatch(latch) {
Log.d("#TEST", "Recovery :${keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}")
keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey == creationInfo.recoveryKey
}
}
mTestHelper.signOutAndClose(aliceSession1)
mTestHelper.signOutAndClose(aliceSession2)
commonTestHelper.signOutAndClose(aliceSession1)
commonTestHelper.signOutAndClose(aliceSession2)
}
@Test
fun test_ImproperKeyShareBug() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
mTestHelper.doSync<Unit> {
commonTestHelper.doSync<Unit> {
aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
@ -331,7 +327,7 @@ class KeyShareTests : InstrumentedTest {
}
// Create an encrypted room and send a couple of messages
val roomId = mTestHelper.runBlockingTest {
val roomId = commonTestHelper.runBlockingTest {
aliceSession.createRoom(
CreateRoomParams().apply {
visibility = RoomDirectoryVisibility.PRIVATE
@ -343,12 +339,12 @@ class KeyShareTests : InstrumentedTest {
assertNotNull(roomAlicePov)
Thread.sleep(1_000)
assertTrue(roomAlicePov?.isEncrypted() == true)
val secondEventId = mTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId
val secondEventId = commonTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId
// Create bob session
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
mTestHelper.doSync<Unit> {
val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
commonTestHelper.doSync<Unit> {
bobSession.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
@ -365,11 +361,11 @@ class KeyShareTests : InstrumentedTest {
}
// Let alice invite bob
mTestHelper.runBlockingTest {
commonTestHelper.runBlockingTest {
roomAlicePov.invite(bobSession.myUserId, null)
}
mTestHelper.runBlockingTest {
commonTestHelper.runBlockingTest {
bobSession.joinRoom(roomAlicePov.roomId, null, emptyList())
}
@ -377,7 +373,7 @@ class KeyShareTests : InstrumentedTest {
aliceSession.cryptoService().discardOutboundSession(roomAlicePov.roomId)
// and now resend a new message to reset index to 0
mTestHelper.sendTextMessage(roomAlicePov, "After", 1)
commonTestHelper.sendTextMessage(roomAlicePov, "After", 1)
val roomRoomBobPov = aliceSession.getRoom(roomId)
val beforeJoin = roomRoomBobPov!!.getTimeLineEvent(secondEventId)

View file

@ -41,8 +41,8 @@ import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
@FixMethodOrder(MethodSorters.JVM)
class WithHeldTests : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
@Test
fun test_WithHeldUnverifiedReason() {
@ -50,19 +50,19 @@ class WithHeldTests : InstrumentedTest {
// ARRANGE
// =============================
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val bobSession = testHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
// Initialize cross signing on both
mCryptoTestHelper.initializeCrossSigning(aliceSession)
mCryptoTestHelper.initializeCrossSigning(bobSession)
cryptoTestHelper.initializeCrossSigning(aliceSession)
cryptoTestHelper.initializeCrossSigning(bobSession)
val roomId = mCryptoTestHelper.createDM(aliceSession, bobSession)
mCryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, roomId)
val roomId = cryptoTestHelper.createDM(aliceSession, bobSession)
cryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, roomId)
val roomAlicePOV = aliceSession.getRoom(roomId)!!
val bobUnverifiedSession = mTestHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
val bobUnverifiedSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
// =============================
// ACT
@ -71,11 +71,11 @@ class WithHeldTests : InstrumentedTest {
// Alice decide to not send to unverified sessions
aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(true)
val timelineEvent = mTestHelper.sendTextMessage(roomAlicePOV, "Hello Bob", 1).first()
val timelineEvent = testHelper.sendTextMessage(roomAlicePOV, "Hello Bob", 1).first()
// await for bob unverified session to get the message
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(timelineEvent.eventId) != null
}
}
@ -101,10 +101,10 @@ class WithHeldTests : InstrumentedTest {
// enable back sending to unverified
aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(false)
val secondEvent = mTestHelper.sendTextMessage(roomAlicePOV, "Verify your device!!", 1).first()
val secondEvent = testHelper.sendTextMessage(roomAlicePOV, "Verify your device!!", 1).first()
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val ev = bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(secondEvent.eventId)
// wait until it's decrypted
ev?.root?.getClearType() == EventType.MESSAGE
@ -123,17 +123,17 @@ class WithHeldTests : InstrumentedTest {
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
}
mTestHelper.signOutAndClose(aliceSession)
mTestHelper.signOutAndClose(bobSession)
mTestHelper.signOutAndClose(bobUnverifiedSession)
testHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(bobSession)
testHelper.signOutAndClose(bobUnverifiedSession)
}
@Test
fun test_WithHeldNoOlm() {
val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = testData.firstSession
val bobSession = testData.secondSession!!
val aliceInterceptor = mTestHelper.getTestInterceptor(aliceSession)
val aliceInterceptor = testHelper.getTestInterceptor(aliceSession)
// Simulate no OTK
aliceInterceptor!!.addRule(MockOkHttpInterceptor.SimpleRule(
@ -147,11 +147,11 @@ class WithHeldTests : InstrumentedTest {
val roomAlicePov = aliceSession.getRoom(testData.roomId)!!
val eventId = mTestHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId
val eventId = testHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId
// await for bob session to get the message
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
bobSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId) != null
}
}
@ -177,14 +177,14 @@ class WithHeldTests : InstrumentedTest {
// Add a new device for bob
aliceInterceptor.clearRules()
val bobSecondSession = mTestHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(withInitialSync = true))
val bobSecondSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(withInitialSync = true))
// send a second message
val secondMessageId = mTestHelper.sendTextMessage(roomAlicePov, "second message", 1).first().eventId
val secondMessageId = testHelper.sendTextMessage(roomAlicePov, "second message", 1).first().eventId
// Check that the
// await for bob SecondSession session to get the message
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(secondMessageId) != null
}
}
@ -194,27 +194,27 @@ class WithHeldTests : InstrumentedTest {
Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2)
aliceInterceptor.clearRules()
testData.cleanUp(mTestHelper)
mTestHelper.signOutAndClose(bobSecondSession)
testData.cleanUp(testHelper)
testHelper.signOutAndClose(bobSecondSession)
}
@Test
fun test_WithHeldKeyRequest() {
val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = testData.firstSession
val bobSession = testData.secondSession!!
val roomAlicePov = aliceSession.getRoom(testData.roomId)!!
val eventId = mTestHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId
val eventId = testHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId
mTestHelper.signOutAndClose(bobSession)
testHelper.signOutAndClose(bobSession)
// Create a new session for bob
val bobSecondSession = mTestHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
val bobSecondSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
// initialize to force request keys if missing
mCryptoTestHelper.initializeCrossSigning(bobSecondSession)
cryptoTestHelper.initializeCrossSigning(bobSecondSession)
// Trust bob second device from Alice POV
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!, NoOpMatrixCallback())
@ -223,8 +223,8 @@ class WithHeldTests : InstrumentedTest {
var sessionId: String? = null
// Check that the
// await for bob SecondSession session to get the message
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId)?.also {
// try to decrypt and force key request
tryOrNull { bobSecondSession.cryptoService().decryptEvent(it.root, "") }
@ -235,8 +235,8 @@ class WithHeldTests : InstrumentedTest {
}
// Check that bob second session requested the key
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val wc = bobSecondSession.cryptoService().getWithHeldMegolmSession(roomAlicePov.roomId, sessionId!!)
wc?.code == WithHeldCode.UNAUTHORISED
}

View file

@ -22,7 +22,6 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@ -43,7 +42,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreat
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import java.util.ArrayList
import java.util.Collections
import java.util.concurrent.CountDownLatch
@ -51,9 +49,9 @@ import java.util.concurrent.CountDownLatch
@FixMethodOrder(MethodSorters.JVM)
class KeysBackupTest : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
private val mKeysBackupTestHelper = KeysBackupTestHelper(mTestHelper, mCryptoTestHelper)
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
private val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
/**
* - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
@ -62,7 +60,7 @@ class KeysBackupTest : InstrumentedTest {
*/
@Test
fun roomKeysTest_testBackupStore_ok() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
// From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
@ -92,7 +90,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
/**
@ -100,7 +98,7 @@ class KeysBackupTest : InstrumentedTest {
*/
@Test
fun prepareKeysBackupVersionTest() {
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
assertNotNull(bobSession.cryptoService().keysBackupService())
@ -110,7 +108,7 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled)
val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
keysBackup.prepareKeysBackupVersion(null, null, it)
}
@ -120,7 +118,7 @@ class KeysBackupTest : InstrumentedTest {
assertNotNull(megolmBackupCreationInfo.recoveryKey)
stateObserver.stopAndCheckStates(null)
mTestHelper.signOutAndClose(bobSession)
testHelper.signOutAndClose(bobSession)
}
/**
@ -128,7 +126,7 @@ class KeysBackupTest : InstrumentedTest {
*/
@Test
fun createKeysBackupVersionTest() {
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
val keysBackup = bobSession.cryptoService().keysBackupService()
@ -136,14 +134,14 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled)
val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
keysBackup.prepareKeysBackupVersion(null, null, it)
}
assertFalse(keysBackup.isEnabled)
// Create the version
mTestHelper.doSync<KeysVersion> {
testHelper.doSync<KeysVersion> {
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
}
@ -151,7 +149,7 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(keysBackup.isEnabled)
stateObserver.stopAndCheckStates(null)
mTestHelper.signOutAndClose(bobSession)
testHelper.signOutAndClose(bobSession)
}
/**
@ -160,8 +158,9 @@ class KeysBackupTest : InstrumentedTest {
*/
@Test
fun backupAfterCreateKeysBackupVersionTest() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
keysBackupTestHelper.waitForKeybackUpBatching()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
val latch = CountDownLatch(1)
@ -171,9 +170,9 @@ class KeysBackupTest : InstrumentedTest {
val stateObserver = StateObserver(keysBackup, latch, 5)
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
mTestHelper.await(latch)
testHelper.await(latch)
val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
@ -191,7 +190,7 @@ class KeysBackupTest : InstrumentedTest {
KeysBackupState.ReadyToBackUp
)
)
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
/**
@ -199,13 +198,13 @@ class KeysBackupTest : InstrumentedTest {
*/
@Test
fun backupAllGroupSessionsTest() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
val stateObserver = StateObserver(keysBackup)
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
// Check that backupAllGroupSessions returns valid data
val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
@ -214,7 +213,7 @@ class KeysBackupTest : InstrumentedTest {
var lastBackedUpKeysProgress = 0
mTestHelper.doSync<Unit> {
testHelper.doSync<Unit> {
keysBackup.backupAllGroupSessions(object : ProgressListener {
override fun onProgress(progress: Int, total: Int) {
assertEquals(nbOfKeys, total)
@ -230,7 +229,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
/**
@ -243,7 +242,7 @@ class KeysBackupTest : InstrumentedTest {
*/
@Test
fun testEncryptAndDecryptKeysBackupData() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService
@ -252,7 +251,7 @@ class KeysBackupTest : InstrumentedTest {
// - Pick a megolm key
val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0]
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
// - Check encryptGroupSession() returns stg
val keyBackupData = keysBackup.encryptGroupSession(session)
@ -270,10 +269,10 @@ class KeysBackupTest : InstrumentedTest {
decryption!!)
assertNotNull(sessionData)
// - Compare the decrypted megolm key with the original one
mKeysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData)
keysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData)
stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
/**
@ -284,10 +283,10 @@ class KeysBackupTest : InstrumentedTest {
*/
@Test
fun restoreKeysBackupTest() {
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
// - Restore the e2e backup from the homeserver
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> {
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
null,
@ -297,9 +296,9 @@ class KeysBackupTest : InstrumentedTest {
)
}
mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
testData.cleanUp(mTestHelper)
testData.cleanUp(testHelper)
}
/**
@ -369,7 +368,7 @@ class KeysBackupTest : InstrumentedTest {
fun trustKeyBackupVersionTest() {
// - Do an e2e backup to the homeserver with a recovery key
// - And log Alice on a new device
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
@ -379,7 +378,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
// - Trust the backup from the new device
mTestHelper.doSync<Unit> {
testHelper.doSync<Unit> {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
true,
@ -388,21 +387,21 @@ class KeysBackupTest : InstrumentedTest {
}
// Wait for backup state to be ReadyToBackUp
mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
// - Backup must be enabled on the new device, on the same version
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
// - Retrieve the last version from the server
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
val keysVersionResult = testHelper.doSync<KeysVersionResult?> {
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
}
// - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
}
@ -411,7 +410,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, keysBackupVersionTrust.signatures.size)
stateObserver.stopAndCheckStates(null)
testData.cleanUp(mTestHelper)
testData.cleanUp(testHelper)
}
/**
@ -428,7 +427,7 @@ class KeysBackupTest : InstrumentedTest {
fun trustKeyBackupVersionWithRecoveryKeyTest() {
// - Do an e2e backup to the homeserver with a recovery key
// - And log Alice on a new device
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
@ -438,7 +437,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
// - Trust the backup from the new device with the recovery key
mTestHelper.doSync<Unit> {
testHelper.doSync<Unit> {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
@ -447,21 +446,21 @@ class KeysBackupTest : InstrumentedTest {
}
// Wait for backup state to be ReadyToBackUp
mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
// - Backup must be enabled on the new device, on the same version
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
// - Retrieve the last version from the server
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
val keysVersionResult = testHelper.doSync<KeysVersionResult?> {
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
}
// - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
}
@ -470,7 +469,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, keysBackupVersionTrust.signatures.size)
stateObserver.stopAndCheckStates(null)
testData.cleanUp(mTestHelper)
testData.cleanUp(testHelper)
}
/**
@ -485,7 +484,7 @@ class KeysBackupTest : InstrumentedTest {
fun trustKeyBackupVersionWithWrongRecoveryKeyTest() {
// - Do an e2e backup to the homeserver with a recovery key
// - And log Alice on a new device
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
@ -501,7 +500,7 @@ class KeysBackupTest : InstrumentedTest {
"Bad recovery key",
TestMatrixCallback(latch, false)
)
mTestHelper.await(latch)
testHelper.await(latch)
// - The new device must still see the previous backup as not trusted
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
@ -509,7 +508,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
stateObserver.stopAndCheckStates(null)
testData.cleanUp(mTestHelper)
testData.cleanUp(testHelper)
}
/**
@ -528,7 +527,7 @@ class KeysBackupTest : InstrumentedTest {
// - Do an e2e backup to the homeserver with a password
// - And log Alice on a new device
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
@ -538,7 +537,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
// - Trust the backup from the new device with the password
mTestHelper.doSync<Unit> {
testHelper.doSync<Unit> {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
password,
@ -547,21 +546,21 @@ class KeysBackupTest : InstrumentedTest {
}
// Wait for backup state to be ReadyToBackUp
mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
// - Backup must be enabled on the new device, on the same version
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
// - Retrieve the last version from the server
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
val keysVersionResult = testHelper.doSync<KeysVersionResult?> {
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
}
// - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
}
@ -570,7 +569,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, keysBackupVersionTrust.signatures.size)
stateObserver.stopAndCheckStates(null)
testData.cleanUp(mTestHelper)
testData.cleanUp(testHelper)
}
/**
@ -588,7 +587,7 @@ class KeysBackupTest : InstrumentedTest {
// - Do an e2e backup to the homeserver with a password
// - And log Alice on a new device
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
@ -604,7 +603,7 @@ class KeysBackupTest : InstrumentedTest {
badPassword,
TestMatrixCallback(latch, false)
)
mTestHelper.await(latch)
testHelper.await(latch)
// - The new device must still see the previous backup as not trusted
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
@ -612,7 +611,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
stateObserver.stopAndCheckStates(null)
testData.cleanUp(mTestHelper)
testData.cleanUp(testHelper)
}
/**
@ -623,7 +622,7 @@ class KeysBackupTest : InstrumentedTest {
*/
@Test
fun restoreKeysBackupWithAWrongRecoveryKeyTest() {
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
// - Try to restore the e2e backup with a wrong recovery key
val latch2 = CountDownLatch(1)
@ -640,12 +639,12 @@ class KeysBackupTest : InstrumentedTest {
}
}
)
mTestHelper.await(latch2)
testHelper.await(latch2)
// onSuccess may not have been called
assertNull(importRoomKeysResult)
testData.cleanUp(mTestHelper)
testData.cleanUp(testHelper)
}
/**
@ -658,12 +657,12 @@ class KeysBackupTest : InstrumentedTest {
fun testBackupWithPassword() {
val password = "password"
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
// - Restore the e2e backup with the password
val steps = ArrayList<StepProgressListener.Step>()
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> {
testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
password,
null,
@ -698,9 +697,9 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress)
assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress)
mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
testData.cleanUp(mTestHelper)
testData.cleanUp(testHelper)
}
/**
@ -714,7 +713,7 @@ class KeysBackupTest : InstrumentedTest {
val password = "password"
val wrongPassword = "passw0rd"
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
// - Try to restore the e2e backup with a wrong password
val latch2 = CountDownLatch(1)
@ -731,12 +730,12 @@ class KeysBackupTest : InstrumentedTest {
}
}
)
mTestHelper.await(latch2)
testHelper.await(latch2)
// onSuccess may not have been called
assertNull(importRoomKeysResult)
testData.cleanUp(mTestHelper)
testData.cleanUp(testHelper)
}
/**
@ -749,10 +748,10 @@ class KeysBackupTest : InstrumentedTest {
fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() {
val password = "password"
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
// - Restore the e2e backup with the recovery key.
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> {
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
null,
@ -762,9 +761,9 @@ class KeysBackupTest : InstrumentedTest {
)
}
mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
testData.cleanUp(mTestHelper)
testData.cleanUp(testHelper)
}
/**
@ -775,7 +774,7 @@ class KeysBackupTest : InstrumentedTest {
*/
@Test
fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() {
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
// - Try to restore the e2e backup with a password
val latch2 = CountDownLatch(1)
@ -792,12 +791,12 @@ class KeysBackupTest : InstrumentedTest {
}
}
)
mTestHelper.await(latch2)
testHelper.await(latch2)
// onSuccess may not have been called
assertNull(importRoomKeysResult)
testData.cleanUp(mTestHelper)
testData.cleanUp(testHelper)
}
/**
@ -807,22 +806,22 @@ class KeysBackupTest : InstrumentedTest {
@Test
fun testIsKeysBackupTrusted() {
// - Create a backup version
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
val stateObserver = StateObserver(keysBackup)
// - Do an e2e backup to the homeserver
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
// Get key backup version from the homeserver
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
val keysVersionResult = testHelper.doSync<KeysVersionResult?> {
keysBackup.getCurrentVersion(it)
}
// - Check the returned KeyBackupVersion is trusted
val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
keysBackup.getKeysBackupTrust(keysVersionResult!!, it)
}
@ -837,7 +836,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
/**
@ -849,9 +848,8 @@ class KeysBackupTest : InstrumentedTest {
*/
@Test
fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() {
fail("This test still fail. To investigate")
// - Create a backup version
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
@ -859,15 +857,15 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled)
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
assertTrue(keysBackup.isEnabled)
// - Restart alice session
// - Log Alice on a new device
val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
val aliceSession2 = testHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
val keysBackup2 = aliceSession2.cryptoService().keysBackupService()
@ -891,13 +889,13 @@ class KeysBackupTest : InstrumentedTest {
}
}
})
mTestHelper.await(latch)
testHelper.await(latch)
assertEquals(keyBackupCreationInfo.version, keysBackup2.currentBackupVersion)
stateObserver.stopAndCheckStates(null)
stateObserver2.stopAndCheckStates(null)
mTestHelper.signOutAndClose(aliceSession2)
testHelper.signOutAndClose(aliceSession2)
}
/**
@ -911,7 +909,7 @@ class KeysBackupTest : InstrumentedTest {
@Test
fun testBackupWhenAnotherBackupWasCreated() {
// - Create a backup version
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
@ -939,15 +937,15 @@ class KeysBackupTest : InstrumentedTest {
})
// - Make alice back up her keys to her homeserver
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
assertTrue(keysBackup.isEnabled)
mTestHelper.await(latch0)
testHelper.await(latch0)
// - Create a new backup with fake data on the homeserver, directly using the rest client
val megolmBackupCreationInfo = mCryptoTestHelper.createFakeMegolmBackupCreationInfo()
mTestHelper.doSync<KeysVersion> {
val megolmBackupCreationInfo = cryptoTestHelper.createFakeMegolmBackupCreationInfo()
testHelper.doSync<KeysVersion> {
(keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, it)
}
@ -957,14 +955,14 @@ class KeysBackupTest : InstrumentedTest {
// - Make alice back up all her keys again
val latch2 = CountDownLatch(1)
keysBackup.backupAllGroupSessions(null, TestMatrixCallback(latch2, false))
mTestHelper.await(latch2)
testHelper.await(latch2)
// -> That must fail and her backup state must be WrongBackUpVersion
assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.state)
assertFalse(keysBackup.isEnabled)
stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
/**
@ -982,17 +980,17 @@ class KeysBackupTest : InstrumentedTest {
@Test
fun testBackupAfterVerifyingADevice() {
// - Create a backup version
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
val stateObserver = StateObserver(keysBackup)
// - Make alice back up her keys to her homeserver
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
// Wait for keys backup to finish by asking again to backup keys.
mTestHelper.doSync<Unit> {
testHelper.doSync<Unit> {
keysBackup.backupAllGroupSessions(null, it)
}
@ -1001,14 +999,14 @@ class KeysBackupTest : InstrumentedTest {
val aliceUserId = cryptoTestData.firstSession.myUserId
// - Log Alice on a new device
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
// - Post a message to have a new megolm session
aliceSession2.cryptoService().setWarnOnUnknownDevices(false)
val room2 = aliceSession2.getRoom(cryptoTestData.roomId)!!
mTestHelper.sendTextMessage(room2, "New key", 1)
testHelper.sendTextMessage(room2, "New key", 1)
// - Try to backup all in aliceSession2, it must fail
val keysBackup2 = aliceSession2.cryptoService().keysBackupService()
@ -1025,7 +1023,7 @@ class KeysBackupTest : InstrumentedTest {
super.onSuccess(data)
}
})
mTestHelper.await(latch2)
testHelper.await(latch2)
assertFalse(isSuccessful)
@ -1049,12 +1047,12 @@ class KeysBackupTest : InstrumentedTest {
}
}
})
mTestHelper.await(latch4)
testHelper.await(latch4)
// -> It must use the same backup version
assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion)
mTestHelper.doSync<Unit> {
testHelper.doSync<Unit> {
aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it)
}
@ -1063,8 +1061,8 @@ class KeysBackupTest : InstrumentedTest {
stateObserver.stopAndCheckStates(null)
stateObserver2.stopAndCheckStates(null)
mTestHelper.signOutAndClose(aliceSession2)
cryptoTestData.cleanUp(mTestHelper)
testHelper.signOutAndClose(aliceSession2)
cryptoTestData.cleanUp(testHelper)
}
/**
@ -1074,7 +1072,7 @@ class KeysBackupTest : InstrumentedTest {
@Test
fun deleteKeysBackupTest() {
// - Create a backup version
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
@ -1082,17 +1080,17 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled)
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
assertTrue(keysBackup.isEnabled)
// Delete the backup
mTestHelper.doSync<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) }
testHelper.doSync<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) }
// Backup is now disabled
assertFalse(keysBackup.isEnabled)
stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
}

View file

@ -32,8 +32,12 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
import java.util.concurrent.CountDownLatch
class KeysBackupTestHelper(
private val mTestHelper: CommonTestHelper,
private val mCryptoTestHelper: CryptoTestHelper) {
private val testHelper: CommonTestHelper,
private val cryptoTestHelper: CryptoTestHelper) {
fun waitForKeybackUpBatching() {
Thread.sleep(400)
}
/**
* Common initial condition
@ -43,7 +47,9 @@ class KeysBackupTestHelper(
* @param password optional password
*/
fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
waitForKeybackUpBatching()
val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
@ -57,7 +63,7 @@ class KeysBackupTestHelper(
var lastProgress = 0
var lastTotal = 0
mTestHelper.doSync<Unit> {
testHelper.doSync<Unit> {
keysBackup.backupAllGroupSessions(object : ProgressListener {
override fun onProgress(progress: Int, total: Int) {
lastProgress = progress
@ -72,7 +78,7 @@ class KeysBackupTestHelper(
val aliceUserId = cryptoTestData.firstSession.myUserId
// - Log Alice on a new device
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
// Test check: aliceSession2 has no keys at login
Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false))
@ -92,7 +98,7 @@ class KeysBackupTestHelper(
password: String? = null): PrepareKeysBackupDataResult {
val stateObserver = StateObserver(keysBackup)
val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
keysBackup.prepareKeysBackupVersion(password, null, it)
}
@ -101,7 +107,7 @@ class KeysBackupTestHelper(
Assert.assertFalse(keysBackup.isEnabled)
// Create the version
val keysVersion = mTestHelper.doSync<KeysVersion> {
val keysVersion = testHelper.doSync<KeysVersion> {
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
}
@ -136,7 +142,7 @@ class KeysBackupTestHelper(
}
})
mTestHelper.await(latch)
testHelper.await(latch)
}
fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {

View file

@ -18,10 +18,6 @@ package org.matrix.android.sdk.internal.crypto.ssss
import androidx.lifecycle.Observer
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
@ -36,6 +32,7 @@ import org.matrix.android.sdk.api.session.securestorage.EncryptedSecretContent
import org.matrix.android.sdk.api.session.securestorage.KeySigner
import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
import org.matrix.android.sdk.api.session.securestorage.SecretStorageKeyContent
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageError
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
import org.matrix.android.sdk.api.util.Optional
@ -45,13 +42,12 @@ import org.matrix.android.sdk.common.TestConstants
import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class QuadSTests : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val testHelper = CommonTestHelper(context())
private val emptyKeySigner = object : KeySigner {
override fun sign(canonicalJson: String): Map<String, Map<String, String>>? {
@ -60,35 +56,29 @@ class QuadSTests : InstrumentedTest {
}
@Test
@Suppress("EXPERIMENTAL_API_USAGE")
fun test_Generate4SKey() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val quadS = aliceSession.sharedSecretStorageService
val TEST_KEY_ID = "my.test.Key"
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner)
}
// Assert Account data is updated
val accountDataLock = CountDownLatch(1)
var accountData: UserAccountDataEvent? = null
val liveAccountData = runBlocking(Dispatchers.Main) {
aliceSession.accountDataService().getLiveUserAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
}
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") {
accountData = t.getOrNull()
accountDataLock.countDown()
// Assert Account data is updated
testHelper.waitWithLatch {
val liveAccountData = aliceSession.accountDataService().getLiveUserAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") {
accountData = t.getOrNull()
}
it.countDown()
}
liveAccountData.observeForever(accountDataObserver)
}
GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) }
mTestHelper.await(accountDataLock)
assertNotNull("Key should be stored in account data", accountData)
val parsed = SecretStorageKeyContent.fromJson(accountData!!.content)
assertNotNull("Key Content cannot be parsed", parsed)
@ -96,36 +86,29 @@ class QuadSTests : InstrumentedTest {
assertEquals("Unexpected key name", "Test Key", parsed.name)
assertNull("Key was not generated from passphrase", parsed.passphrase)
// Set as default key
GlobalScope.launch {
quadS.setDefaultKey(TEST_KEY_ID)
}
var defaultKeyAccountData: UserAccountDataEvent? = null
val defaultDataLock = CountDownLatch(1)
val liveDefAccountData = runBlocking(Dispatchers.Main) {
aliceSession.accountDataService().getLiveUserAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
}
val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) {
defaultKeyAccountData = t.getOrNull()!!
defaultDataLock.countDown()
// Set as default key
testHelper.waitWithLatch { latch ->
quadS.setDefaultKey(TEST_KEY_ID)
val liveDefAccountData =
aliceSession.accountDataService().getLiveUserAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) {
defaultKeyAccountData = t.getOrNull()!!
latch.countDown()
}
}
liveDefAccountData.observeForever(accountDefDataObserver)
}
GlobalScope.launch(Dispatchers.Main) { liveDefAccountData.observeForever(accountDefDataObserver) }
mTestHelper.await(defaultDataLock)
assertNotNull(defaultKeyAccountData?.content)
assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key"))
mTestHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(aliceSession)
}
@Test
fun test_StoreSecret() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val keyId = "My.Key"
val info = generatedSecret(aliceSession, keyId, true)
@ -133,7 +116,7 @@ class QuadSTests : InstrumentedTest {
// Store a secret
val clearSecret = "42".toByteArray().toBase64NoPadding()
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.storeSecret(
"secret.of.life",
clearSecret,
@ -154,7 +137,7 @@ class QuadSTests : InstrumentedTest {
// Try to decrypt??
val decryptedSecret = mTestHelper.runBlockingTest {
val decryptedSecret = testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.getSecret(
"secret.of.life",
null, // default key
@ -163,32 +146,32 @@ class QuadSTests : InstrumentedTest {
}
assertEquals("Secret mismatch", clearSecret, decryptedSecret)
mTestHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(aliceSession)
}
@Test
fun test_SetDefaultLocalEcho() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val quadS = aliceSession.sharedSecretStorageService
val TEST_KEY_ID = "my.test.Key"
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner)
}
// Test that we don't need to wait for an account data sync to access directly the keyid from DB
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
quadS.setDefaultKey(TEST_KEY_ID)
}
mTestHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(aliceSession)
}
@Test
fun test_StoreSecretWithMultipleKey() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val keyId1 = "Key.1"
val key1Info = generatedSecret(aliceSession, keyId1, true)
val keyId2 = "Key2"
@ -196,7 +179,7 @@ class QuadSTests : InstrumentedTest {
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.storeSecret(
"my.secret",
mySecretText.toByteArray().toBase64NoPadding(),
@ -216,33 +199,33 @@ class QuadSTests : InstrumentedTest {
assertNotNull(encryptedContent?.get(keyId2))
// Assert that can decrypt with both keys
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId1,
RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!
)
}
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId2,
RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!
)
}
mTestHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(aliceSession)
}
@Test
fun test_GetSecretWithBadPassphrase() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val keyId1 = "Key.1"
val passphrase = "The good pass phrase"
val key1Info = generatedSecretFromPassphrase(aliceSession, passphrase, keyId1, true)
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.storeSecret(
"my.secret",
mySecretText.toByteArray().toBase64NoPadding(),
@ -250,19 +233,23 @@ class QuadSTests : InstrumentedTest {
)
}
mTestHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId1,
RawBytesKeySpec.fromPassphrase(
"A bad passphrase",
key1Info.content?.passphrase?.salt ?: "",
key1Info.content?.passphrase?.iterations ?: 0,
null)
)
testHelper.runBlockingTest {
try {
aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId1,
RawBytesKeySpec.fromPassphrase(
"A bad passphrase",
key1Info.content?.passphrase?.salt ?: "",
key1Info.content?.passphrase?.iterations ?: 0,
null)
)
} catch (throwable: Throwable) {
assert(throwable is SharedSecretStorageError.BadMac)
}
}
// Now try with correct key
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId1,
RawBytesKeySpec.fromPassphrase(
@ -273,42 +260,36 @@ class QuadSTests : InstrumentedTest {
)
}
mTestHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(aliceSession)
}
@Suppress("EXPERIMENTAL_API_USAGE")
private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
val accountDataLock = CountDownLatch(1)
var accountData: UserAccountDataEvent? = null
val liveAccountData = runBlocking(Dispatchers.Main) {
session.accountDataService().getLiveUserAccountDataEvent(type)
}
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
if (t?.getOrNull()?.type == type) {
accountData = t.getOrNull()
accountDataLock.countDown()
testHelper.waitWithLatch {
val liveAccountData = session.accountDataService().getLiveUserAccountDataEvent(type)
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
if (t?.getOrNull()?.type == type) {
accountData = t.getOrNull()
it.countDown()
}
}
liveAccountData.observeForever(accountDataObserver)
}
GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) }
mTestHelper.await(accountDataLock)
assertNotNull("Account Data type:$type should be found", accountData)
return accountData!!
}
private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
val quadS = session.sharedSecretStorageService
val creationInfo = mTestHelper.runBlockingTest {
val creationInfo = testHelper.runBlockingTest {
quadS.generateKey(keyId, null, keyId, emptyKeySigner)
}
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
if (asDefault) {
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
quadS.setDefaultKey(keyId)
}
assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
@ -320,7 +301,7 @@ class QuadSTests : InstrumentedTest {
private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
val quadS = session.sharedSecretStorageService
val creationInfo = mTestHelper.runBlockingTest {
val creationInfo = testHelper.runBlockingTest {
quadS.generateKeyWithPassphrase(
keyId,
keyId,
@ -331,7 +312,7 @@ class QuadSTests : InstrumentedTest {
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
if (asDefault) {
mTestHelper.runBlockingTest {
testHelper.runBlockingTest {
quadS.setDefaultKey(keyId)
}
assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)

View file

@ -53,12 +53,12 @@ import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class SASTest : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
@Test
fun test_aliceStartThenAliceCancel() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
@ -83,7 +83,7 @@ class SASTest : InstrumentedTest {
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
assertNotNull("Alice should have a started transaction", aliceKeyTx)
mTestHelper.await(bobTxCreatedLatch)
testHelper.await(bobTxCreatedLatch)
bobVerificationService.removeListener(bobListener)
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
@ -116,7 +116,7 @@ class SASTest : InstrumentedTest {
bobVerificationService.addListener(bobListener2)
aliceSasTx.cancel(CancelCode.User)
mTestHelper.await(cancelLatch)
testHelper.await(cancelLatch)
assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled)
assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled)
@ -133,13 +133,13 @@ class SASTest : InstrumentedTest {
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
@Test
fun test_key_agreement_protocols_must_include_curve25519() {
fail("Not passing for the moment")
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
@ -186,17 +186,17 @@ class SASTest : InstrumentedTest {
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
mTestHelper.await(cancelLatch)
testHelper.await(cancelLatch)
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
@Test
fun test_key_agreement_macs_Must_include_hmac_sha256() {
fail("Not passing for the moment")
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
@ -223,18 +223,18 @@ class SASTest : InstrumentedTest {
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
mTestHelper.await(cancelLatch)
testHelper.await(cancelLatch)
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
@Test
fun test_key_agreement_short_code_include_decimal() {
fail("Not passing for the moment")
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
@ -261,12 +261,12 @@ class SASTest : InstrumentedTest {
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
mTestHelper.await(cancelLatch)
testHelper.await(cancelLatch)
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
private fun fakeBobStart(bobSession: Session,
@ -303,7 +303,7 @@ class SASTest : InstrumentedTest {
// If a device has two verifications in progress with the same device, then it should cancel both verifications.
@Test
fun test_aliceStartTwoRequests() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
@ -332,10 +332,10 @@ class SASTest : InstrumentedTest {
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
mTestHelper.await(aliceCreatedLatch)
mTestHelper.await(aliceCancelledLatch)
testHelper.await(aliceCreatedLatch)
testHelper.await(aliceCancelledLatch)
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
/**
@ -343,7 +343,7 @@ class SASTest : InstrumentedTest {
*/
@Test
fun test_aliceAndBobAgreement() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
@ -383,7 +383,7 @@ class SASTest : InstrumentedTest {
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
mTestHelper.await(aliceAcceptedLatch)
testHelper.await(aliceAcceptedLatch)
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
@ -397,12 +397,12 @@ class SASTest : InstrumentedTest {
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
}
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
@Test
fun test_aliceAndBobSASCode() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
@ -444,8 +444,8 @@ class SASTest : InstrumentedTest {
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
mTestHelper.await(aliceSASLatch)
mTestHelper.await(bobSASLatch)
testHelper.await(aliceSASLatch)
testHelper.await(bobSASLatch)
val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
@ -453,12 +453,12 @@ class SASTest : InstrumentedTest {
assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
@Test
fun test_happyPath() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
@ -520,8 +520,8 @@ class SASTest : InstrumentedTest {
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
mTestHelper.await(aliceSASLatch)
mTestHelper.await(bobSASLatch)
testHelper.await(aliceSASLatch)
testHelper.await(bobSASLatch)
// Assert that devices are verified
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId)
@ -532,12 +532,12 @@ class SASTest : InstrumentedTest {
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
@Test
fun test_ConcurrentStart() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
@ -553,8 +553,8 @@ class SASTest : InstrumentedTest {
var requestID: String? = null
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
requestID = prAlicePOV?.transactionId
Log.v("TEST", "== alicePOV is $prAlicePOV")
@ -564,8 +564,8 @@ class SASTest : InstrumentedTest {
Log.v("TEST", "== requestID is $requestID")
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
val prBobPOV = bobVerificationService.getExistingVerificationRequests(aliceSession.myUserId).firstOrNull()
Log.v("TEST", "== prBobPOV is $prBobPOV")
prBobPOV?.transactionId == requestID
@ -579,8 +579,8 @@ class SASTest : InstrumentedTest {
)
// wait for alice to get the ready
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null
@ -606,22 +606,22 @@ class SASTest : InstrumentedTest {
var alicePovTx: SasVerificationTransaction?
var bobPovTx: SasVerificationTransaction?
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction
Log.v("TEST", "== alicePovTx is $alicePovTx")
alicePovTx?.state == VerificationTxState.ShortCodeReady
}
}
// wait for alice to get the ready
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction
Log.v("TEST", "== bobPovTx is $bobPovTx")
bobPovTx?.state == VerificationTxState.ShortCodeReady
}
}
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
}

View file

@ -40,8 +40,8 @@ import kotlin.coroutines.resume
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class VerificationTest : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
data class ExpectedResult(
val sasIsSupported: Boolean = false,
@ -155,12 +155,12 @@ class VerificationTest : InstrumentedTest {
bobSupportedMethods: List<VerificationMethod>,
expectedResultForAlice: ExpectedResult,
expectedResultForBob: ExpectedResult) {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
mTestHelper.doSync<Unit> { callback ->
testHelper.doSync<Unit> { callback ->
aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
@ -176,7 +176,7 @@ class VerificationTest : InstrumentedTest {
}, callback)
}
mTestHelper.doSync<Unit> { callback ->
testHelper.doSync<Unit> { callback ->
bobSession.cryptoService().crossSigningService()
.initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
@ -234,7 +234,7 @@ class VerificationTest : InstrumentedTest {
val bobUserId = bobSession.myUserId
// Step 1: Alice starts a verification request
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
mTestHelper.await(latch)
testHelper.await(latch)
aliceReadyPendingVerificationRequest!!.let { pr ->
pr.isSasSupported() shouldBe expectedResultForAlice.sasIsSupported
@ -248,6 +248,6 @@ class VerificationTest : InstrumentedTest {
pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode
}
cryptoTestData.cleanUp(mTestHelper)
cryptoTestData.cleanUp(testHelper)
}
}

View file

@ -45,7 +45,7 @@ object RoomDataHelper {
content: Content? = null,
prevContent: Content? = null,
sender: String = FAKE_TEST_SENDER,
stateKey: String = FAKE_TEST_SENDER
stateKey: String? = null
): Event {
return Event(
type = type,
@ -64,6 +64,6 @@ object RoomDataHelper {
private fun createFakeRoomMemberEvent(): Event {
val roomMember = RoomMemberContent(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember, stateKey = FAKE_TEST_SENDER)
}
}

View file

@ -65,14 +65,8 @@ class TimelineForwardPaginationTest : InstrumentedTest {
message,
numberOfMessagesToSend)
// Alice clear the cache
commonTestHelper.runBlockingTest {
aliceSession.clearCache()
}
// And restarts the sync
aliceSession.startSync(true)
// Alice clear the cache and restart the sync
commonTestHelper.clearCacheAndSync(aliceSession)
val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30))
aliceTimeline.start()

View file

@ -24,14 +24,10 @@ import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.api.session.search.SearchResult
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestData
import org.matrix.android.sdk.common.CryptoTestHelper
import java.util.concurrent.CountDownLatch
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@ -84,24 +80,12 @@ class SearchMessagesTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession
val aliceRoomId = cryptoTestData.roomId
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(10))
aliceTimeline.start()
val lock = CountDownLatch(1)
val eventListener = commonTestHelper.createEventListener(lock) { snapshot ->
snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2
}
aliceTimeline.addListener(eventListener)
commonTestHelper.sendTextMessage(
roomFromAlicePOV,
MESSAGE,
2)
commonTestHelper.await(lock)
val data = commonTestHelper.runBlockingTest {
block.invoke(cryptoTestData)
}
@ -114,7 +98,6 @@ class SearchMessagesTest : InstrumentedTest {
}.orFalse()
)
aliceTimeline.removeAllListeners()
cryptoTestData.cleanUp(commonTestHelper)
}
}

View file

@ -16,9 +16,7 @@
package org.matrix.android.sdk.session.space
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
@ -50,18 +48,15 @@ class SpaceCreationTest : InstrumentedTest {
private val commonTestHelper = CommonTestHelper(context())
@Test
@Suppress("EXPERIMENTAL_API_USAGE")
fun createSimplePublicSpace() {
val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true))
val roomName = "My Space"
val topic = "A public space for test"
var spaceId: String = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
spaceId = session.spaceService().createSpace(roomName, topic, null, true)
// wait a bit to let the summary update it self :/
it.countDown()
}
spaceId = session.spaceService().createSpace(roomName, topic, null, true)
// wait a bit to let the summary update it self :/
it.countDown()
}
val syncedSpace = session.spaceService().getSpace(spaceId)
@ -134,7 +129,6 @@ class SpaceCreationTest : InstrumentedTest {
}
@Test
@Suppress("EXPERIMENTAL_API_USAGE")
fun testSimplePublicSpaceWithChildren() {
val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true))
@ -148,50 +142,40 @@ class SpaceCreationTest : InstrumentedTest {
// create a room
var firstChild: String? = null
commonTestHelper.waitWithLatch {
GlobalScope.launch {
firstChild = aliceSession.createRoom(CreateRoomParams().apply {
this.name = "FirstRoom"
this.topic = "Description of first room"
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
})
it.countDown()
}
firstChild = aliceSession.createRoom(CreateRoomParams().apply {
this.name = "FirstRoom"
this.topic = "Description of first room"
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
})
it.countDown()
}
commonTestHelper.waitWithLatch {
GlobalScope.launch {
syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", suggested = true)
it.countDown()
}
syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", suggested = true)
it.countDown()
}
var secondChild: String? = null
commonTestHelper.waitWithLatch {
GlobalScope.launch {
secondChild = aliceSession.createRoom(CreateRoomParams().apply {
this.name = "SecondRoom"
this.topic = "Description of second room"
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
})
it.countDown()
}
secondChild = aliceSession.createRoom(CreateRoomParams().apply {
this.name = "SecondRoom"
this.topic = "Description of second room"
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
})
it.countDown()
}
commonTestHelper.waitWithLatch {
GlobalScope.launch {
syncedSpace?.addChildren(secondChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", suggested = true)
it.countDown()
}
syncedSpace?.addChildren(secondChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", suggested = true)
it.countDown()
}
// Try to join from bob, it's a public space no need to invite
var joinResult: JoinSpaceResult? = null
commonTestHelper.waitWithLatch {
GlobalScope.launch {
joinResult = bobSession.spaceService().joinSpace(spaceId)
// wait a bit to let the summary update it self :/
it.countDown()
}
joinResult = bobSession.spaceService().joinSpace(spaceId)
// wait a bit to let the summary update it self :/
it.countDown()
}
assertEquals(JoinSpaceResult.Success, joinResult)

View file

@ -18,9 +18,6 @@ package org.matrix.android.sdk.session.space
import android.util.Log
import androidx.lifecycle.Observer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
@ -56,43 +53,34 @@ class SpaceHierarchyTest : InstrumentedTest {
private val commonTestHelper = CommonTestHelper(context())
@Test
@Suppress("EXPERIMENTAL_API_USAGE")
fun createCanonicalChildRelation() {
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
val spaceName = "My Space"
val topic = "A public space for test"
var spaceId: String = ""
var spaceId = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
it.countDown()
}
spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
it.countDown()
}
val syncedSpace = session.spaceService().getSpace(spaceId)
var roomId: String = ""
var roomId = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
roomId = session.createRoom(CreateRoomParams().apply { name = "General" })
it.countDown()
}
roomId = session.createRoom(CreateRoomParams().apply { name = "General" })
it.countDown()
}
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
commonTestHelper.waitWithLatch {
GlobalScope.launch {
syncedSpace!!.addChildren(roomId, viaServers, null, true)
it.countDown()
}
syncedSpace!!.addChildren(roomId, viaServers, null, true)
it.countDown()
}
commonTestHelper.waitWithLatch {
GlobalScope.launch {
session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers)
it.countDown()
}
session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers)
it.countDown()
}
Thread.sleep(9000)
@ -181,7 +169,6 @@ class SpaceHierarchyTest : InstrumentedTest {
// }
@Test
@Suppress("EXPERIMENTAL_API_USAGE")
fun testFilteringBySpace() {
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
@ -205,29 +192,23 @@ class SpaceHierarchyTest : InstrumentedTest {
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
commonTestHelper.waitWithLatch {
GlobalScope.launch {
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
it.countDown()
}
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
it.countDown()
}
// Create orphan rooms
var orphan1 = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
orphan1 = session.createRoom(CreateRoomParams().apply { name = "O1" })
it.countDown()
}
orphan1 = session.createRoom(CreateRoomParams().apply { name = "O1" })
it.countDown()
}
var orphan2 = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
orphan2 = session.createRoom(CreateRoomParams().apply { name = "O2" })
it.countDown()
}
orphan2 = session.createRoom(CreateRoomParams().apply { name = "O2" })
it.countDown()
}
val allRooms = session.getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) })
@ -250,11 +231,9 @@ class SpaceHierarchyTest : InstrumentedTest {
// Add a non canonical child and check that it does not appear as orphan
commonTestHelper.waitWithLatch {
GlobalScope.launch {
val a3 = session.createRoom(CreateRoomParams().apply { name = "A3" })
spaceA!!.addChildren(a3, viaServers, null, false)
it.countDown()
}
val a3 = session.createRoom(CreateRoomParams().apply { name = "A3" })
spaceA!!.addChildren(a3, viaServers, null, false)
it.countDown()
}
Thread.sleep(2_000)
@ -265,7 +244,6 @@ class SpaceHierarchyTest : InstrumentedTest {
}
@Test
@Suppress("EXPERIMENTAL_API_USAGE")
fun testBreakCycle() {
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
@ -283,20 +261,16 @@ class SpaceHierarchyTest : InstrumentedTest {
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
commonTestHelper.waitWithLatch {
GlobalScope.launch {
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
it.countDown()
}
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
it.countDown()
}
// add back A as subspace of C
commonTestHelper.waitWithLatch {
GlobalScope.launch {
val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId)
spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true)
it.countDown()
}
val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId)
spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true)
it.countDown()
}
Thread.sleep(1000)
@ -313,7 +287,6 @@ class SpaceHierarchyTest : InstrumentedTest {
}
@Test
@Suppress("EXPERIMENTAL_API_USAGE")
fun testLiveFlatChildren() {
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
@ -336,12 +309,14 @@ class SpaceHierarchyTest : InstrumentedTest {
session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
}
val flatAChildren = runBlocking(Dispatchers.Main) {
session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
}
val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
Triple("C1", true /*auto-join*/, true/*canonical*/),
Triple("C2", true, true)
))
commonTestHelper.waitWithLatch { latch ->
val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
val childObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(children: List<RoomSummary>?) {
// Log.d("## TEST", "Space A flat children update : ${children?.map { it.name }}")
@ -354,20 +329,13 @@ class SpaceHierarchyTest : InstrumentedTest {
}
}
val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
Triple("C1", true /*auto-join*/, true/*canonical*/),
Triple("C2", true, true)
))
// add C as subspace of B
runBlocking {
val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
}
val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
// C1 and C2 should be in flatten child of A now
GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
flatAChildren.observeForever(childObserver)
}
// Test part one of the rooms
@ -376,7 +344,7 @@ class SpaceHierarchyTest : InstrumentedTest {
val bRoom = session.getRoom(bRoomId)
commonTestHelper.waitWithLatch { latch ->
val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
val childObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(children: List<RoomSummary>?) {
System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}")
@ -389,13 +357,10 @@ class SpaceHierarchyTest : InstrumentedTest {
}
// part from b room
runBlocking {
bRoom!!.leave(null)
}
bRoom!!.leave(null)
// The room should have disapear from flat children
GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
flatAChildren.observeForever(childObserver)
}
commonTestHelper.signOutAndClose(session)
}
@ -404,94 +369,66 @@ class SpaceHierarchyTest : InstrumentedTest {
val roomIds: List<String>
)
@Suppress("EXPERIMENTAL_API_USAGE")
private fun createPublicSpace(session: Session,
spaceName: String,
childInfo: List<Triple<String, Boolean, Boolean?>>
/** Name, auto-join, canonical*/
): TestSpaceCreationResult {
var spaceId = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true)
it.countDown()
var roomIds: List<String> = emptyList()
commonTestHelper.waitWithLatch { latch ->
spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true)
val syncedSpace = session.spaceService().getSpace(spaceId)
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
roomIds = childInfo.map { entry ->
session.createRoom(CreateRoomParams().apply { name = entry.first })
}
}
val syncedSpace = session.spaceService().getSpace(spaceId)
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
val roomIds =
childInfo.map { entry ->
var roomId = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
roomId = session.createRoom(CreateRoomParams().apply { name = entry.first })
it.countDown()
}
}
roomId
}
roomIds.forEachIndexed { index, roomId ->
runBlocking {
roomIds.forEachIndexed { index, roomId ->
syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
val canonical = childInfo[index].third
if (canonical != null) {
session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
}
}
latch.countDown()
}
return TestSpaceCreationResult(spaceId, roomIds)
}
@Suppress("EXPERIMENTAL_API_USAGE")
private fun createPrivateSpace(session: Session,
spaceName: String,
childInfo: List<Triple<String, Boolean, Boolean?>>
/** Name, auto-join, canonical*/
): TestSpaceCreationResult {
var spaceId = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false)
it.countDown()
}
}
val syncedSpace = session.spaceService().getSpace(spaceId)
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
val roomIds =
childInfo.map { entry ->
var roomId = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
val homeServerCapabilities = session
.getHomeServerCapabilities()
roomId = session.createRoom(CreateRoomParams().apply {
name = entry.first
this.featurePreset = RestrictedRoomPreset(
homeServerCapabilities,
listOf(
RoomJoinRulesAllowEntry.restrictedToRoom(spaceId)
)
)
})
it.countDown()
}
var roomIds: List<String> = emptyList()
commonTestHelper.waitWithLatch { latch ->
spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false)
val syncedSpace = session.spaceService().getSpace(spaceId)
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
roomIds =
childInfo.map { entry ->
val homeServerCapabilities = session
.getHomeServerCapabilities()
session.createRoom(CreateRoomParams().apply {
name = entry.first
this.featurePreset = RestrictedRoomPreset(
homeServerCapabilities,
listOf(
RoomJoinRulesAllowEntry.restrictedToRoom(spaceId)
)
)
})
}
roomId
}
roomIds.forEachIndexed { index, roomId ->
runBlocking {
roomIds.forEachIndexed { index, roomId ->
syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
val canonical = childInfo[index].third
if (canonical != null) {
session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
}
}
latch.countDown()
}
return TestSpaceCreationResult(spaceId, roomIds)
}
@ -559,11 +496,9 @@ class SpaceHierarchyTest : InstrumentedTest {
var bobRoomId = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
bobRoomId = bobSession.createRoom(CreateRoomParams().apply { name = "A Bob Room" })
bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId)
it.countDown()
}
bobRoomId = bobSession.createRoom(CreateRoomParams().apply { name = "A Bob Room" })
bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId)
it.countDown()
}
commonTestHelper.runBlockingTest {
@ -577,10 +512,8 @@ class SpaceHierarchyTest : InstrumentedTest {
}
commonTestHelper.waitWithLatch {
GlobalScope.launch {
bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
it.countDown()
}
bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
it.countDown()
}
commonTestHelper.waitWithLatch { latch ->
@ -600,19 +533,17 @@ class SpaceHierarchyTest : InstrumentedTest {
// Let's now try to make alice admin of the room
commonTestHelper.waitWithLatch {
GlobalScope.launch {
val room = bobSession.getRoom(bobRoomId)!!
val currentPLContent = room
.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
?.let { it.content.toModel<PowerLevelsContent>() }
val room = bobSession.getRoom(bobRoomId)!!
val currentPLContent = room
.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
?.let { it.content.toModel<PowerLevelsContent>() }
val newPowerLevelsContent = currentPLContent
?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
?.toContent()
val newPowerLevelsContent = currentPLContent
?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
?.toContent()
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent!!)
it.countDown()
}
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent!!)
it.countDown()
}
commonTestHelper.waitWithLatch { latch ->
@ -627,10 +558,8 @@ class SpaceHierarchyTest : InstrumentedTest {
}
commonTestHelper.waitWithLatch {
GlobalScope.launch {
aliceSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
it.countDown()
}
aliceSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
it.countDown()
}
commonTestHelper.waitWithLatch { latch ->

View file

@ -20,6 +20,7 @@ import android.content.Context
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.Configuration
import androidx.work.WorkManager
import androidx.work.WorkerFactory
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.auth.AuthenticationService
@ -33,6 +34,7 @@ import org.matrix.android.sdk.internal.di.DaggerMatrixComponent
import org.matrix.android.sdk.internal.network.ApiInterceptor
import org.matrix.android.sdk.internal.network.UserAgentHolder
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
import org.matrix.olm.OlmManager
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
@ -53,12 +55,17 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
@Inject internal lateinit var sessionManager: SessionManager
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
@Inject internal lateinit var apiInterceptor: ApiInterceptor
@Inject internal lateinit var matrixWorkerFactory: MatrixWorkerFactory
init {
Monarchy.init(context)
DaggerMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
if (context.applicationContext !is Configuration.Provider) {
WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build())
val configuration = Configuration.Builder()
.setExecutor(Executors.newCachedThreadPool())
.setWorkerFactory(matrixWorkerFactory)
.build()
WorkManager.initialize(context, configuration)
}
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
}
@ -77,6 +84,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
return legacySessionImporter
}
fun workerFactory(): WorkerFactory = matrixWorkerFactory
fun registerApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) {
apiInterceptor.addListener(path, listener)
}

View file

@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@ -34,9 +35,8 @@ import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import javax.inject.Inject
internal class CancelGossipRequestWorker(context: Context,
params: WorkerParameters) :
SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, Params::class.java) {
internal class CancelGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(

View file

@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest
@ -37,9 +38,8 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import timber.log.Timber
import javax.inject.Inject
internal class SendGossipRequestWorker(context: Context,
params: WorkerParameters) :
SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, Params::class.java) {
internal class SendGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(

View file

@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
@ -37,9 +38,8 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import timber.log.Timber
import javax.inject.Inject
internal class SendGossipWorker(context: Context,
params: WorkerParameters) :
SessionSafeCoroutineWorker<SendGossipWorker.Params>(context, params, Params::class.java) {
internal class SendGossipWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<SendGossipWorker.Params>(context, params, sessionManager, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(

View file

@ -25,6 +25,7 @@ import io.realm.kotlin.where
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity
@ -50,9 +51,8 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import timber.log.Timber
import javax.inject.Inject
internal class UpdateTrustWorker(context: Context,
params: WorkerParameters) :
SessionSafeCoroutineWorker<UpdateTrustWorker.Params>(context, params, Params::class.java) {
internal class UpdateTrustWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<UpdateTrustWorker.Params>(context, params, sessionManager, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(

View file

@ -20,6 +20,7 @@ import androidx.work.Data
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
@ -33,9 +34,8 @@ import javax.inject.Inject
* Possible previous worker: None
* Possible next worker : None
*/
internal class SendVerificationMessageWorker(context: Context,
params: WorkerParameters) :
SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, Params::class.java) {
internal class SendVerificationMessageWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, sessionManager, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(

View file

@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.session.TestInterceptor
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
import org.matrix.android.sdk.internal.util.system.SystemModule
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
import org.matrix.olm.OlmManager
import java.io.File
@ -86,6 +87,8 @@ internal interface MatrixComponent {
fun sessionManager(): SessionManager
fun matrixWorkerFactory(): MatrixWorkerFactory
fun inject(matrix: Matrix)
@Component.Factory

View file

@ -20,6 +20,8 @@ import dagger.Module
import dagger.Provides
import org.matrix.android.sdk.internal.session.MockHttpInterceptor
import org.matrix.android.sdk.internal.session.TestInterceptor
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
import org.matrix.android.sdk.internal.util.DefaultBackgroundDetectionObserver
@Module
internal object NoOpTestModule {
@ -28,4 +30,11 @@ internal object NoOpTestModule {
@JvmStatic
@MockHttpInterceptor
fun providesTestInterceptor(): TestInterceptor? = null
@Provides
@JvmStatic
@MatrixScope
fun providesBackgroundDetectionObserver(): BackgroundDetectionObserver {
return DefaultBackgroundDetectionObserver()
}
}

View file

@ -17,24 +17,38 @@
package org.matrix.android.sdk.internal.di
import android.content.Context
import androidx.lifecycle.Observer
import androidx.work.Constraints
import androidx.work.ListenableWorker
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkRequest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@SessionScope
internal class WorkManagerProvider @Inject constructor(
context: Context,
@SessionId private val sessionId: String
@SessionId private val sessionId: String,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionScope: CoroutineScope
) {
private val tag = MATRIX_SDK_TAG_PREFIX + sessionId
val workManager = WorkManager.getInstance(context)
init {
checkIfWorkerFactoryIsSetup()
}
/**
* Create a OneTimeWorkRequestBuilder, with the Matrix SDK tag
*/
@ -60,6 +74,27 @@ internal class WorkManagerProvider @Inject constructor(
}
}
private fun checkIfWorkerFactoryIsSetup() {
sessionScope.launch(coroutineDispatchers.main) {
val checkWorkerRequest = OneTimeWorkRequestBuilder<MatrixWorkerFactory.CheckFactoryWorker>().build()
workManager.enqueue(checkWorkerRequest)
val checkWorkerLiveState = workManager.getWorkInfoByIdLiveData(checkWorkerRequest.id)
val observer = object : Observer<WorkInfo> {
override fun onChanged(workInfo: WorkInfo) {
if (workInfo.state.isFinished) {
checkWorkerLiveState.removeObserver(this)
if (workInfo.state == WorkInfo.State.FAILED) {
throw RuntimeException("MatrixWorkerFactory is not being set on your worker configuration.\n" +
"Makes sure to add it to a DelegatingWorkerFactory if you have your own factory or use it directly.\n" +
"You can grab the instance through the Matrix class.")
}
}
}
}
checkWorkerLiveState.observeForever(observer)
}
}
companion object {
private const val MATRIX_SDK_TAG_PREFIX = "MatrixSDK-"

View file

@ -203,8 +203,11 @@ internal class MxCallImpl(
override fun selectAnswer() {
Timber.tag(loggerTag.value).v("select answer $callId")
if (isOutgoing) return
state = CallState.Answering
if (!isOutgoing) return
// This is an outgoing call, select the remote client that answered.
if (state != CallState.Dialing && state !is CallState.Connected) {
Timber.tag(loggerTag.value).w("Expected state is CallState.Dialing or CallState.Connected got $state.")
}
CallSelectAnswerContent(
callId = callId,
partyId = ourPartyId,

View file

@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
import org.matrix.android.sdk.api.util.MimeTypes
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
@ -63,8 +64,8 @@ private data class NewAttachmentAttributes(
* Possible previous worker: None
* Possible next worker : Always [MultipleEventSendingDispatcherWorker]
*/
internal class UploadContentWorker(val context: Context, params: WorkerParameters) :
SessionSafeCoroutineWorker<UploadContentWorker.Params>(context, params, Params::class.java) {
internal class UploadContentWorker(val context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<UploadContentWorker.Params>(context, params, sessionManager, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.group
import android.content.Context
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
@ -28,8 +29,8 @@ import javax.inject.Inject
* Possible previous worker: None
* Possible next worker : None
*/
internal class GetGroupDataWorker(context: Context, params: WorkerParameters) :
SessionSafeCoroutineWorker<GetGroupDataWorker.Params>(context, params, Params::class.java) {
internal class GetGroupDataWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<GetGroupDataWorker.Params>(context, params, sessionManager, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(

View file

@ -19,13 +19,14 @@ import android.content.Context
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import javax.inject.Inject
internal class AddPusherWorker(context: Context, params: WorkerParameters) :
SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, Params::class.java) {
internal class AddPusherWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, sessionManager, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(

View file

@ -21,6 +21,7 @@ import androidx.work.OneTimeWorkRequest
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
@ -38,8 +39,8 @@ import javax.inject.Inject
* Possible previous worker: Always [UploadContentWorker]
* Possible next worker : None, but it will post new work to send events, encrypted or not
*/
internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters) :
SessionSafeCoroutineWorker<MultipleEventSendingDispatcherWorker.Params>(context, params, Params::class.java) {
internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<MultipleEventSendingDispatcherWorker.Params>(context, params, sessionManager, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(

View file

@ -19,6 +19,7 @@ import android.content.Context
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.SessionComponent
@ -32,8 +33,8 @@ import javax.inject.Inject
* Possible previous worker: None
* Possible next worker : None
*/
internal class RedactEventWorker(context: Context, params: WorkerParameters) :
SessionSafeCoroutineWorker<RedactEventWorker.Params>(context, params, Params::class.java) {
internal class RedactEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<RedactEventWorker.Params>(context, params, sessionManager, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(

View file

@ -23,6 +23,7 @@ import io.realm.RealmConfiguration
import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.SessionComponent
@ -38,9 +39,8 @@ import javax.inject.Inject
* Possible previous worker: [EncryptEventWorker] or first worker
* Possible next worker : None
*/
internal class SendEventWorker(context: Context,
params: WorkerParameters) :
SessionSafeCoroutineWorker<SendEventWorker.Params>(context, params, Params::class.java) {
internal class SendEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<SendEventWorker.Params>(context, params, sessionManager, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(

View file

@ -25,9 +25,12 @@ import javax.inject.Inject
internal class SyncTokenStore @Inject constructor(@SessionDatabase private val monarchy: Monarchy) {
fun getLastToken(): String? {
return Realm.getInstance(monarchy.realmConfiguration).use {
val token = Realm.getInstance(monarchy.realmConfiguration).use {
// Makes sure realm is up-to-date as it's used for querying internally on non looper thread.
it.refresh()
it.where(SyncEntity::class.java).findFirst()?.nextBatch
}
return token
}
fun saveToken(realm: Realm, token: String?) {

View file

@ -21,8 +21,8 @@ import androidx.work.ExistingWorkPolicy
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.failure.isTokenError
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.session.sync.SyncPresence
import org.matrix.android.sdk.internal.session.sync.SyncTask
@ -41,9 +41,8 @@ private const val DEFAULT_DELAY_TIMEOUT = 30_000L
* Possible previous worker: None
* Possible next worker : None
*/
internal class SyncWorker(context: Context,
workerParameters: WorkerParameters
) : SessionSafeCoroutineWorker<SyncWorker.Params>(context, workerParameters, Params::class.java) {
internal class SyncWorker(context: Context, workerParameters: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<SyncWorker.Params>(context, workerParameters, sessionManager, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
@ -56,7 +55,6 @@ internal class SyncWorker(context: Context,
@Inject lateinit var syncTask: SyncTask
@Inject lateinit var taskExecutor: TaskExecutor
@Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker
@Inject lateinit var workManagerProvider: WorkManagerProvider
override fun injectWith(injector: SessionComponent) {

View file

@ -18,26 +18,32 @@ package org.matrix.android.sdk.internal.util
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import org.matrix.android.sdk.internal.di.MatrixScope
import timber.log.Timber
import javax.inject.Inject
/**
* To be attached to ProcessLifecycleOwner lifecycle
*/
@MatrixScope
internal class BackgroundDetectionObserver @Inject constructor() : DefaultLifecycleObserver {
interface BackgroundDetectionObserver : DefaultLifecycleObserver {
val isInBackground: Boolean
var isInBackground: Boolean = true
fun register(listener: Listener)
fun unregister(listener: Listener)
interface Listener {
fun onMoveToForeground()
fun onMoveToBackground()
}
}
internal class DefaultBackgroundDetectionObserver : BackgroundDetectionObserver {
override var isInBackground: Boolean = true
private set
private val listeners = LinkedHashSet<Listener>()
private val listeners = LinkedHashSet<BackgroundDetectionObserver.Listener>()
fun register(listener: Listener) {
override fun register(listener: BackgroundDetectionObserver.Listener) {
listeners.add(listener)
}
fun unregister(listener: Listener) {
override fun unregister(listener: BackgroundDetectionObserver.Listener) {
listeners.remove(listener)
}
@ -52,9 +58,4 @@ internal class BackgroundDetectionObserver @Inject constructor() : DefaultLifecy
isInBackground = true
listeners.forEach { it.onMoveToBackground() }
}
interface Listener {
fun onMoveToForeground()
fun onMoveToBackground()
}
}

View file

@ -17,16 +17,33 @@
package org.matrix.android.sdk.internal.worker
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.ListenableWorker
import androidx.work.WorkerFactory
import androidx.work.WorkerParameters
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.CancelGossipRequestWorker
import org.matrix.android.sdk.internal.crypto.SendGossipRequestWorker
import org.matrix.android.sdk.internal.crypto.SendGossipWorker
import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker
import org.matrix.android.sdk.internal.crypto.verification.SendVerificationMessageWorker
import org.matrix.android.sdk.internal.di.MatrixScope
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
import org.matrix.android.sdk.internal.session.sync.job.SyncWorker
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Provider
class MatrixWorkerFactory @Inject constructor(
private val workerFactories: Map<Class<out ListenableWorker>, @JvmSuppressWildcards Provider<DelegateWorkerFactory>>
) : WorkerFactory() {
/**
* This factory is responsible of creating Workers by giving the session manager.
* This is not the cleanest way but getting SessionComponent is dependant of args type.
*/
@MatrixScope
internal class MatrixWorkerFactory @Inject constructor(private val sessionManager: SessionManager) : WorkerFactory() {
override fun createWorker(
appContext: Context,
@ -34,11 +51,61 @@ class MatrixWorkerFactory @Inject constructor(
workerParameters: WorkerParameters
): ListenableWorker? {
Timber.d("MatrixWorkerFactory.createWorker for $workerClassName")
return when (workerClassName) {
CheckFactoryWorker::class.java.name ->
CheckFactoryWorker(appContext, workerParameters, true)
AddPusherWorker::class.java.name ->
AddPusherWorker(appContext, workerParameters, sessionManager)
CancelGossipRequestWorker::class.java.name ->
CancelGossipRequestWorker(appContext, workerParameters, sessionManager)
GetGroupDataWorker::class.java.name ->
GetGroupDataWorker(appContext, workerParameters, sessionManager)
MultipleEventSendingDispatcherWorker::class.java.name ->
MultipleEventSendingDispatcherWorker(appContext, workerParameters, sessionManager)
RedactEventWorker::class.java.name ->
RedactEventWorker(appContext, workerParameters, sessionManager)
SendEventWorker::class.java.name ->
SendEventWorker(appContext, workerParameters, sessionManager)
SendGossipRequestWorker::class.java.name ->
SendGossipRequestWorker(appContext, workerParameters, sessionManager)
SendGossipWorker::class.java.name ->
SendGossipWorker(appContext, workerParameters, sessionManager)
SendVerificationMessageWorker::class.java.name ->
SendVerificationMessageWorker(appContext, workerParameters, sessionManager)
SyncWorker::class.java.name ->
SyncWorker(appContext, workerParameters, sessionManager)
UpdateTrustWorker::class.java.name ->
UpdateTrustWorker(appContext, workerParameters, sessionManager)
UploadContentWorker::class.java.name ->
UploadContentWorker(appContext, workerParameters, sessionManager)
else -> {
Timber.w("No worker defined on MatrixWorkerFactory for $workerClassName will delegate to default.")
// Return null to delegate to the default WorkerFactory.
null
}
}
}
val foundEntry =
workerFactories.entries.find { Class.forName(workerClassName).isAssignableFrom(it.key) }
val factoryProvider = foundEntry?.value
?: throw IllegalArgumentException("unknown worker class name: $workerClassName")
return factoryProvider.get().create(appContext, workerParameters)
/**
* This worker is launched by the factory with the isCreatedByMatrixWorkerFactory flag to true.
* If the MatrixWorkerFactory is not set up, it will default to the other constructor and it will throw
*/
class CheckFactoryWorker(context: Context,
workerParameters: WorkerParameters,
private val isCreatedByMatrixWorkerFactory: Boolean) :
CoroutineWorker(context, workerParameters) {
// Called by WorkManager if there is no MatrixWorkerFactory
constructor(context: Context, workerParameters: WorkerParameters) : this(context,
workerParameters,
isCreatedByMatrixWorkerFactory = false)
override suspend fun doWork(): Result {
return if (!isCreatedByMatrixWorkerFactory) {
Result.failure()
} else {
Result.success()
}
}
}
}

View file

@ -22,6 +22,7 @@ import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.session.SessionComponent
import timber.log.Timber
@ -33,6 +34,7 @@ import timber.log.Timber
internal abstract class SessionSafeCoroutineWorker<PARAM : SessionWorkerParams>(
context: Context,
workerParameters: WorkerParameters,
private val sessionManager: SessionManager,
private val paramClass: Class<PARAM>
) : CoroutineWorker(context, workerParameters) {
@ -48,7 +50,7 @@ internal abstract class SessionSafeCoroutineWorker<PARAM : SessionWorkerParams>(
.also { Timber.e("Unable to parse work parameters") }
return try {
val sessionComponent = getSessionComponent(params.sessionId)
val sessionComponent = sessionManager.getSessionComponent(params.sessionId)
?: return buildErrorResult(params, "No session")
// Make sure to inject before handling error as you may need some dependencies to process them.

View file

@ -1,25 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.worker
import androidx.work.ListenableWorker
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.internal.session.SessionComponent
internal fun ListenableWorker.getSessionComponent(sessionId: String): SessionComponent? {
return Matrix.getInstance(applicationContext).sessionManager.getSessionComponent(sessionId)
}

View file

@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1
# android\.text\.TextUtils
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
enum class===108
enum class===110
### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3

View file

@ -25,3 +25,8 @@
### Use style="@style/Widget.Vector.TextView.*" instead of textSize attribute
android:textSize===9
### Use `@id` and not `@+id` when referencing ids in layouts
layout_(.*)="@\+id
accessibilityTraversal(.*)="@\+id
toolbarId="@\+id

26
tools/compressVideo.sh Executable file
View file

@ -0,0 +1,26 @@
#!/usr/bin/env bash
#
# Copyright (c) 2021 New Vector Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
set -e
echo "Converting file $1"
file=$(echo $1 | sed 's/\.[^.]*$//')
ffmpeg -i $1 -filter_complex "[0:v] fps=12,scale=480:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" $file-tmp.gif
echo "Converting to Gif"
gifsicle -O3 --lossy=80 -o $file.gif $file-tmp.gif
rm $file-tmp.gif
echo "Done, $file.gif has been generated"

View file

@ -105,8 +105,6 @@
suggest="${underscoreToCamelCase(classToResource(fragmentClass))}ViewEvents"
default="MainViewEvents"
help="The name of the view events to create" />
<parameter
id="packageName"

View file

@ -140,16 +140,8 @@ android {
buildConfigField "String", "BUILD_NUMBER", "\"${buildNumber}\""
resValue "string", "build_number", "\"${buildNumber}\""
// The two booleans must not have the same value. We need two values for the manifest
// LoginFlowV2 is disabled to be merged on develop (changelog: Improve login/register flow (#1410, #2585, #3172))
resValue "bool", "useLoginV1", "true"
resValue "bool", "useLoginV2", "false"
// NotificationSettingsV2 is disabled. To be released in conjunction with iOS/Web
def useNotificationSettingsV2 = true
buildConfigField "Boolean", "USE_NOTIFICATION_SETTINGS_V2", "${useNotificationSettingsV2}"
resValue "bool", "useNotificationSettingsV1", "${!useNotificationSettingsV2}"
resValue "bool", "useNotificationSettingsV2", "${useNotificationSettingsV2}"
buildConfigField "im.vector.app.features.VectorFeatures.LoginVersion", "LOGIN_VERSION", "im.vector.app.features.VectorFeatures.LoginVersion.V1"
buildConfigField "im.vector.app.features.VectorFeatures.NotificationSettingsVersion", "NOTIFICATION_SETTINGS_VERSION", "im.vector.app.features.VectorFeatures.NotificationSettingsVersion.V2"
buildConfigField "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy", "outboundSessionKeySharingStrategy", "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy.WhenTyping"

View file

@ -21,22 +21,27 @@ import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.espresso.tools.clickOnPreference
import im.vector.app.features.VectorFeatures
class SettingsNotificationsRobot {
fun crawl() {
if (BuildConfig.USE_NOTIFICATION_SETTINGS_V2) {
clickOn(R.string.settings_notification_default)
pressBack()
clickOn(R.string.settings_notification_mentions_and_keywords)
// TODO Test adding a keyword?
pressBack()
clickOn(R.string.settings_notification_other)
pressBack()
} else {
clickOn(R.string.settings_notification_advanced)
pressBack()
when (BuildConfig.NOTIFICATION_SETTINGS_VERSION!!) {
VectorFeatures.NotificationSettingsVersion.V1 -> {
clickOn(R.string.settings_notification_advanced)
pressBack()
}
VectorFeatures.NotificationSettingsVersion.V2 -> {
clickOn(R.string.settings_notification_default)
pressBack()
clickOn(R.string.settings_notification_mentions_and_keywords)
// TODO Test adding a keyword?
pressBack()
clickOn(R.string.settings_notification_other)
pressBack()
}
}
/*
clickOn(R.string.settings_noisy_notifications_preferences)
TODO Cannot go back

View file

@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
The aim of this file is to test the different themes of Riot
The aim of this file is to test the different themes of Element
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"

View file

@ -1,10 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
The aim of this file is to test the different themes of Riot
The aim of this file is to test the different themes of Element
Unfortunately, this does not work in the preview.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"

View file

@ -113,45 +113,33 @@
<activity android:name=".features.home.HomeActivity" />
<!-- exported="true" is required to handle android.intent.action.VIEW for URL redirection-->
<activity
android:name=".features.login.SSORedirectRouterActivity"
android:exported="true"
android:theme="@style/Theme.Vector.Black.Transparent">
<!-- Add intent filter to handle redirection URL after SSO login in external browser -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="connect"
android:scheme="element" />
</intent-filter>
</activity>
<activity
android:name=".features.login.LoginActivity"
android:enabled="@bool/useLoginV1"
android:exported="true"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<!-- Add intent filter to handle redirection URL after SSO login in external browser -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
android:windowSoftInputMode="adjustResize" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="connect"
android:scheme="element" />
</intent-filter>
</activity>
<!-- exported="true" is required to handle android.intent.action.VIEW for URL redirection-->
<activity
android:name=".features.login2.LoginActivity2"
android:enabled="@bool/useLoginV2"
android:exported="true"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<!-- Add intent filter to handle redirection URL after SSO login in external browser -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="connect"
android:scheme="element" />
</intent-filter>
</activity>
android:windowSoftInputMode="adjustResize" />
<!-- Add tools:ignore="Instantiatable" for the error reported only by Buildkite :/ -->
<activity

View file

@ -223,6 +223,7 @@ class VectorApplication :
override fun getWorkManagerConfiguration(): WorkConfiguration {
return WorkConfiguration.Builder()
.setWorkerFactory(Matrix.getInstance(this.appContext).workerFactory())
.setExecutor(Executors.newCachedThreadPool())
.build()
}

View file

@ -31,6 +31,8 @@ import im.vector.app.core.error.DefaultErrorFormatter
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.time.Clock
import im.vector.app.core.time.DefaultClock
import im.vector.app.features.DefaultVectorFeatures
import im.vector.app.features.VectorFeatures
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
import im.vector.app.features.invite.AutoAcceptInvites
@ -138,4 +140,9 @@ object VectorStaticModule {
fun providesCoroutineDispatchers(): CoroutineDispatchers {
return CoroutineDispatchers(io = Dispatchers.IO, computation = Dispatchers.Default)
}
@Provides
fun providesFeatures(): VectorFeatures {
return DefaultVectorFeatures()
}
}

View file

@ -17,6 +17,7 @@
package im.vector.app.core.epoxy
import android.view.View
import android.widget.TextView
import im.vector.app.core.utils.DebouncedClickListener
/**
@ -32,6 +33,26 @@ fun View.onClick(listener: ClickListener?) {
}
}
fun TextView.onLongClickIgnoringLinks(listener: View.OnLongClickListener?) {
if (listener == null) {
setOnLongClickListener(null)
} else {
setOnLongClickListener(object : View.OnLongClickListener {
override fun onLongClick(v: View): Boolean {
if (hasLongPressedLink()) {
return false
}
return listener.onLongClick(v)
}
/**
* Infer that a Clickable span has been click by the presence of a selection
*/
private fun hasLongPressedLink() = selectionStart != -1 || selectionEnd != -1
})
}
}
/**
* Simple Text listener lambda
*/

View file

@ -62,7 +62,6 @@ import im.vector.app.core.extensions.restart
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.utils.toast
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
@ -122,7 +121,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
protected fun View.debouncedClicks(onClicked: () -> Unit) {
clicks()
.throttleFirst(300)
.onEach { onClicked() }
.launchIn(lifecycleScope)
}

View file

@ -36,7 +36,6 @@ import dagger.hilt.android.EntryPointAccessors
import im.vector.app.core.di.ActivityEntryPoint
import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.analytics.VectorAnalytics
import kotlinx.coroutines.flow.launchIn
@ -174,7 +173,6 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
protected fun View.debouncedClicks(onClicked: () -> Unit) {
clicks()
.throttleFirst(300)
.onEach { onClicked() }
.launchIn(viewLifecycleOwner.lifecycleScope)
}

View file

@ -42,7 +42,6 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.flow.throttleFirst
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.navigation.Navigator
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
@ -242,7 +241,6 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
protected fun View.debouncedClicks(onClicked: () -> Unit) {
clicks()
.throttleFirst(300)
.onEach { onClicked() }
.launchIn(viewLifecycleOwner.lifecycleScope)
}

View file

@ -42,8 +42,6 @@ import im.vector.app.features.pin.UnlockedActivity
import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.signout.hard.SignedOutActivity
import im.vector.app.features.signout.soft.SoftLogoutActivity
import im.vector.app.features.signout.soft.SoftLogoutActivity2
import im.vector.app.features.themes.ActivityOtherThemes
import im.vector.app.features.ui.UiStateRepository
import kotlinx.coroutines.Dispatchers
@ -226,9 +224,11 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
navigator.openLogin(this, null)
null
}
args.isSoftLogout ->
args.isSoftLogout -> {
// The homeserver has invalidated the token, with a soft logout
getSoftLogoutActivityIntent()
navigator.softLogout(this)
null
}
args.isUserLoggedOut ->
// the homeserver has invalidated the token (password changed, device deleted, other security reasons)
SignedOutActivity.newIntent(this)
@ -239,7 +239,8 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
HomeActivity.newIntent(this)
} else {
// The token is still invalid
getSoftLogoutActivityIntent()
navigator.softLogout(this)
null
}
else -> {
// First start, or no active session
@ -250,12 +251,4 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
intent?.let { startActivity(it) }
finish()
}
private fun getSoftLogoutActivityIntent(): Intent {
return if (resources.getBoolean(R.bool.useLoginV2)) {
SoftLogoutActivity2.newIntent(this)
} else {
SoftLogoutActivity.newIntent(this)
}
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features
import im.vector.app.BuildConfig
interface VectorFeatures {
fun loginVersion(): LoginVersion
fun notificationSettingsVersion(): NotificationSettingsVersion
enum class LoginVersion {
V1,
V2
}
enum class NotificationSettingsVersion {
V1,
V2
}
}
class DefaultVectorFeatures : VectorFeatures {
override fun loginVersion(): VectorFeatures.LoginVersion = BuildConfig.LOGIN_VERSION
override fun notificationSettingsVersion(): VectorFeatures.NotificationSettingsVersion = BuildConfig.NOTIFICATION_SETTINGS_VERSION
}

View file

@ -79,7 +79,7 @@ class AttachmentsPreviewFragment @Inject constructor(
applyInsets()
setupRecyclerViews()
setupToolbar(views.attachmentPreviewerToolbar)
views.attachmentPreviewerSendButton.setOnClickListener {
views.attachmentPreviewerSendButton.debouncedClicks {
setResultAndFinish()
}
}

View file

@ -35,7 +35,6 @@ import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.viewModel
@ -62,8 +61,6 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.RoomDetailArgs
import io.github.hyuwah.draggableviewlib.DraggableView
import io.github.hyuwah.draggableviewlib.setupDraggable
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.logger.LoggerTag
@ -142,12 +139,9 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
}
}
callViewModel.viewEvents
.stream()
.onEach {
handleViewEvents(it)
}
.launchIn(lifecycleScope)
callViewModel.observeViewEvents {
handleViewEvents(it)
}
callViewModel.onEach(VectorCallViewState::callId, VectorCallViewState::isVideoCall) { _, isVideoCall ->
if (isVideoCall) {

View file

@ -77,7 +77,7 @@ class ContactsBookFragment @Inject constructor(
}
private fun setupConsentView() {
views.phoneBookSearchForMatrixContacts.setOnClickListener {
views.phoneBookSearchForMatrixContacts.debouncedClicks {
contactsBookViewModel.handle(ContactsBookAction.UserConsentRequest)
}
}

View file

@ -58,8 +58,8 @@ class KeysBackupRestoreFromKeyFragment @Inject constructor() :
views.keyInputLayout.error = newValue
}
views.keysRestoreButton.setOnClickListener { onRestoreFromKey() }
views.keysBackupImport.setOnClickListener { onImport() }
views.keysRestoreButton.debouncedClicks { onRestoreFromKey() }
views.keysBackupImport.debouncedClicks { onImport() }
views.keyTextEdit.doOnTextChanged { text, _, _, _ -> onRestoreKeyTextEditChange(text) }
}

View file

@ -58,8 +58,8 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBase
return@setOnEditorActionListener false
}
views.helperTextWithLink.setOnClickListener { onUseRecoveryKey() }
views.keysBackupRestoreWithPassphraseSubmit.setOnClickListener { onRestoreBackup() }
views.helperTextWithLink.debouncedClicks { onUseRecoveryKey() }
views.keysBackupRestoreWithPassphraseSubmit.debouncedClicks { onRestoreBackup() }
views.keysBackupPassphraseEnterEdittext.doOnTextChanged { text, _, _, _ -> onPassphraseTextEditChange(text) }
}

View file

@ -52,7 +52,7 @@ class KeysBackupRestoreSuccessFragment @Inject constructor() : VectorBaseFragmen
views.successText.text = context?.getString(R.string.keys_backup_restore_success_title_already_up_to_date)
views.successDetailsText.isVisible = false
}
views.keysBackupSetupDoneButton.setOnClickListener { onDone() }
views.keysBackupSetupDoneButton.debouncedClicks { onDone() }
}
private fun onDone() {

View file

@ -45,8 +45,8 @@ class KeysBackupSetupStep1Fragment @Inject constructor() : VectorBaseFragment<Fr
views.keysBackupSetupStep1ManualExportButton.visibility = if (showOption) View.VISIBLE else View.GONE
}
views.keysBackupSetupStep1Button.setOnClickListener { onButtonClick() }
views.keysBackupSetupStep1ManualExportButton.setOnClickListener { onManualExportClick() }
views.keysBackupSetupStep1Button.debouncedClicks { onButtonClick() }
views.keysBackupSetupStep1ManualExportButton.debouncedClicks { onManualExportClick() }
}
private fun onButtonClick() {

View file

@ -128,8 +128,8 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
}
private fun setupViews() {
views.keysBackupSetupStep2Button.setOnClickListener { doNext() }
views.keysBackupSetupStep2SkipButton.setOnClickListener { skipPassphrase() }
views.keysBackupSetupStep2Button.debouncedClicks { doNext() }
views.keysBackupSetupStep2SkipButton.debouncedClicks { skipPassphrase() }
views.keysBackupSetupStep2PassphraseEnterEdittext.doOnTextChanged { _, _, _, _ -> onPassphraseChanged() }
views.keysBackupSetupStep2PassphraseConfirmEditText.doOnTextChanged { _, _, _, _ -> onConfirmPassphraseChanged() }

View file

@ -85,9 +85,9 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
}
private fun setupViews() {
views.keysBackupSetupStep3FinishButton.setOnClickListener { onFinishButtonClicked() }
views.keysBackupSetupStep3CopyButton.setOnClickListener { onCopyButtonClicked() }
views.keysBackupSetupStep3RecoveryKeyText.setOnClickListener { onRecoveryKeyClicked() }
views.keysBackupSetupStep3FinishButton.debouncedClicks { onFinishButtonClicked() }
views.keysBackupSetupStep3CopyButton.debouncedClicks { onCopyButtonClicked() }
views.keysBackupSetupStep3RecoveryKeyText.debouncedClicks { onRecoveryKeyClicked() }
}
private fun onFinishButtonClicked() {
@ -127,7 +127,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
}
}
dialog.findViewById<View>(R.id.keys_backup_setup_save)?.setOnClickListener {
dialog.findViewById<View>(R.id.keys_backup_setup_save)?.debouncedClicks {
val userId = viewModel.userId
val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
selectTxtFileToWrite(
@ -139,7 +139,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
dialog.dismiss()
}
dialog.findViewById<View>(R.id.keys_backup_setup_share)?.setOnClickListener {
dialog.findViewById<View>(R.id.keys_backup_setup_share)?.debouncedClicks {
startSharePlainTextIntent(
fragment = this,
activityResultLauncher = null,

View file

@ -898,7 +898,7 @@ class RoomDetailFragment @Inject constructor(
}
private fun setupJumpToReadMarkerView() {
views.jumpToReadMarkerView.setOnClickListener {
views.jumpToReadMarkerView.debouncedClicks {
onJumpToReadMarkerClicked()
}
views.jumpToReadMarkerView.setOnCloseIconClickListener {
@ -954,7 +954,7 @@ class RoomDetailFragment @Inject constructor(
super.onCreateOptionsMenu(menu, inflater)
// We use a custom layout for this menu item, so we need to set a ClickListener
menu.findItem(R.id.open_matrix_apps)?.let { menuItem ->
menuItem.actionView.setOnClickListener {
menuItem.actionView.debouncedClicks {
onOptionsItemSelected(menuItem)
}
}
@ -1463,7 +1463,7 @@ class RoomDetailFragment @Inject constructor(
callback = this@RoomDetailFragment
isVisible = true
render(inviter, VectorInviteView.Mode.LARGE, mainState.changeMembershipState)
setOnClickListener { }
setOnClickListener(null)
}
Unit
} else if (mainState.asyncInviter.complete) {

View file

@ -25,6 +25,7 @@ import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.epoxy.onLongClickIgnoringLinks
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
@ -94,10 +95,9 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
}
super.bind(holder)
holder.messageView.movementMethod = movementMethod
renderSendState(holder.messageView, holder.messageView)
holder.messageView.onClick(attributes.itemClickListener)
holder.messageView.setOnLongClickListener(attributes.itemLongClickListener)
holder.messageView.onLongClickIgnoringLinks(attributes.itemLongClickListener)
if (canUseTextFuture) {
holder.messageView.setTextFuture(textFuture)
@ -133,6 +133,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
previewUrlView?.render(state, safeImageContentRenderer)
}
}
companion object {
private const val STUB_ID = R.id.messageContentTextStub
}

View file

@ -17,7 +17,6 @@ package im.vector.app.features.home.room.list.actions
import androidx.annotation.StringRes
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.epoxy.bottomSheetDividerItem
import im.vector.app.core.epoxy.bottomsheet.bottomSheetActionItem
@ -25,6 +24,7 @@ import im.vector.app.core.epoxy.bottomsheet.bottomSheetRoomPreviewItem
import im.vector.app.core.epoxy.profiles.notifications.radioButtonItem
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.VectorFeatures
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomprofile.notifications.notificationOptions
import im.vector.app.features.roomprofile.notifications.notificationStateMapped
@ -38,7 +38,8 @@ import javax.inject.Inject
class RoomListQuickActionsEpoxyController @Inject constructor(
private val avatarRenderer: AvatarRenderer,
private val colorProvider: ColorProvider,
private val stringProvider: StringProvider
private val stringProvider: StringProvider,
private val features: VectorFeatures
) : TypedEpoxyController<RoomListQuickActionViewState>() {
var listener: Listener? = null
@ -47,7 +48,7 @@ class RoomListQuickActionsEpoxyController @Inject constructor(
val notificationViewState = state.notificationSettingsViewState
val roomSummary = notificationViewState.roomSummary() ?: return
val host = this
val isV2 = BuildConfig.USE_NOTIFICATION_SETTINGS_V2
val isV2 = features.notificationSettingsVersion() == VectorFeatures.NotificationSettingsVersion.V2
// V2 always shows full details as we no longer display the sheet from RoomProfile > Notifications
val showFull = state.roomListActionsArgs.mode == RoomListActionsArgs.Mode.FULL || isV2
@ -73,14 +74,14 @@ class RoomListQuickActionsEpoxyController @Inject constructor(
}
if (isV2) {
notificationViewState.notificationOptions.forEach { notificationState ->
notificationViewState.notificationOptions.forEach { notificationState ->
val title = titleForNotificationState(notificationState)
radioButtonItem {
id(notificationState.name)
titleRes(title)
selected(notificationViewState.notificationStateMapped() == notificationState)
listener {
host.listener?.didSelectRoomNotificationState(notificationState)
host.listener?.didSelectRoomNotificationState(notificationState)
}
}
}
@ -102,8 +103,9 @@ class RoomListQuickActionsEpoxyController @Inject constructor(
RoomNotificationState.ALL_MESSAGES_NOISY -> R.string.room_settings_all_messages
RoomNotificationState.MENTIONS_ONLY -> R.string.room_settings_mention_and_keyword_only
RoomNotificationState.MUTE -> R.string.room_settings_none
else -> null
else -> null
}
private fun RoomListQuickActionsSharedAction.toBottomSheetItem(index: Int, roomNotificationState: RoomNotificationState? = null) {
val host = this@RoomListQuickActionsEpoxyController
val selected = when (this) {

View file

@ -18,6 +18,7 @@ package im.vector.app.features.login
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
@ -363,5 +364,11 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), ToolbarCo
putExtra(EXTRA_CONFIG, loginConfig)
}
}
fun redirectIntent(context: Context, data: Uri?): Intent {
return Intent(context, LoginActivity::class.java).apply {
setData(data)
}
}
}
}

View file

@ -81,7 +81,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
}
private fun setupForgottenPasswordButton() {
views.forgetPasswordButton.setOnClickListener { forgetPasswordClicked() }
views.forgetPasswordButton.debouncedClicks { forgetPasswordClicked() }
}
private fun setupAutoFill(state: LoginViewState) {
@ -226,7 +226,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
}
private fun setupSubmitButton() {
views.loginSubmit.setOnClickListener { submit() }
views.loginSubmit.debouncedClicks { submit() }
combine(
views.loginField.textChanges().map { it.trim().isNotEmpty() },
views.passwordField.textChanges().map { it.isNotEmpty() }

View file

@ -78,8 +78,8 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
}
private fun setupViews() {
views.loginGenericTextInputFormOtherButton.setOnClickListener { onOtherButtonClicked() }
views.loginGenericTextInputFormSubmit.setOnClickListener { submit() }
views.loginGenericTextInputFormOtherButton.debouncedClicks { onOtherButtonClicked() }
views.loginGenericTextInputFormSubmit.debouncedClicks { submit() }
}
private fun setupAutoFill() {

View file

@ -61,7 +61,7 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<F
}
private fun setupSubmitButton() {
views.resetPasswordSubmit.setOnClickListener { submit() }
views.resetPasswordSubmit.debouncedClicks { submit() }
combine(
views.resetPasswordEmail.textChanges().map { it.isEmail() },
views.passwordField.textChanges().map { it.isNotEmpty() }

View file

@ -40,7 +40,7 @@ class LoginResetPasswordMailConfirmationFragment @Inject constructor() : Abstrac
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.resetPasswordMailConfirmationSubmit.setOnClickListener { submit() }
views.resetPasswordMailConfirmationSubmit.debouncedClicks { submit() }
}
private fun setupUi(state: LoginViewState) {

View file

@ -35,7 +35,7 @@ class LoginResetPasswordSuccessFragment @Inject constructor() : AbstractLoginFra
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.resetPasswordSuccessSubmit.setOnClickListener { submit() }
views.resetPasswordSuccessSubmit.debouncedClicks { submit() }
}
private fun submit() {

View file

@ -43,11 +43,11 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
}
private fun initViews() {
views.loginServerChoiceEmsLearnMore.setOnClickListener { learnMore() }
views.loginServerChoiceMatrixOrg.setOnClickListener { selectMatrixOrg() }
views.loginServerChoiceEms.setOnClickListener { selectEMS() }
views.loginServerChoiceOther.setOnClickListener { selectOther() }
views.loginServerIKnowMyIdSubmit.setOnClickListener { loginWithMatrixId() }
views.loginServerChoiceEmsLearnMore.debouncedClicks { learnMore() }
views.loginServerChoiceMatrixOrg.debouncedClicks { selectMatrixOrg() }
views.loginServerChoiceEms.debouncedClicks { selectEMS() }
views.loginServerChoiceOther.debouncedClicks { selectOther() }
views.loginServerIKnowMyIdSubmit.debouncedClicks { loginWithMatrixId() }
}
private fun updateSelectedChoice(state: LoginViewState) {

View file

@ -57,9 +57,9 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment<F
}
private fun setupViews() {
views.loginServerUrlFormLearnMore.setOnClickListener { learnMore() }
views.loginServerUrlFormClearHistory.setOnClickListener { clearHistory() }
views.loginServerUrlFormSubmit.setOnClickListener { submit() }
views.loginServerUrlFormLearnMore.debouncedClicks { learnMore() }
views.loginServerUrlFormClearHistory.debouncedClicks { clearHistory() }
views.loginServerUrlFormSubmit.debouncedClicks { submit() }
}
private fun setupHomeServerField() {

Some files were not shown because too many files have changed in this diff Show more