Merge tag 'v1.3.12' into sc

Change-Id: I66c0120371c3f52295f033ec21702c08381b5b10

Conflicts:
	build.gradle
	vector/build.gradle
	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt
	vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
	vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsEpoxyController.kt
	vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
	vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
	vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt
	vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
	vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt
	vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt
	vector/src/main/res/drawable/highlighted_message_background.xml
	vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
	vector/src/main/res/layout/composer_layout_constraint_set_expanded.xml
	vector/src/main/res/layout/fragment_home_detail.xml
	vector/src/main/res/layout/fragment_login_server_selection.xml
	vector/src/main/res/layout/fragment_room_detail.xml
	vector/src/main/res/layout/item_bottom_sheet_message_preview.xml
	vector/src/main/res/layout/item_expandable_textview.xml
	vector/src/main/res/layout/item_login_header.xml
	vector/src/main/res/layout/item_radio.xml
	vector/src/main/res/layout/item_room.xml
	vector/src/main/res/layout/item_timeline_event_base.xml
	vector/src/main/res/layout/item_timeline_event_call_tile_stub.xml
	vector/src/main/res/layout/item_timeline_event_code_block_stub.xml
	vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
	vector/src/main/res/layout/item_timeline_event_poll_stub.xml
	vector/src/main/res/layout/item_timeline_event_redacted_stub.xml
	vector/src/main/res/layout/item_timeline_event_voice_stub.xml
	vector/src/main/res/layout/view_stub_room_profile_header.xml
	vector/src/main/res/layout/view_url_preview.xml
	vector/src/main/res/xml/vector_settings_labs.xml
	vector/src/main/res/xml/vector_settings_root.xml
This commit is contained in:
SpiritCroc 2021-12-23 13:10:57 +01:00
commit acdd4e24b9
660 changed files with 8171 additions and 4005 deletions

View file

@ -70,4 +70,27 @@ jobs:
body: | body: |
- Update SAS Strings from matrix-doc. - Update SAS Strings from matrix-doc.
branch: sync-sas-strings branch: sync-sas-strings
base: develop
sync-analytics-plan:
runs-on: ubuntu-latest
# Skip in forks
if: github.repository == 'vector-im/element-android'
steps:
- uses: actions/checkout@v2
- name: Run analytics import script
run: ./tools/import_analytic_plan.sh
- name: Create Pull Request for analytics plan
uses: peter-evans/create-pull-request@v3
with:
commit-message: Sync analytics plan
title: Sync analytics plan
body: |
### Update analytics plan
Reviewers:
- [ ] Please remove usage of Event or Enum which may have been removed or updated
- [ ] please ensure new Events or new Enums are used to send analytics by pushing new commit(s) to this PR.
*Note*: Change are coming from [this project](https://github.com/matrix-org/matrix-analytics-events)
branch: sync-analytics-plan
base: develop base: develop

View file

@ -6,7 +6,7 @@ on:
jobs: jobs:
move_needs_info_issues: 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 runs-on: ubuntu-latest
steps: steps:
- uses: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338 - uses: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338
@ -17,22 +17,24 @@ jobs:
label-name: "X-Needs-Info" label-name: "X-Needs-Info"
add_priority_design_issues_to_project: 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 runs-on: ubuntu-latest
if: > if: >
contains(github.event.issue.labels.*.name, 'X-Needs-Design') && contains(github.event.issue.labels.*.name, 'X-Needs-Design') &&
(contains(github.event.issue.labels.*.name, 'O-Frequent') || (contains(github.event.issue.labels.*.name, 'S-Critical') &&
contains(github.event.issue.labels.*.name, 'O-Occasional')) && (contains(github.event.issue.labels.*.name, 'O-Frequent') ||
(contains(github.event.issue.labels.*.name, 'S-Critical') || contains(github.event.issue.labels.*.name, 'O-Occasional')) ||
contains(github.event.issue.labels.*.name, 'S-Major') || contains(github.event.issue.labels.*.name, 'S-Major') &&
contains(github.event.issue.labels.*.name, 'S-Minor')) contains(github.event.issue.labels.*.name, 'O-Frequent') ||
contains(github.event.issue.labels.*.name, 'A11y') &&
contains(github.event.issue.labels.*.name, 'O-Frequent'))
steps: steps:
- uses: octokit/graphql-action@v2.x - uses: octokit/graphql-action@v2.x
id: add_to_project id: add_to_project
with: with:
headers: '{"GraphQL-Features": "projects_next_graphql"}' headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: | query: |
mutation add_to_project($projectid:String!,$contentid:String!) { mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
projectNextItem { projectNextItem {
id id
@ -45,40 +47,33 @@ jobs:
PROJECT_ID: "PN_kwDOAM0swc0sUA" PROJECT_ID: "PN_kwDOAM0swc0sUA"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_spaces_issues: # delight_issues_to_board:
name: Move Spaces issues to Delight project board # name: Spaces issues to new Delight project board
runs-on: ubuntu-latest # runs-on: ubuntu-latest
if: > # if: >
contains(github.event.issue.labels.*.name, 'A-Spaces') || # contains(github.event.issue.labels.*.name, 'A-Spaces') ||
contains(github.event.issue.labels.*.name, 'A-Space-Settings') || # contains(github.event.issue.labels.*.name, 'A-Space-Settings') ||
contains(github.event.issue.labels.*.name, 'A-Subspaces') # contains(github.event.issue.labels.*.name, 'A-Subspaces')
steps: # steps:
- uses: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338 # - uses: octokit/graphql-action@v2.x
with: # with:
action-token: "${{ secrets.ELEMENT_BOT_TOKEN }}" # headers: '{"GraphQL-Features": "projects_next_graphql"}'
project-url: "https://github.com/orgs/vector-im/projects/6" # query: |
column-name: "📥 Inbox" # mutation add_to_project($projectid:ID!,$contentid:ID!) {
label-name: "A-Spaces" # addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
- uses: octokit/graphql-action@v2.x # projectNextItem {
id: add_to_delight2 # id
with: # }
headers: '{"GraphQL-Features": "projects_next_graphql"}' # }
query: | # }
mutation add_to_project($projectid:String!,$contentid:String!) { # projectid: ${{ env.PROJECT_ID }}
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { # contentid: ${{ github.event.issue.node_id }}
projectNextItem { # env:
id # PROJECT_ID: "PN_kwDOAM0swc1HvQ"
} # GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PN_kwDOAM0swc1HvQ"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_voice-message_issues: 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 runs-on: ubuntu-latest
if: > if: >
contains(github.event.issue.labels.*.name, 'A-Voice Messages') contains(github.event.issue.labels.*.name, 'A-Voice Messages')
@ -87,7 +82,7 @@ jobs:
with: with:
headers: '{"GraphQL-Features": "projects_next_graphql"}' headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: | query: |
mutation add_to_project($projectid:String!,$contentid:String!) { mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
projectNextItem { projectNextItem {
id id
@ -101,7 +96,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_threads_issues: move_threads_issues:
name: Move A-Threads to Thread board name: A-Threads to Thread board
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: > if: >
contains(github.event.issue.labels.*.name, 'A-Threads') contains(github.event.issue.labels.*.name, 'A-Threads')
@ -110,7 +105,7 @@ jobs:
with: with:
headers: '{"GraphQL-Features": "projects_next_graphql"}' headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: | query: |
mutation add_to_project($projectid:String!,$contentid:String!) { mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
projectNextItem { projectNextItem {
id id
@ -122,3 +117,26 @@ jobs:
env: env:
PROJECT_ID: "PN_kwDOAM0swc0rRA" PROJECT_ID: "PN_kwDOAM0swc0rRA"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_message_bubbles_issues:
name: A-Message-Bubbles to Message bubbles board
runs-on: ubuntu-latest
if: >
contains(github.event.issue.labels.*.name, 'A-Message-Bubbles')
steps:
- uses: octokit/graphql-action@v2.x
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
projectNextItem {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PN_kwDOAM0swc3m-g"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}

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: on:
issues: issues:

View file

@ -24,6 +24,7 @@
<w>pbkdf</w> <w>pbkdf</w>
<w>pids</w> <w>pids</w>
<w>pkcs</w> <w>pkcs</w>
<w>posthog</w>
<w>previewable</w> <w>previewable</w>
<w>previewables</w> <w>previewables</w>
<w>pstn</w> <w>pstn</w>

View file

@ -1,3 +1,65 @@
Changes in Element v1.3.12 (2021-12-20)
=======================================
Bugfixes 🐛
----------
- Fixing emoji related crashes on android 8.1.1 and below ([#4769](https://github.com/vector-im/element-android/issues/4769))
Changes in Element v1.3.11 (2021-12-17)
=======================================
Bugfixes 🐛
----------
- Fixing proximity sensor still being active after a call ([#2467](https://github.com/vector-im/element-android/issues/2467))
- Fix name and shield are truncated in the room detail screen ([#4700](https://github.com/vector-im/element-android/issues/4700))
- Call banner: center text vertically ([#4710](https://github.com/vector-im/element-android/issues/4710))
- Fixes unable to render messages by allowing them to render whilst the emoji library is initialising ([#4733](https://github.com/vector-im/element-android/issues/4733))
- Fix app crash uppon long press on a reply event ([#4742](https://github.com/vector-im/element-android/issues/4742))
- Fixes crash when launching rooms which contain emojis in the emote content on android 12+ ([#4743](https://github.com/vector-im/element-android/issues/4743))
Other changes
-------------
- Avoids leaking the activity windows when loading dialogs are displaying ([#4713](https://github.com/vector-im/element-android/issues/4713))
Changes in Element v1.3.10 (2021-12-14)
=======================================
Features ✨
----------
- Poll Feature - Render in timeline ([#4653](https://github.com/vector-im/element-android/issues/4653))
- Updates URL previews to match latest designs ([#4278](https://github.com/vector-im/element-android/issues/4278))
- Setup Analytics framework using PostHog. Analytics are disabled by default. Opt-in screen not automatically displayed yet. ([#4559](https://github.com/vector-im/element-android/issues/4559))
- Create a legal screen in the setting to group all the different policies. ([#4660](https://github.com/vector-im/element-android/issues/4660))
- Add a help section in the settings. ([#4638](https://github.com/vector-im/element-android/issues/4638))
- MSC2732: Olm fallback keys ([#3473](https://github.com/vector-im/element-android/issues/3473))
Bugfixes 🐛
----------
- Fixes message menu showing when copying message urls ([#4324](https://github.com/vector-im/element-android/issues/4324))
- Fix lots of integration tests by introducing TestMatrix class and MatrixWorkerFactory. ([#4546](https://github.com/vector-im/element-android/issues/4546))
- Fix empty Dev Tools screen issue. ([#4592](https://github.com/vector-im/element-android/issues/4592))
- Fix for outgoing voip call via sip bridge failing after 1 minute. ([#4621](https://github.com/vector-im/element-android/issues/4621))
- Update log warning for call selection during voip calls. ([#4636](https://github.com/vector-im/element-android/issues/4636))
- Fix possible crash when having identical subspaces in multiple root spaces ([#4693](https://github.com/vector-im/element-android/issues/4693))
- Fix a crash in the timeline with some Emojis. Also migrate to androidx.emoji2 ([#4698](https://github.com/vector-im/element-android/issues/4698))
- At the very first room search after opening the app sometimes no results are displayed ([#4600](https://github.com/vector-im/element-android/issues/4600))
Other changes
-------------
- Upgrade OLM to v3.2.7 and get it from our maven repository. ([#4647](https://github.com/vector-im/element-android/issues/4647))
- Add explicit dependency location, regarding the several maven repository. Also update some libraries (flexbox and alerter), and do some cleanup. ([#4670](https://github.com/vector-im/element-android/issues/4670))
- Introducing feature flagging to the login and notification settings flows ([#4626](https://github.com/vector-im/element-android/issues/4626))
- There is no need to call job.cancel() when we are using viewModelScope() ([#4602](https://github.com/vector-im/element-android/issues/4602))
- Debounce some clicks ([#4645](https://github.com/vector-im/element-android/issues/4645))
- Improve issue automation workflows ([#4617](https://github.com/vector-im/element-android/issues/4617))
- Add automation to move message bubbles issues to message bubbles board. ([#4666](https://github.com/vector-im/element-android/issues/4666))
- Fix graphql warning in issue workflow automation ([#4671](https://github.com/vector-im/element-android/issues/4671))
- Cleanup the layout files ([#4604](https://github.com/vector-im/element-android/issues/4604))
- Cleanup id ref. Use type views instead ([#4650](https://github.com/vector-im/element-android/issues/4650))
Changes in Element v1.3.9 (2021-12-01) Changes in Element v1.3.9 (2021-12-01)
====================================== ======================================

View file

@ -1,12 +1,11 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
apply from: 'dependencies.gradle' apply from: 'dependencies.gradle'
apply from: 'dependencies_groups.gradle'
repositories { repositories {
google() google()
jcenter()
maven { maven {
url "https://plugins.gradle.org/m2/" url "https://plugins.gradle.org/m2/"
} }
@ -37,50 +36,50 @@ allprojects {
apply plugin: "org.jlleitschuh.gradle.ktlint" apply plugin: "org.jlleitschuh.gradle.ktlint"
repositories { repositories {
// For olm library. This has to be declared first, to ensure that Olm library is not downloaded from another repo // For olm library.
maven {
url 'https://gitlab.matrix.org/api/v4/projects/27/packages/maven'
content {
groups.olm.regex.each { includeGroupByRegex it }
groups.olm.group.each { includeGroup it }
}
}
maven { maven {
url 'https://jitpack.io' url 'https://jitpack.io'
content { content {
// Use this repo only for olm library groups.jitpack.regex.each { includeGroupByRegex it }
includeGroupByRegex "org\\.matrix\\.gitlab\\.matrix-org" groups.jitpack.group.each { includeGroup it }
// And also for FilePicker
includeGroupByRegex "com\\.github\\.jaiselrahman"
// And monarchy
includeGroupByRegex "com\\.github\\.Zhuinden"
// And ucrop
includeGroupByRegex "com\\.github\\.yalantis"
// JsonViewer
includeGroupByRegex 'com\\.github\\.BillCarsonFr'
// PhotoView
includeGroupByRegex 'com\\.github\\.chrisbanes'
// PFLockScreen-Android
includeGroupByRegex 'com\\.github\\.vector-im'
// DraggableView
includeGroupByRegex 'com\\.github\\.hyuwah'
// UnifiedPush
includeGroupByRegex 'com\\.github\\.UnifiedPush'
// SchildiChat
includeGroupByRegex 'com\\.github\\.SchildiChat'
// Chat effects
includeGroupByRegex 'com\\.github\\.jetradarmobile'
includeGroupByRegex 'nl\\.dionsegijn'
// Voice RecordView
includeGroupByRegex 'com\\.github\\.Armen101'
} }
} }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
// Jitsi repo // Jitsi repo
maven { maven {
url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.10.0" url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.10.0"
// Note: to test Jitsi release you can use a local file like this: // Note: to test Jitsi release you can use a local file like this:
// url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0" // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0"
content {
groups.jitsi.regex.each { includeGroupByRegex it }
groups.jitsi.group.each { includeGroup it }
}
}
google {
content {
groups.google.regex.each { includeGroupByRegex it }
groups.google.group.each { includeGroup it }
}
}
mavenCentral {
content {
groups.mavenCentral.regex.each { includeGroupByRegex it }
groups.mavenCentral.group.each { includeGroup it }
}
}
//noinspection JcenterRepositoryObsolete
jcenter {
content {
groups.jcenter.regex.each { includeGroupByRegex it }
groups.jcenter.group.each { includeGroup it }
}
} }
google()
mavenCentral()
jcenter()
} }
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {

View file

@ -7,11 +7,11 @@ ext.versions = [
'targetCompat' : JavaVersion.VERSION_1_8, 'targetCompat' : JavaVersion.VERSION_1_8,
] ]
def gradle = "7.0.3" def gradle = "7.0.4"
// Ref: https://kotlinlang.org/releases.html // Ref: https://kotlinlang.org/releases.html
def kotlin = "1.5.31" def kotlin = "1.5.31"
def kotlinCoroutines = "1.5.2" def kotlinCoroutines = "1.5.2"
def dagger = "2.40.3" def dagger = "2.40.5"
def retrofit = "2.9.0" def retrofit = "2.9.0"
def arrow = "0.8.2" def arrow = "0.8.2"
def markwon = "4.6.2" def markwon = "4.6.2"
@ -19,7 +19,7 @@ def moshi = "1.12.0"
def lifecycle = "2.4.0" def lifecycle = "2.4.0"
def flowBinding = "1.2.0" def flowBinding = "1.2.0"
def epoxy = "4.6.2" def epoxy = "4.6.2"
def mavericks = "2.4.0" def mavericks = "2.5.0"
def glide = "4.12.0" def glide = "4.12.0"
def bigImageViewer = "1.8.1" def bigImageViewer = "1.8.1"
def jjwt = "0.11.2" def jjwt = "0.11.2"

203
dependencies_groups.gradle Normal file
View file

@ -0,0 +1,203 @@
ext.groups = [
jitpack : [
regex: [
],
group: [
'com.github.Armen101',
'com.github.BillCarsonFr',
'com.github.chrisbanes',
'com.github.hyuwah',
'com.github.jetradarmobile',
'com.github.SchildiChat',
'com.github.tapadoo',
'com.github.UnifiedPush',
'com.github.vector-im',
'com.github.yalantis',
'com.github.Zhuinden',
]
],
olm : [
regex: [
],
group: [
'org.matrix.android',
]
],
jitsi : [
regex: [
],
group: [
'com.facebook.react',
'org.jitsi.react',
'org.webkit',
]
],
google : [
regex: [
'androidx\\..*',
'com\\.android\\.tools\\..*',
'com\\.google\\.android\\..*',
],
group: [
'com.google.firebase',
'com.android',
'com.android.tools',
]
],
mavenCentral: [
regex: [
],
group: [
'com.adevinta.android',
'com.airbnb.android',
'com.almworks.sqlite4java',
'com.arthenica',
'com.atlassian.commonmark',
'com.atlassian.pom',
'com.beust',
'com.davemorrissey.labs',
'com.dropbox.core',
'com.facebook.fresco',
'com.facebook.infer.annotation',
'com.facebook.soloader',
'com.facebook.stetho',
'com.fasterxml',
'com.fasterxml.jackson',
'com.fasterxml.jackson.core',
'com.gabrielittner.threetenbp',
'com.getkeepsafe.relinker',
'com.github.bumptech.glide',
'com.github.filippudak',
'com.github.filippudak.progresspieview',
'com.github.javaparser',
'com.github.piasy',
'com.github.shyiko.klob',
'com.google',
'com.google.auto.service',
'com.google.auto.value',
'com.google.code.findbugs',
'com.google.code.gson',
'com.google.dagger',
'com.google.devtools.ksp',
'com.google.errorprone',
'com.google.googlejavaformat',
'com.google.guava',
'com.google.j2objc',
'com.google.jimfs',
'com.google.protobuf',
'com.google.zxing',
'com.googlecode.htmlcompressor',
'com.googlecode.json-simple',
'com.googlecode.libphonenumber',
'com.ibm.icu',
'com.jakewharton.android.repackaged',
'com.jakewharton.timber',
'com.linkedin.dexmaker',
'com.nulab-inc',
'com.otaliastudios.opengl',
'com.parse.bolts',
'com.pinterest',
'com.pinterest.ktlint',
'com.posthog.android',
'com.squareup',
'com.squareup.duktape',
'com.squareup.moshi',
'com.squareup.okhttp3',
'com.squareup.okio',
'com.squareup.retrofit2',
'com.sun.activation',
'com.sun.istack',
'com.sun.xml.bind',
'com.sun.xml.bind.mvn',
'com.sun.xml.fastinfoset',
'com.thoughtworks.qdox',
'com.vanniktech',
'commons-cli',
'commons-codec',
'commons-io',
'commons-logging',
'info.picocli',
'io.arrow-kt',
'io.github.detekt.sarif4k',
'io.github.reactivecircus.flowbinding',
'io.jsonwebtoken',
'io.kindedj',
'io.mockk',
'io.noties.markwon',
'io.reactivex.rxjava2',
'io.realm',
'it.unimi.dsi',
'jakarta.activation',
'jakarta.xml.bind',
'javax.annotation',
'javax.inject',
'jline',
'jp.wasabeef',
'junit',
'me.leolin',
'me.saket',
'net.bytebuddy',
'net.java',
'net.java.dev.jna',
'net.lachlanmckee',
'net.ltgt.gradle.incap',
'net.sf.jopt-simple',
'net.sf.kxml',
'nl.dionsegijn',
'org.amshove.kluent',
'org.apache',
'org.apache.ant',
'org.apache.commons',
'org.apache.httpcomponents',
'org.apache.sanselan',
'org.bouncycastle',
'org.checkerframework',
'org.codehaus',
'org.codehaus.groovy',
'org.codehaus.mojo',
'org.eclipse.ee4j',
'org.ec4j.core',
'org.glassfish.jaxb',
'org.hamcrest',
'org.jetbrains',
'org.jetbrains.intellij.deps',
'org.jetbrains.kotlin',
'org.jetbrains.kotlinx',
'org.jsoup',
'org.junit',
'org.junit.jupiter',
'org.junit.platform',
'org.jvnet.staxex',
'org.mockito',
'org.mongodb',
'org.objenesis',
'org.opentest4j',
'org.ow2',
'org.ow2.asm',
'org.ow2.asm',
'org.reactivestreams',
'org.robolectric',
'org.slf4j',
'org.sonatype.oss',
'org.testng',
'org.threeten',
'xerces',
'xml-apis',
]
],
jcenter : [
regex: [
],
group: [
'com.amulyakhare',
'com.otaliastudios',
'com.yqritc',
// https://github.com/cmelchior/realmfieldnameshelper/issues/42
'dk.ilios',
'im.dlg',
'me.dm7.barcodescanner',
'me.gujun.android',
]
]
]

16
docs/analytics.md Normal file
View file

@ -0,0 +1,16 @@
# Analytics in Element
## Solution
Element is using PostHog to send analytics event.
We ask for the user to give consent before sending any analytics data.
## How to add a new Event
The analytics plan is shared between all Element clients. To add an Event, please open a PR to this project: https://github.com/matrix-org/matrix-analytics-events
Then, once the PR has been merged, you can run the tool `import_analytic_plan.sh` to import the plan to Element, and then you can use the new Event. Note that this tool is run by Github action once a week.
## Forks of Element
Analytics on forks are disabled by default. Please refer to AnalyticsConfig and there implementation to setup analytics on your project.

View file

@ -1,2 +1,2 @@
Hlavní změny v této verzi: Opravy chyb týkající se především oznámení. Hlavní změny v této verzi: Opravy chyb týkající se především oznámení.
Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.7 Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: Opravy chyb!
Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -1,2 +1,2 @@
Hauptänderungen: Fehler bei Benachrichtigungen gefixt Hauptänderungen: Fehler bei Benachrichtigungen gefixt
Ganze Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.7 Ganze Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
Änderungen: Verschiedene Fehler behoben
Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -0,0 +1,2 @@
Main changes in this version: Add support for polls (in labs). New URL preview design.
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.10

View file

@ -0,0 +1,2 @@
Main changes in this version: Bug fixes!
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.11

View file

@ -0,0 +1,2 @@
Main changes in this version: Bug fixes!
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.12

View file

@ -1,2 +1,2 @@
Põhilised muutused selles versioonis: erinevad veaparandused, neist enamus on seotud teavitustega. Põhilised muutused selles versioonis: erinevad veaparandused, neist enamus on seotud teavitustega.
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.7 Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
Põhilised muutused selles versioonis: pinu veaparandusi!
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -1,2 +1,2 @@
تغییرات اصلی در این نگارش: رفع اشکال‌هایی عمدتاً مربوط به آگاهی‌ها. تغییرات اصلی در این نگارش: رفع اشکال‌هایی عمدتاً مربوط به آگاهی‌ها.
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.7 گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
تغییرات عمده در این نگارش: رفع مشکل‌ها!
گزارش دگرکونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -1,2 +1,2 @@
Principaux changements pour cette version : corrections de problèmes, principalement sur les notifications Principaux changements pour cette version : corrections de problèmes, principalement sur les notifications
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.7 Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
Principaux changements pour cette version : corrections de bugs !
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -1,2 +1,2 @@
Fő változás ebben a verzióban: Értesítési hibajavítások Fő változás ebben a verzióban: Értesítési hibajavítások
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.7 Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
Főbb változtatások ebben a verzióban: Hibajavítások
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -1,2 +1,2 @@
Perubahan utama di versi ini: Perbaikan bug terutama untuk notifikasinya. Perubahan utama di versi ini: Perbaikan bug terutama untuk notifikasinya.
Changelog lengkap: https://github.com/vector-im/element-android/releases/tag/v1.3.7 Changelog lengkap: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
Perubahan utama di versi ini: Beberapa perbaikan bug!
Changelog lengkap: https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -26,7 +26,7 @@ Element menempatkan Anda dalam kendali dengan cara yang berbeda:
2. Host sendiri akun Anda dengan menjalankan server pada infrastruktur IT Anda sendiri 2. Host sendiri akun Anda dengan menjalankan server pada infrastruktur IT Anda sendiri
3. Daftar untuk akun di server khusus dengan berlangganan platform hosting Layanan Matrix Element 3. Daftar untuk akun di server khusus dengan berlangganan platform hosting Layanan Matrix Element
<b>Pesan terbuka dan kolaborasi</b> <b>Perpesanan dan kolaborasi terbuka</b>
Anda dapat mengobrol dengan siapa saja di jaringan Matrix, jika mereka menggunakan Element, aplikasi Matrix lain atau bahkan menggunakan aplikasi perpesanan yang berbeda. Anda dapat mengobrol dengan siapa saja di jaringan Matrix, jika mereka menggunakan Element, aplikasi Matrix lain atau bahkan menggunakan aplikasi perpesanan yang berbeda.
<b>Sangat aman</b> <b>Sangat aman</b>

View file

@ -1,2 +1,2 @@
Modifiche principali in questa versione: correzioni riguardo le notifiche. Modifiche principali in questa versione: correzioni riguardo le notifiche.
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.7 Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
Modifiche principali in questa versione: correzioni di errori!
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -1,2 +1,2 @@
Principais mudanças nesta versão: Consertos de bugs principalmente quanto às notificações. Principais mudanças nesta versão: Consertos de bugs principalmente quanto às notificações.
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.7 Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
Principais mudanças nesta versão: Consertos de bugs!
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: Opravy chýb týkajúce sa najmä oznámení.
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: Opravy chýb!
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -1 +1 @@
Zabezpečené konverzácie a VoIP. Ochráňte vaše údaje pred tretími stranami. Skupinový messenger - šifrované správy, skupinové konverzácie a videohovory

View file

@ -0,0 +1,2 @@
Ndryshimet kryesore në këtë version: Ndreqje të metash të lidhura kryesisht me njoftimet.
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
Ndryshimet kryesore në këtë version: Ndreqje të metash!
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -1,2 +1,2 @@
Huvudsakliga ändringar i den här versionen: Buggfixar som huvudsakligen rör aviseringar. Huvudsakliga ändringar i den här versionen: Buggfixar som huvudsakligen rör aviseringar.
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.7 Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
Huvudsakliga ändringar i den här versionen: Buggfixar!
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -1,2 +1,2 @@
Основні зміни в цій версії: виправлення помилок в основному у повідомленнях. Основні зміни в цій версії: виправлення помилок в основному у повідомленнях.
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.7 Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
Основні зміни у цій версії: Виправлення помилок!
Повний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -1,7 +1,7 @@
Element — це і безпечний месенджер, і застосунок для співпраці команди, який ідеально підходить для групових бесід під час віддаленої роботи. Цей застосунок для спілкування застосовує наскрізне шифрування для забезпечення відеоконференцій, обміну файлами та голосових викликів. Element — це і безпечний месенджер, і застосунок для співпраці команди, який ідеально підходить спілкування групами під час віддаленої роботи. Цей застосунок для спілкування використовує наскрізне шифрування для забезпечення відеоконференцій, обміну файлами та голосових викликів.
<b>Можливості Element включають:</b> <b>Можливості Element включають:</b>
- Розширені засоби спілкування в Інтернеті - Розширені засоби онлайн-спілкування
- Повністю зашифровані повідомлення для надання можливості безпечнішого корпоративного спілкування, навіть для віддалених працівників - Повністю зашифровані повідомлення для надання можливості безпечнішого корпоративного спілкування, навіть для віддалених працівників
- Децентралізований чат на основі відкритого коду Matrix - Децентралізований чат на основі відкритого коду Matrix
- Безпечний обмін файлами із зашифрованими даними для керування проєктами - Безпечний обмін файлами із зашифрованими даними для керування проєктами
@ -33,10 +33,10 @@ Element надає такі можливості на вибір:
Справжнє наскрізне шифрування (лише учасники бесіди можуть розшифровувати повідомлення) та взаємне підписування пристроїв. Справжнє наскрізне шифрування (лише учасники бесіди можуть розшифровувати повідомлення) та взаємне підписування пристроїв.
<b>Повноцінні спілкування та інтеграція</b> <b>Повноцінні спілкування та інтеграція</b>
Обмін повідомленнями, голосові та відеовиклики, обмін файлами, спільний доступ до екрана та ціла купа інтеграцій, ботів та розширень. Створюйте кімнати, спільноти, залишайтеся на зв’язку та виконуйте завдання. Обмін повідомленнями, голосові та відеовиклики, обмін файлами, спільний доступ до екрана та ціла купа інтеграцій, ботів та віджетів. Створюйте кімнати, спільноти, залишайтеся на зв’язку та виконуйте завдання.
<b>Продовжуйте, де зупинилися</b> <b>Продовжуйте, де зупинилися</b>
Залишайтеся на зв'язку, де б ви не знаходились, з повністю синхронізованою історією повідомлень на всіх своїх пристроях та в Інтернеті за адресою https://app.element.io Залишайтеся на зв'язку, де б ви не знаходились, з повністю синхронізованою історією повідомлень на всіх своїх пристроях та в Інтернеті за адресою https://app.element.io
<b>Відкритий код</b> <b>Відкритий код</b>
Element для Android це проєкт з відкритим кодом, розміщений GitHub. Будь ласка, повідомте про помилки та/або сприяйте його розвитку на https://github.com/vector-im/element-android Element для Android це проєкт з відкритим кодом, розміщений на GitHub. Повідомляйте про помилки та/або допомагайте його розвитку на https://github.com/vector-im/element-android

View file

@ -1,2 +1,2 @@
此版本的主要变化:主要关于通知的错误修复。 此版本的主要变化:主要关于通知的错误修复。
完整更新日志https://github.com/vector-im/element-android/releases/tag/v1.3.7 完整更新日志https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
此版本主要变化Bug 修复!
完整更新日志https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -1,2 +1,2 @@
此版本中的主要變動:主要關於通知的臭蟲修復。 此版本中的主要變動:主要關於通知的臭蟲修復。
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.3.7 完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2

View file

@ -0,0 +1,2 @@
此版本中的主要變動:臭蟲修復!
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.3.8

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=00b273629df4ce46e68df232161d5a7c4e495b9a029ce6e0420f071e21316867 distributionSha256Sum=dd54e87b4d7aa8ff3c6afb0f7805aa121d4b70bca55b8c9b1b896eb103184582
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View file

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

View file

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

View file

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

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="width_percent">0.6</dimen>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="width_percent">0.5</dimen>
</resources>

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="width_percent">1</dimen>
</resources>

View file

@ -9,7 +9,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath "io.realm:realm-gradle-plugin:10.8.1" classpath "io.realm:realm-gradle-plugin:10.9.0"
} }
} }
@ -31,7 +31,7 @@ android {
// that the app's state is completely cleared between tests. // that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true' testInstrumentationRunnerArguments clearPackageData: 'true'
buildConfigField "String", "SDK_VERSION", "\"1.3.9\"" buildConfigField "String", "SDK_VERSION", "\"1.3.12\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
resValue "string", "git_sdk_revision", "\"${gitRevision()}\"" resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
@ -140,8 +140,8 @@ dependencies {
implementation libs.arrow.core implementation libs.arrow.core
implementation libs.arrow.instances implementation libs.arrow.instances
// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm // olm lib is now hosted by maven at https://gitlab.matrix.org/api/v4/projects/27/packages/maven
implementation 'org.matrix.gitlab.matrix-org:olm:3.2.4' implementation 'org.matrix.android:olm:3.2.7'
// DI // DI
implementation libs.dagger.dagger implementation libs.dagger.dagger
@ -158,10 +158,10 @@ dependencies {
implementation libs.apache.commonsImaging implementation libs.apache.commonsImaging
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.38' implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.39'
testImplementation libs.tests.junit testImplementation libs.tests.junit
testImplementation 'org.robolectric:robolectric:4.7.2' testImplementation 'org.robolectric:robolectric:4.7.3'
//testImplementation 'org.robolectric:shadows-support-v4:3.0' //testImplementation 'org.robolectric:shadows-support-v4:3.0'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation libs.mockk.mockk testImplementation libs.mockk.mockk

View file

@ -20,9 +20,10 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.test.internal.runner.junit4.statement.UiThreadStatement 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.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -30,7 +31,6 @@ import kotlinx.coroutines.withTimeout
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue 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.MatrixCallback
import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig 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.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.session.sync.SyncState
import java.util.ArrayList import timber.log.Timber
import java.util.UUID import java.util.UUID
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -56,13 +56,14 @@ import java.util.concurrent.TimeUnit
*/ */
class CommonTestHelper(context: Context) { 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 { init {
UiThreadStatement.runOnUiThread { UiThreadStatement.runOnUiThread {
Matrix.initialize( TestMatrix.initialize(
context, context,
MatrixConfiguration( MatrixConfiguration(
applicationFlavor = "TestFlavor", 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 { fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session {
@ -95,33 +96,47 @@ class CommonTestHelper(context: Context) {
* *
* @param session the session to sync * @param session the session to sync
*/ */
@Suppress("EXPERIMENTAL_API_USAGE") fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis * 10) {
fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis) {
val lock = CountDownLatch(1) val lock = CountDownLatch(1)
coroutineScope.launch {
val job = GlobalScope.launch(Dispatchers.Main) { session.startSync(true)
session.open() val syncLiveData = session.getSyncStateLive()
} val syncObserver = object : Observer<SyncState> {
runBlocking { job.join() } override fun onChanged(t: SyncState?) {
if (session.hasAlreadySynced()) {
session.startSync(true) lock.countDown()
syncLiveData.removeObserver(this)
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)
} }
} }
syncLiveData.observeForever(syncObserver)
} }
GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) }
await(lock, timeout) 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 * 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 * @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> { 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 sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
val latch = CountDownLatch(1) val timeline = room.createTimeline(null, TimelineSettings(10))
val timelineListener = object : Timeline.Listener { timeline.start()
override fun onTimelineFailure(throwable: Throwable) { waitWithLatch(timeout + 1_000L * nbOfMessages) { latch ->
} val timelineListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) {
}
override fun onNewTimelineEvents(eventIds: List<String>) { override fun onNewTimelineEvents(eventIds: List<String>) {
// noop // noop
} }
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) { override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
val newMessages = snapshot val newMessages = snapshot
.filter { it.root.sendState == SendState.SYNCED } .filter { it.root.sendState == SendState.SYNCED }
.filter { it.root.getClearType() == EventType.MESSAGE } .filter { it.root.getClearType() == EventType.MESSAGE }
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true } .filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
if (newMessages.size == nbOfMessages) { Timber.v("New synced message size: ${newMessages.size}")
sentEvents.addAll(newMessages) if (newMessages.size == nbOfMessages) {
// Remove listener now, if not at the next update sendEvents could change sentEvents.addAll(newMessages)
timeline.removeListener(this) // Remove listener now, if not at the next update sendEvents could change
latch.countDown() 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() timeline.dispose()
// Check that all events has been created // Check that all events has been created
assertEquals("Message number do not match $sentEvents", nbOfMessages.toLong(), sentEvents.size.toLong()) assertEquals("Message number do not match $sentEvents", nbOfMessages.toLong(), sentEvents.size.toLong())
return sentEvents 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 ***************************************************************************** // PRIVATE METHODS *****************************************************************************
/** /**
@ -239,10 +265,10 @@ class CommonTestHelper(context: Context) {
assertTrue(registrationResult is RegistrationResult.Success) assertTrue(registrationResult is RegistrationResult.Success)
val session = (registrationResult as RegistrationResult.Success).session val session = (registrationResult as RegistrationResult.Success).session
session.open()
if (sessionTestParams.withInitialSync) { if (sessionTestParams.withInitialSync) {
syncSession(session, 60_000) syncSession(session, 60_000)
} }
return session return session
} }
@ -267,7 +293,7 @@ class CommonTestHelper(context: Context) {
.getLoginWizard() .getLoginWizard()
.login(userName, password, "myDevice") .login(userName, password, "myDevice")
} }
session.open()
if (sessionTestParams.withInitialSync) { if (sessionTestParams.withInitialSync) {
syncSession(session) syncSession(session)
} }
@ -332,22 +358,21 @@ class CommonTestHelper(context: Context) {
assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)) assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
} }
@Suppress("EXPERIMENTAL_API_USAGE") suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { while (true) {
GlobalScope.launch { delay(1000)
while (true) { if (condition()) {
delay(1000) latch.countDown()
if (condition()) { return
latch.countDown()
return@launch
}
} }
} }
} }
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) val latch = CountDownLatch(1)
block(latch) coroutineScope.launch(dispatcher) {
block(latch)
}
await(latch, timeout) await(latch, timeout)
} }

View file

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

View file

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

View file

@ -18,10 +18,39 @@ package org.matrix.android.sdk.common
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides
import org.matrix.android.sdk.internal.di.MatrixComponent 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 @Module
internal abstract class TestModule { internal abstract class TestModule {
@Binds @Binds
abstract fun providesMatrixComponent(testMatrixComponent: TestMatrixComponent): MatrixComponent 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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -22,7 +22,6 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith 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.KeysVersion
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import java.util.ArrayList
import java.util.Collections import java.util.Collections
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@ -51,9 +49,9 @@ import java.util.concurrent.CountDownLatch
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
class KeysBackupTest : InstrumentedTest { class KeysBackupTest : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context()) private val testHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) private val cryptoTestHelper = CryptoTestHelper(testHelper)
private val mKeysBackupTestHelper = KeysBackupTestHelper(mTestHelper, mCryptoTestHelper) private val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
/** /**
* - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys * - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
@ -62,7 +60,7 @@ class KeysBackupTest : InstrumentedTest {
*/ */
@Test @Test
fun roomKeysTest_testBackupStore_ok() { fun roomKeysTest_testBackupStore_ok() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
// From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys // From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
@ -92,7 +90,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
cryptoTestData.cleanUp(mTestHelper) cryptoTestData.cleanUp(testHelper)
} }
/** /**
@ -100,7 +98,7 @@ class KeysBackupTest : InstrumentedTest {
*/ */
@Test @Test
fun prepareKeysBackupVersionTest() { fun prepareKeysBackupVersionTest() {
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams) val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
assertNotNull(bobSession.cryptoService().keysBackupService()) assertNotNull(bobSession.cryptoService().keysBackupService())
@ -110,7 +108,7 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> { val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
keysBackup.prepareKeysBackupVersion(null, null, it) keysBackup.prepareKeysBackupVersion(null, null, it)
} }
@ -120,7 +118,7 @@ class KeysBackupTest : InstrumentedTest {
assertNotNull(megolmBackupCreationInfo.recoveryKey) assertNotNull(megolmBackupCreationInfo.recoveryKey)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
mTestHelper.signOutAndClose(bobSession) testHelper.signOutAndClose(bobSession)
} }
/** /**
@ -128,7 +126,7 @@ class KeysBackupTest : InstrumentedTest {
*/ */
@Test @Test
fun createKeysBackupVersionTest() { 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() val keysBackup = bobSession.cryptoService().keysBackupService()
@ -136,14 +134,14 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> { val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
keysBackup.prepareKeysBackupVersion(null, null, it) keysBackup.prepareKeysBackupVersion(null, null, it)
} }
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
// Create the version // Create the version
mTestHelper.doSync<KeysVersion> { testHelper.doSync<KeysVersion> {
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it) keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
} }
@ -151,7 +149,7 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(keysBackup.isEnabled) assertTrue(keysBackup.isEnabled)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
mTestHelper.signOutAndClose(bobSession) testHelper.signOutAndClose(bobSession)
} }
/** /**
@ -160,8 +158,9 @@ class KeysBackupTest : InstrumentedTest {
*/ */
@Test @Test
fun backupAfterCreateKeysBackupVersionTest() { fun backupAfterCreateKeysBackupVersionTest() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
keysBackupTestHelper.waitForKeybackUpBatching()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
val latch = CountDownLatch(1) val latch = CountDownLatch(1)
@ -171,9 +170,9 @@ class KeysBackupTest : InstrumentedTest {
val stateObserver = StateObserver(keysBackup, latch, 5) 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 nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true) val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
@ -191,7 +190,7 @@ class KeysBackupTest : InstrumentedTest {
KeysBackupState.ReadyToBackUp KeysBackupState.ReadyToBackUp
) )
) )
cryptoTestData.cleanUp(mTestHelper) cryptoTestData.cleanUp(testHelper)
} }
/** /**
@ -199,13 +198,13 @@ class KeysBackupTest : InstrumentedTest {
*/ */
@Test @Test
fun backupAllGroupSessionsTest() { fun backupAllGroupSessionsTest() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
val stateObserver = StateObserver(keysBackup) val stateObserver = StateObserver(keysBackup)
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
// Check that backupAllGroupSessions returns valid data // Check that backupAllGroupSessions returns valid data
val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false) val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
@ -214,7 +213,7 @@ class KeysBackupTest : InstrumentedTest {
var lastBackedUpKeysProgress = 0 var lastBackedUpKeysProgress = 0
mTestHelper.doSync<Unit> { testHelper.doSync<Unit> {
keysBackup.backupAllGroupSessions(object : ProgressListener { keysBackup.backupAllGroupSessions(object : ProgressListener {
override fun onProgress(progress: Int, total: Int) { override fun onProgress(progress: Int, total: Int) {
assertEquals(nbOfKeys, total) assertEquals(nbOfKeys, total)
@ -230,7 +229,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(mTestHelper) cryptoTestData.cleanUp(testHelper)
} }
/** /**
@ -243,7 +242,7 @@ class KeysBackupTest : InstrumentedTest {
*/ */
@Test @Test
fun testEncryptAndDecryptKeysBackupData() { fun testEncryptAndDecryptKeysBackupData() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService
@ -252,7 +251,7 @@ class KeysBackupTest : InstrumentedTest {
// - Pick a megolm key // - Pick a megolm key
val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0] val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0]
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
// - Check encryptGroupSession() returns stg // - Check encryptGroupSession() returns stg
val keyBackupData = keysBackup.encryptGroupSession(session) val keyBackupData = keysBackup.encryptGroupSession(session)
@ -270,10 +269,10 @@ class KeysBackupTest : InstrumentedTest {
decryption!!) decryption!!)
assertNotNull(sessionData) assertNotNull(sessionData)
// - Compare the decrypted megolm key with the original one // - Compare the decrypted megolm key with the original one
mKeysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData) keysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(mTestHelper) cryptoTestData.cleanUp(testHelper)
} }
/** /**
@ -284,10 +283,10 @@ class KeysBackupTest : InstrumentedTest {
*/ */
@Test @Test
fun restoreKeysBackupTest() { fun restoreKeysBackupTest() {
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
// - Restore the e2e backup from the homeserver // - 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.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
null, 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() { fun trustKeyBackupVersionTest() {
// - Do an e2e backup to the homeserver with a recovery key // - Do an e2e backup to the homeserver with a recovery key
// - And log Alice on a new device // - And log Alice on a new device
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService()) val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
@ -379,7 +378,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
// - Trust the backup from the new device // - Trust the backup from the new device
mTestHelper.doSync<Unit> { testHelper.doSync<Unit> {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion( testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
true, true,
@ -388,21 +387,21 @@ class KeysBackupTest : InstrumentedTest {
} }
// Wait for backup state to be ReadyToBackUp // 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 // - Backup must be enabled on the new device, on the same version
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version) assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
// - Retrieve the last version from the server // - Retrieve the last version from the server
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { val keysVersionResult = testHelper.doSync<KeysVersionResult?> {
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
} }
// - It must be the same // - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> { val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
} }
@ -411,7 +410,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, keysBackupVersionTrust.signatures.size) assertEquals(2, keysBackupVersionTrust.signatures.size)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testData.cleanUp(mTestHelper) testData.cleanUp(testHelper)
} }
/** /**
@ -428,7 +427,7 @@ class KeysBackupTest : InstrumentedTest {
fun trustKeyBackupVersionWithRecoveryKeyTest() { fun trustKeyBackupVersionWithRecoveryKeyTest() {
// - Do an e2e backup to the homeserver with a recovery key // - Do an e2e backup to the homeserver with a recovery key
// - And log Alice on a new device // - And log Alice on a new device
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService()) val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
@ -438,7 +437,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
// - Trust the backup from the new device with the recovery key // - 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().trustKeysBackupVersionWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
@ -447,21 +446,21 @@ class KeysBackupTest : InstrumentedTest {
} }
// Wait for backup state to be ReadyToBackUp // 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 // - Backup must be enabled on the new device, on the same version
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version) assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
// - Retrieve the last version from the server // - Retrieve the last version from the server
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { val keysVersionResult = testHelper.doSync<KeysVersionResult?> {
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
} }
// - It must be the same // - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> { val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
} }
@ -470,7 +469,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, keysBackupVersionTrust.signatures.size) assertEquals(2, keysBackupVersionTrust.signatures.size)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testData.cleanUp(mTestHelper) testData.cleanUp(testHelper)
} }
/** /**
@ -485,7 +484,7 @@ class KeysBackupTest : InstrumentedTest {
fun trustKeyBackupVersionWithWrongRecoveryKeyTest() { fun trustKeyBackupVersionWithWrongRecoveryKeyTest() {
// - Do an e2e backup to the homeserver with a recovery key // - Do an e2e backup to the homeserver with a recovery key
// - And log Alice on a new device // - And log Alice on a new device
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService()) val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
@ -501,7 +500,7 @@ class KeysBackupTest : InstrumentedTest {
"Bad recovery key", "Bad recovery key",
TestMatrixCallback(latch, false) TestMatrixCallback(latch, false)
) )
mTestHelper.await(latch) testHelper.await(latch)
// - The new device must still see the previous backup as not trusted // - The new device must still see the previous backup as not trusted
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
@ -509,7 +508,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
stateObserver.stopAndCheckStates(null) 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 // - Do an e2e backup to the homeserver with a password
// - And log Alice on a new device // - And log Alice on a new device
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService()) val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
@ -538,7 +537,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
// - Trust the backup from the new device with the password // - 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().trustKeysBackupVersionWithPassphrase(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
password, password,
@ -547,21 +546,21 @@ class KeysBackupTest : InstrumentedTest {
} }
// Wait for backup state to be ReadyToBackUp // 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 // - Backup must be enabled on the new device, on the same version
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version) assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
// - Retrieve the last version from the server // - Retrieve the last version from the server
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { val keysVersionResult = testHelper.doSync<KeysVersionResult?> {
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
} }
// - It must be the same // - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> { val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
} }
@ -570,7 +569,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, keysBackupVersionTrust.signatures.size) assertEquals(2, keysBackupVersionTrust.signatures.size)
stateObserver.stopAndCheckStates(null) 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 // - Do an e2e backup to the homeserver with a password
// - And log Alice on a new device // - And log Alice on a new device
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService()) val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
@ -604,7 +603,7 @@ class KeysBackupTest : InstrumentedTest {
badPassword, badPassword,
TestMatrixCallback(latch, false) TestMatrixCallback(latch, false)
) )
mTestHelper.await(latch) testHelper.await(latch)
// - The new device must still see the previous backup as not trusted // - The new device must still see the previous backup as not trusted
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
@ -612,7 +611,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testData.cleanUp(mTestHelper) testData.cleanUp(testHelper)
} }
/** /**
@ -623,7 +622,7 @@ class KeysBackupTest : InstrumentedTest {
*/ */
@Test @Test
fun restoreKeysBackupWithAWrongRecoveryKeyTest() { fun restoreKeysBackupWithAWrongRecoveryKeyTest() {
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
// - Try to restore the e2e backup with a wrong recovery key // - Try to restore the e2e backup with a wrong recovery key
val latch2 = CountDownLatch(1) val latch2 = CountDownLatch(1)
@ -640,12 +639,12 @@ class KeysBackupTest : InstrumentedTest {
} }
} }
) )
mTestHelper.await(latch2) testHelper.await(latch2)
// onSuccess may not have been called // onSuccess may not have been called
assertNull(importRoomKeysResult) assertNull(importRoomKeysResult)
testData.cleanUp(mTestHelper) testData.cleanUp(testHelper)
} }
/** /**
@ -658,12 +657,12 @@ class KeysBackupTest : InstrumentedTest {
fun testBackupWithPassword() { fun testBackupWithPassword() {
val password = "password" val password = "password"
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
// - Restore the e2e backup with the password // - Restore the e2e backup with the password
val steps = ArrayList<StepProgressListener.Step>() 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!!, testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
password, password,
null, null,
@ -698,9 +697,9 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress) assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress)
assertEquals(100, (steps[104] 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 password = "password"
val wrongPassword = "passw0rd" val wrongPassword = "passw0rd"
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
// - Try to restore the e2e backup with a wrong password // - Try to restore the e2e backup with a wrong password
val latch2 = CountDownLatch(1) val latch2 = CountDownLatch(1)
@ -731,12 +730,12 @@ class KeysBackupTest : InstrumentedTest {
} }
} }
) )
mTestHelper.await(latch2) testHelper.await(latch2)
// onSuccess may not have been called // onSuccess may not have been called
assertNull(importRoomKeysResult) assertNull(importRoomKeysResult)
testData.cleanUp(mTestHelper) testData.cleanUp(testHelper)
} }
/** /**
@ -749,10 +748,10 @@ class KeysBackupTest : InstrumentedTest {
fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() { fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() {
val password = "password" val password = "password"
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
// - Restore the e2e backup with the recovery key. // - 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.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
null, 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 @Test
fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() { fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() {
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
// - Try to restore the e2e backup with a password // - Try to restore the e2e backup with a password
val latch2 = CountDownLatch(1) val latch2 = CountDownLatch(1)
@ -792,12 +791,12 @@ class KeysBackupTest : InstrumentedTest {
} }
} }
) )
mTestHelper.await(latch2) testHelper.await(latch2)
// onSuccess may not have been called // onSuccess may not have been called
assertNull(importRoomKeysResult) assertNull(importRoomKeysResult)
testData.cleanUp(mTestHelper) testData.cleanUp(testHelper)
} }
/** /**
@ -807,22 +806,22 @@ class KeysBackupTest : InstrumentedTest {
@Test @Test
fun testIsKeysBackupTrusted() { fun testIsKeysBackupTrusted() {
// - Create a backup version // - Create a backup version
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
val stateObserver = StateObserver(keysBackup) val stateObserver = StateObserver(keysBackup)
// - Do an e2e backup to the homeserver // - Do an e2e backup to the homeserver
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
// Get key backup version from the homeserver // Get key backup version from the homeserver
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { val keysVersionResult = testHelper.doSync<KeysVersionResult?> {
keysBackup.getCurrentVersion(it) keysBackup.getCurrentVersion(it)
} }
// - Check the returned KeyBackupVersion is trusted // - Check the returned KeyBackupVersion is trusted
val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> { val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
keysBackup.getKeysBackupTrust(keysVersionResult!!, it) keysBackup.getKeysBackupTrust(keysVersionResult!!, it)
} }
@ -837,7 +836,7 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId) assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(mTestHelper) cryptoTestData.cleanUp(testHelper)
} }
/** /**
@ -849,9 +848,8 @@ class KeysBackupTest : InstrumentedTest {
*/ */
@Test @Test
fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() { fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() {
fail("This test still fail. To investigate")
// - Create a backup version // - Create a backup version
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
@ -859,15 +857,15 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
assertTrue(keysBackup.isEnabled) assertTrue(keysBackup.isEnabled)
// - Restart alice session // - Restart alice session
// - Log Alice on a new device // - 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() val keysBackup2 = aliceSession2.cryptoService().keysBackupService()
@ -891,13 +889,13 @@ class KeysBackupTest : InstrumentedTest {
} }
} }
}) })
mTestHelper.await(latch) testHelper.await(latch)
assertEquals(keyBackupCreationInfo.version, keysBackup2.currentBackupVersion) assertEquals(keyBackupCreationInfo.version, keysBackup2.currentBackupVersion)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
stateObserver2.stopAndCheckStates(null) stateObserver2.stopAndCheckStates(null)
mTestHelper.signOutAndClose(aliceSession2) testHelper.signOutAndClose(aliceSession2)
} }
/** /**
@ -911,7 +909,7 @@ class KeysBackupTest : InstrumentedTest {
@Test @Test
fun testBackupWhenAnotherBackupWasCreated() { fun testBackupWhenAnotherBackupWasCreated() {
// - Create a backup version // - Create a backup version
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
@ -939,15 +937,15 @@ class KeysBackupTest : InstrumentedTest {
}) })
// - Make alice back up her keys to her homeserver // - Make alice back up her keys to her homeserver
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
assertTrue(keysBackup.isEnabled) assertTrue(keysBackup.isEnabled)
mTestHelper.await(latch0) testHelper.await(latch0)
// - Create a new backup with fake data on the homeserver, directly using the rest client // - Create a new backup with fake data on the homeserver, directly using the rest client
val megolmBackupCreationInfo = mCryptoTestHelper.createFakeMegolmBackupCreationInfo() val megolmBackupCreationInfo = cryptoTestHelper.createFakeMegolmBackupCreationInfo()
mTestHelper.doSync<KeysVersion> { testHelper.doSync<KeysVersion> {
(keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, it) (keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, it)
} }
@ -957,14 +955,14 @@ class KeysBackupTest : InstrumentedTest {
// - Make alice back up all her keys again // - Make alice back up all her keys again
val latch2 = CountDownLatch(1) val latch2 = CountDownLatch(1)
keysBackup.backupAllGroupSessions(null, TestMatrixCallback(latch2, false)) keysBackup.backupAllGroupSessions(null, TestMatrixCallback(latch2, false))
mTestHelper.await(latch2) testHelper.await(latch2)
// -> That must fail and her backup state must be WrongBackUpVersion // -> That must fail and her backup state must be WrongBackUpVersion
assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.state) assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.state)
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(mTestHelper) cryptoTestData.cleanUp(testHelper)
} }
/** /**
@ -982,17 +980,17 @@ class KeysBackupTest : InstrumentedTest {
@Test @Test
fun testBackupAfterVerifyingADevice() { fun testBackupAfterVerifyingADevice() {
// - Create a backup version // - Create a backup version
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
val stateObserver = StateObserver(keysBackup) val stateObserver = StateObserver(keysBackup)
// - Make alice back up her keys to her homeserver // - 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. // Wait for keys backup to finish by asking again to backup keys.
mTestHelper.doSync<Unit> { testHelper.doSync<Unit> {
keysBackup.backupAllGroupSessions(null, it) keysBackup.backupAllGroupSessions(null, it)
} }
@ -1001,14 +999,14 @@ class KeysBackupTest : InstrumentedTest {
val aliceUserId = cryptoTestData.firstSession.myUserId val aliceUserId = cryptoTestData.firstSession.myUserId
// - Log Alice on a new device // - 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 // - Post a message to have a new megolm session
aliceSession2.cryptoService().setWarnOnUnknownDevices(false) aliceSession2.cryptoService().setWarnOnUnknownDevices(false)
val room2 = aliceSession2.getRoom(cryptoTestData.roomId)!! 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 // - Try to backup all in aliceSession2, it must fail
val keysBackup2 = aliceSession2.cryptoService().keysBackupService() val keysBackup2 = aliceSession2.cryptoService().keysBackupService()
@ -1025,7 +1023,7 @@ class KeysBackupTest : InstrumentedTest {
super.onSuccess(data) super.onSuccess(data)
} }
}) })
mTestHelper.await(latch2) testHelper.await(latch2)
assertFalse(isSuccessful) assertFalse(isSuccessful)
@ -1049,12 +1047,12 @@ class KeysBackupTest : InstrumentedTest {
} }
} }
}) })
mTestHelper.await(latch4) testHelper.await(latch4)
// -> It must use the same backup version // -> It must use the same backup version
assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion) assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion)
mTestHelper.doSync<Unit> { testHelper.doSync<Unit> {
aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it) aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it)
} }
@ -1063,8 +1061,8 @@ class KeysBackupTest : InstrumentedTest {
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
stateObserver2.stopAndCheckStates(null) stateObserver2.stopAndCheckStates(null)
mTestHelper.signOutAndClose(aliceSession2) testHelper.signOutAndClose(aliceSession2)
cryptoTestData.cleanUp(mTestHelper) cryptoTestData.cleanUp(testHelper)
} }
/** /**
@ -1074,7 +1072,7 @@ class KeysBackupTest : InstrumentedTest {
@Test @Test
fun deleteKeysBackupTest() { fun deleteKeysBackupTest() {
// - Create a backup version // - Create a backup version
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
@ -1082,17 +1080,17 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
assertTrue(keysBackup.isEnabled) assertTrue(keysBackup.isEnabled)
// Delete the backup // Delete the backup
mTestHelper.doSync<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) } testHelper.doSync<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) }
// Backup is now disabled // Backup is now disabled
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
stateObserver.stopAndCheckStates(null) 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 import java.util.concurrent.CountDownLatch
class KeysBackupTestHelper( class KeysBackupTestHelper(
private val mTestHelper: CommonTestHelper, private val testHelper: CommonTestHelper,
private val mCryptoTestHelper: CryptoTestHelper) { private val cryptoTestHelper: CryptoTestHelper) {
fun waitForKeybackUpBatching() {
Thread.sleep(400)
}
/** /**
* Common initial condition * Common initial condition
@ -43,7 +47,9 @@ class KeysBackupTestHelper(
* @param password optional password * @param password optional password
*/ */
fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData { fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
waitForKeybackUpBatching()
val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
@ -57,7 +63,7 @@ class KeysBackupTestHelper(
var lastProgress = 0 var lastProgress = 0
var lastTotal = 0 var lastTotal = 0
mTestHelper.doSync<Unit> { testHelper.doSync<Unit> {
keysBackup.backupAllGroupSessions(object : ProgressListener { keysBackup.backupAllGroupSessions(object : ProgressListener {
override fun onProgress(progress: Int, total: Int) { override fun onProgress(progress: Int, total: Int) {
lastProgress = progress lastProgress = progress
@ -72,7 +78,7 @@ class KeysBackupTestHelper(
val aliceUserId = cryptoTestData.firstSession.myUserId val aliceUserId = cryptoTestData.firstSession.myUserId
// - Log Alice on a new device // - 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 // Test check: aliceSession2 has no keys at login
Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false)) Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false))
@ -92,7 +98,7 @@ class KeysBackupTestHelper(
password: String? = null): PrepareKeysBackupDataResult { password: String? = null): PrepareKeysBackupDataResult {
val stateObserver = StateObserver(keysBackup) val stateObserver = StateObserver(keysBackup)
val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> { val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
keysBackup.prepareKeysBackupVersion(password, null, it) keysBackup.prepareKeysBackupVersion(password, null, it)
} }
@ -101,7 +107,7 @@ class KeysBackupTestHelper(
Assert.assertFalse(keysBackup.isEnabled) Assert.assertFalse(keysBackup.isEnabled)
// Create the version // Create the version
val keysVersion = mTestHelper.doSync<KeysVersion> { val keysVersion = testHelper.doSync<KeysVersion> {
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it) keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
} }
@ -136,7 +142,7 @@ class KeysBackupTestHelper(
} }
}) })
mTestHelper.await(latch) testHelper.await(latch)
} }
fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) { 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.lifecycle.Observer
import androidx.test.ext.junit.runners.AndroidJUnit4 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.assertEquals
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull 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.KeySigner
import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec 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.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.SharedSecretStorageService
import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
import org.matrix.android.sdk.api.util.Optional 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.SSSS_ALGORITHM_AES_HMAC_SHA2
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
class QuadSTests : InstrumentedTest { class QuadSTests : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context()) private val testHelper = CommonTestHelper(context())
private val emptyKeySigner = object : KeySigner { private val emptyKeySigner = object : KeySigner {
override fun sign(canonicalJson: String): Map<String, Map<String, String>>? { override fun sign(canonicalJson: String): Map<String, Map<String, String>>? {
@ -60,35 +56,29 @@ class QuadSTests : InstrumentedTest {
} }
@Test @Test
@Suppress("EXPERIMENTAL_API_USAGE")
fun test_Generate4SKey() { 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 quadS = aliceSession.sharedSecretStorageService
val TEST_KEY_ID = "my.test.Key" val TEST_KEY_ID = "my.test.Key"
mTestHelper.runBlockingTest { testHelper.runBlockingTest {
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner) quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner)
} }
// Assert Account data is updated
val accountDataLock = CountDownLatch(1)
var accountData: UserAccountDataEvent? = null var accountData: UserAccountDataEvent? = null
// Assert Account data is updated
val liveAccountData = runBlocking(Dispatchers.Main) { testHelper.waitWithLatch {
aliceSession.accountDataService().getLiveUserAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") val liveAccountData = aliceSession.accountDataService().getLiveUserAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
} val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") {
if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") { accountData = t.getOrNull()
accountData = t.getOrNull() }
accountDataLock.countDown() it.countDown()
} }
liveAccountData.observeForever(accountDataObserver)
} }
GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) }
mTestHelper.await(accountDataLock)
assertNotNull("Key should be stored in account data", accountData) assertNotNull("Key should be stored in account data", accountData)
val parsed = SecretStorageKeyContent.fromJson(accountData!!.content) val parsed = SecretStorageKeyContent.fromJson(accountData!!.content)
assertNotNull("Key Content cannot be parsed", parsed) assertNotNull("Key Content cannot be parsed", parsed)
@ -96,36 +86,29 @@ class QuadSTests : InstrumentedTest {
assertEquals("Unexpected key name", "Test Key", parsed.name) assertEquals("Unexpected key name", "Test Key", parsed.name)
assertNull("Key was not generated from passphrase", parsed.passphrase) assertNull("Key was not generated from passphrase", parsed.passphrase)
// Set as default key
GlobalScope.launch {
quadS.setDefaultKey(TEST_KEY_ID)
}
var defaultKeyAccountData: UserAccountDataEvent? = null var defaultKeyAccountData: UserAccountDataEvent? = null
val defaultDataLock = CountDownLatch(1) // Set as default key
testHelper.waitWithLatch { latch ->
val liveDefAccountData = runBlocking(Dispatchers.Main) { quadS.setDefaultKey(TEST_KEY_ID)
aliceSession.accountDataService().getLiveUserAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID) val liveDefAccountData =
} aliceSession.accountDataService().getLiveUserAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) { if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) {
defaultKeyAccountData = t.getOrNull()!! defaultKeyAccountData = t.getOrNull()!!
defaultDataLock.countDown() latch.countDown()
}
} }
liveDefAccountData.observeForever(accountDefDataObserver)
} }
GlobalScope.launch(Dispatchers.Main) { liveDefAccountData.observeForever(accountDefDataObserver) }
mTestHelper.await(defaultDataLock)
assertNotNull(defaultKeyAccountData?.content) assertNotNull(defaultKeyAccountData?.content)
assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key")) assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key"))
mTestHelper.signOutAndClose(aliceSession) testHelper.signOutAndClose(aliceSession)
} }
@Test @Test
fun test_StoreSecret() { 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 keyId = "My.Key"
val info = generatedSecret(aliceSession, keyId, true) val info = generatedSecret(aliceSession, keyId, true)
@ -133,7 +116,7 @@ class QuadSTests : InstrumentedTest {
// Store a secret // Store a secret
val clearSecret = "42".toByteArray().toBase64NoPadding() val clearSecret = "42".toByteArray().toBase64NoPadding()
mTestHelper.runBlockingTest { testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.storeSecret( aliceSession.sharedSecretStorageService.storeSecret(
"secret.of.life", "secret.of.life",
clearSecret, clearSecret,
@ -154,7 +137,7 @@ class QuadSTests : InstrumentedTest {
// Try to decrypt?? // Try to decrypt??
val decryptedSecret = mTestHelper.runBlockingTest { val decryptedSecret = testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.getSecret( aliceSession.sharedSecretStorageService.getSecret(
"secret.of.life", "secret.of.life",
null, // default key null, // default key
@ -163,32 +146,32 @@ class QuadSTests : InstrumentedTest {
} }
assertEquals("Secret mismatch", clearSecret, decryptedSecret) assertEquals("Secret mismatch", clearSecret, decryptedSecret)
mTestHelper.signOutAndClose(aliceSession) testHelper.signOutAndClose(aliceSession)
} }
@Test @Test
fun test_SetDefaultLocalEcho() { 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 quadS = aliceSession.sharedSecretStorageService
val TEST_KEY_ID = "my.test.Key" val TEST_KEY_ID = "my.test.Key"
mTestHelper.runBlockingTest { testHelper.runBlockingTest {
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner) 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 // 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) quadS.setDefaultKey(TEST_KEY_ID)
} }
mTestHelper.signOutAndClose(aliceSession) testHelper.signOutAndClose(aliceSession)
} }
@Test @Test
fun test_StoreSecretWithMultipleKey() { 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 keyId1 = "Key.1"
val key1Info = generatedSecret(aliceSession, keyId1, true) val key1Info = generatedSecret(aliceSession, keyId1, true)
val keyId2 = "Key2" val keyId2 = "Key2"
@ -196,7 +179,7 @@ class QuadSTests : InstrumentedTest {
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
mTestHelper.runBlockingTest { testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.storeSecret( aliceSession.sharedSecretStorageService.storeSecret(
"my.secret", "my.secret",
mySecretText.toByteArray().toBase64NoPadding(), mySecretText.toByteArray().toBase64NoPadding(),
@ -216,33 +199,33 @@ class QuadSTests : InstrumentedTest {
assertNotNull(encryptedContent?.get(keyId2)) assertNotNull(encryptedContent?.get(keyId2))
// Assert that can decrypt with both keys // Assert that can decrypt with both keys
mTestHelper.runBlockingTest { testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.getSecret("my.secret", aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId1, keyId1,
RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!! RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!
) )
} }
mTestHelper.runBlockingTest { testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.getSecret("my.secret", aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId2, keyId2,
RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!! RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!
) )
} }
mTestHelper.signOutAndClose(aliceSession) testHelper.signOutAndClose(aliceSession)
} }
@Test @Test
fun test_GetSecretWithBadPassphrase() { 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 keyId1 = "Key.1"
val passphrase = "The good pass phrase" val passphrase = "The good pass phrase"
val key1Info = generatedSecretFromPassphrase(aliceSession, passphrase, keyId1, true) val key1Info = generatedSecretFromPassphrase(aliceSession, passphrase, keyId1, true)
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
mTestHelper.runBlockingTest { testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.storeSecret( aliceSession.sharedSecretStorageService.storeSecret(
"my.secret", "my.secret",
mySecretText.toByteArray().toBase64NoPadding(), mySecretText.toByteArray().toBase64NoPadding(),
@ -250,19 +233,23 @@ class QuadSTests : InstrumentedTest {
) )
} }
mTestHelper.runBlockingTest { testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.getSecret("my.secret", try {
keyId1, aliceSession.sharedSecretStorageService.getSecret("my.secret",
RawBytesKeySpec.fromPassphrase( keyId1,
"A bad passphrase", RawBytesKeySpec.fromPassphrase(
key1Info.content?.passphrase?.salt ?: "", "A bad passphrase",
key1Info.content?.passphrase?.iterations ?: 0, key1Info.content?.passphrase?.salt ?: "",
null) key1Info.content?.passphrase?.iterations ?: 0,
) null)
)
} catch (throwable: Throwable) {
assert(throwable is SharedSecretStorageError.BadMac)
}
} }
// Now try with correct key // Now try with correct key
mTestHelper.runBlockingTest { testHelper.runBlockingTest {
aliceSession.sharedSecretStorageService.getSecret("my.secret", aliceSession.sharedSecretStorageService.getSecret("my.secret",
keyId1, keyId1,
RawBytesKeySpec.fromPassphrase( 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 { private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
val accountDataLock = CountDownLatch(1)
var accountData: UserAccountDataEvent? = null var accountData: UserAccountDataEvent? = null
testHelper.waitWithLatch {
val liveAccountData = runBlocking(Dispatchers.Main) { val liveAccountData = session.accountDataService().getLiveUserAccountDataEvent(type)
session.accountDataService().getLiveUserAccountDataEvent(type) val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
} if (t?.getOrNull()?.type == type) {
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> accountData = t.getOrNull()
if (t?.getOrNull()?.type == type) { it.countDown()
accountData = t.getOrNull() }
accountDataLock.countDown()
} }
liveAccountData.observeForever(accountDataObserver)
} }
GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) }
mTestHelper.await(accountDataLock)
assertNotNull("Account Data type:$type should be found", accountData) assertNotNull("Account Data type:$type should be found", accountData)
return accountData!! return accountData!!
} }
private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
val quadS = session.sharedSecretStorageService val quadS = session.sharedSecretStorageService
val creationInfo = mTestHelper.runBlockingTest { val creationInfo = testHelper.runBlockingTest {
quadS.generateKey(keyId, null, keyId, emptyKeySigner) quadS.generateKey(keyId, null, keyId, emptyKeySigner)
} }
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
if (asDefault) { if (asDefault) {
mTestHelper.runBlockingTest { testHelper.runBlockingTest {
quadS.setDefaultKey(keyId) quadS.setDefaultKey(keyId)
} }
assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) 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 { private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
val quadS = session.sharedSecretStorageService val quadS = session.sharedSecretStorageService
val creationInfo = mTestHelper.runBlockingTest { val creationInfo = testHelper.runBlockingTest {
quadS.generateKeyWithPassphrase( quadS.generateKeyWithPassphrase(
keyId, keyId,
keyId, keyId,
@ -331,7 +312,7 @@ class QuadSTests : InstrumentedTest {
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
if (asDefault) { if (asDefault) {
mTestHelper.runBlockingTest { testHelper.runBlockingTest {
quadS.setDefaultKey(keyId) quadS.setDefaultKey(keyId)
} }
assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)

View file

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

View file

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

View file

@ -45,7 +45,7 @@ object RoomDataHelper {
content: Content? = null, content: Content? = null,
prevContent: Content? = null, prevContent: Content? = null,
sender: String = FAKE_TEST_SENDER, sender: String = FAKE_TEST_SENDER,
stateKey: String = FAKE_TEST_SENDER stateKey: String? = null
): Event { ): Event {
return Event( return Event(
type = type, type = type,
@ -64,6 +64,6 @@ object RoomDataHelper {
private fun createFakeRoomMemberEvent(): Event { private fun createFakeRoomMemberEvent(): Event {
val roomMember = RoomMemberContent(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent() 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, message,
numberOfMessagesToSend) numberOfMessagesToSend)
// Alice clear the cache // Alice clear the cache and restart the sync
commonTestHelper.runBlockingTest { commonTestHelper.clearCacheAndSync(aliceSession)
aliceSession.clearCache()
}
// And restarts the sync
aliceSession.startSync(true)
val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30)) val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30))
aliceTimeline.start() aliceTimeline.start()

View file

@ -24,14 +24,10 @@ import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.extensions.orFalse 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.api.session.search.SearchResult
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestData import org.matrix.android.sdk.common.CryptoTestData
import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.CryptoTestHelper
import java.util.concurrent.CountDownLatch
@RunWith(JUnit4::class) @RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
@ -84,24 +80,12 @@ class SearchMessagesTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceRoomId = cryptoTestData.roomId val aliceRoomId = cryptoTestData.roomId
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! 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( commonTestHelper.sendTextMessage(
roomFromAlicePOV, roomFromAlicePOV,
MESSAGE, MESSAGE,
2) 2)
commonTestHelper.await(lock)
val data = commonTestHelper.runBlockingTest { val data = commonTestHelper.runBlockingTest {
block.invoke(cryptoTestData) block.invoke(cryptoTestData)
} }
@ -114,7 +98,6 @@ class SearchMessagesTest : InstrumentedTest {
}.orFalse() }.orFalse()
) )
aliceTimeline.removeAllListeners()
cryptoTestData.cleanUp(commonTestHelper) cryptoTestData.cleanUp(commonTestHelper)
} }
} }

View file

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

View file

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

View file

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

View file

@ -104,6 +104,8 @@ object EventType {
// Poll // Poll
const val POLL_START = "org.matrix.msc3381.poll.start" const val POLL_START = "org.matrix.msc3381.poll.start"
const val POLL_RESPONSE = "org.matrix.msc3381.poll.response"
const val POLL_END = "org.matrix.msc3381.poll.end"
// Unwedging // Unwedging
internal const val DUMMY = "m.dummy" internal const val DUMMY = "m.dummy"

View file

@ -24,25 +24,24 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class PollSummaryContent( data class PollSummaryContent(
// Index of my vote var myVote: String? = null,
var myVote: Int? = null,
// Array of VoteInfo, list is constructed so that there is only one vote by user // Array of VoteInfo, list is constructed so that there is only one vote by user
// And that optionIndex is valid // And that optionIndex is valid
var votes: List<VoteInfo>? = null var votes: List<VoteInfo>? = null,
) { var votesSummary: Map<String, VoteSummary>? = null,
var totalVotes: Int = 0,
var winnerVoteCount: Int = 0
)
fun voteCount(): Int { @JsonClass(generateAdapter = true)
return votes?.size ?: 0 data class VoteSummary(
} val total: Int = 0,
val percentage: Double = 0.0
fun voteCountForOption(optionIndex: Int): Int { )
return votes?.filter { it.optionIndex == optionIndex }?.count() ?: 0
}
}
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class VoteInfo( data class VoteInfo(
val userId: String, val userId: String,
val optionIndex: Int, val option: String,
val voteTimestamp: Long val voteTimestamp: Long
) )

View file

@ -0,0 +1,29 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
/**
* Class representing the org.matrix.msc3381.poll.end event content
*/
@JsonClass(generateAdapter = true)
data class MessageEndPollContent(
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? = null
)

View file

@ -1,40 +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.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
// Possible values for optionType
const val OPTION_TYPE_POLL = "org.matrix.poll"
const val OPTION_TYPE_BUTTONS = "org.matrix.buttons"
/**
* Polls and bot buttons are m.room.message events with a msgtype of m.options,
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
*/
@JsonClass(generateAdapter = true)
data class MessageOptionsContent(
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_OPTIONS,
@Json(name = "type") val optionType: String? = null,
@Json(name = "body") override val body: String,
@Json(name = "label") val label: String?,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "options") val options: List<OptionItem>? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent

View file

@ -18,8 +18,18 @@ package org.matrix.android.sdk.api.session.room.model.message
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessagePollContent( data class MessagePollContent(
@Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null /**
) * Local message type, not from server
*/
@Transient
override val msgType: String = MessageType.MSGTYPE_POLL_START,
@Json(name = "body") override val body: String = "",
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null,
@Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null
) : MessageContent

View file

@ -21,13 +21,15 @@ import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
/**
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
*/
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessagePollResponseContent( data class MessagePollResponseContent(
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_RESPONSE, /**
@Json(name = "body") override val body: String, * Local message type, not from server
*/
@Transient
override val msgType: String = MessageType.MSGTYPE_POLL_RESPONSE,
@Json(name = "body") override val body: String = "",
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null @Json(name = "m.new_content") override val newContent: Content? = null,
@Json(name = "org.matrix.msc3381.poll.response") val response: PollResponse? = null
) : MessageContent ) : MessageContent

View file

@ -25,15 +25,18 @@ object MessageType {
const val MSGTYPE_VIDEO = "m.video" const val MSGTYPE_VIDEO = "m.video"
const val MSGTYPE_LOCATION = "m.location" const val MSGTYPE_LOCATION = "m.location"
const val MSGTYPE_FILE = "m.file" const val MSGTYPE_FILE = "m.file"
const val MSGTYPE_OPTIONS = "org.matrix.options"
const val MSGTYPE_RESPONSE = "org.matrix.response"
const val MSGTYPE_POLL_CLOSED = "org.matrix.poll_closed"
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request" const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
// Add, in local, a fake message type in order to StickerMessage can inherit Message class // Add, in local, a fake message type in order to StickerMessage can inherit Message class
// Because sticker isn't a message type but a event type without msgtype field // Because sticker isn't a message type but a event type without msgtype field
const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker" const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
// Fake message types for poll events to be able to inherit them from MessageContent
// Because poll events are not message events and they don't hanve msgtype field
const val MSGTYPE_POLL_START = "org.matrix.android.sdk.poll.start"
const val MSGTYPE_POLL_RESPONSE = "org.matrix.android.sdk.poll.response"
const val MSGTYPE_CONFETTI = "nic.custom.confetti" const val MSGTYPE_CONFETTI = "nic.custom.confetti"
const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall" const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
} }

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,11 +19,7 @@ package org.matrix.android.sdk.api.session.room.model.message
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
/**
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
*/
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class OptionItem( data class PollResponse(
@Json(name = "label") val label: String?, @Json(name = "answers") val answers: List<String>? = null
@Json(name = "value") val value: String?
) )

View file

@ -91,11 +91,17 @@ interface SendService {
/** /**
* Method to send a poll response. * Method to send a poll response.
* @param pollEventId the poll currently replied to * @param pollEventId the poll currently replied to
* @param optionIndex The reply index * @param answerId The id of the answer
* @param optionValue The option value (for compatibility)
* @return a [Cancelable] * @return a [Cancelable]
*/ */
fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable fun voteToPoll(pollEventId: String, answerId: String): Cancelable
/**
* End a poll in the room.
* @param pollEventId event id of the poll
* @return a [Cancelable]
*/
fun endPoll(pollEventId: String): Cancelable
/** /**
* Redact (delete) the given event. * Redact (delete) the given event.

View file

@ -32,7 +32,8 @@ object RoomSummaryConstants {
EventType.CALL_ANSWER, EventType.CALL_ANSWER,
EventType.ENCRYPTED, EventType.ENCRYPTED,
EventType.STICKER, EventType.STICKER,
EventType.REACTION EventType.REACTION,
EventType.POLL_START
) )
// SC addition | this is the Element behaviour previous to Element v1.0.7 // SC addition | this is the Element behaviour previous to Element v1.0.7

View file

@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.ReadReceipt
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
@ -126,10 +127,10 @@ fun TimelineEvent.getEditedEventId(): String? {
* Get last MessageContent, after a possible edition * Get last MessageContent, after a possible edition
*/ */
fun TimelineEvent.getLastMessageContent(): MessageContent? { fun TimelineEvent.getLastMessageContent(): MessageContent? {
return if (root.getClearType() == EventType.STICKER) { return when (root.getClearType()) {
root.getClearContent().toModel<MessageStickerContent>() EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>()
} else { EventType.POLL_START -> root.getClearContent().toModel<MessagePollContent>()
(annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
} }
} }

View file

@ -58,6 +58,14 @@ data class SyncResponse(
@Json(name = "device_one_time_keys_count") @Json(name = "device_one_time_keys_count")
val deviceOneTimeKeysCount: DeviceOneTimeKeysCountSyncResponse? = null, val deviceOneTimeKeysCount: DeviceOneTimeKeysCountSyncResponse? = null,
/**
* The key algorithms for which the server has an unused fallback key for the device.
* If the client wants the server to have a fallback key for a given key algorithm,
* but that algorithm is not listed in device_unused_fallback_key_types, the client will upload a new key.
*/
@Json(name = "org.matrix.msc2732.device_unused_fallback_key_types")
val deviceUnusedFallbackKeyTypes: List<String>? = null,
/** /**
* List of groups. * List of groups.
*/ */

View file

@ -16,6 +16,8 @@
package org.matrix.android.sdk.api.session.terms package org.matrix.android.sdk.api.session.terms
import org.matrix.android.sdk.internal.session.terms.TermsResponse
interface TermsService { interface TermsService {
enum class ServiceType { enum class ServiceType {
IntegrationManager, IntegrationManager,
@ -28,4 +30,10 @@ interface TermsService {
baseUrl: String, baseUrl: String,
agreedUrls: List<String>, agreedUrls: List<String>,
token: String?) token: String?)
/**
* Get the homeserver terms, from the register API.
* Will be updated once https://github.com/matrix-org/matrix-doc/pull/3012 will be implemented.
*/
suspend fun getHomeserverTerms(baseUrl: String): TermsResponse
} }

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.Event
import org.matrix.android.sdk.api.session.events.model.EventType 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.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.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore 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 org.matrix.android.sdk.internal.worker.SessionWorkerParams
import javax.inject.Inject import javax.inject.Inject
internal class CancelGossipRequestWorker(context: Context, internal class CancelGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
params: WorkerParameters) : SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( internal data class Params(

View file

@ -67,6 +67,7 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
@ -431,6 +432,14 @@ internal class DefaultCryptoService @Inject constructor(
if (isStarted()) { if (isStarted()) {
// Make sure we process to-device messages before generating new one-time-keys #2782 // Make sure we process to-device messages before generating new one-time-keys #2782
deviceListManager.refreshOutdatedDeviceLists() deviceListManager.refreshOutdatedDeviceLists()
// The presence of device_unused_fallback_key_types indicates that the server supports fallback keys.
// If there's no unused signed_curve25519 fallback key we need a new one.
if (syncResponse.deviceUnusedFallbackKeyTypes != null &&
// Generate a fallback key only if the server does not already have an unused fallback key.
!syncResponse.deviceUnusedFallbackKeyTypes.contains(KEY_SIGNED_CURVE_25519_TYPE)) {
oneTimeKeysUploader.needsNewFallback()
}
oneTimeKeysUploader.maybeUploadOneTimeKeys() oneTimeKeysUploader.maybeUploadOneTimeKeys()
incomingGossipingRequestManager.processReceivedGossipingRequests() incomingGossipingRequestManager.processReceivedGossipingRequests()
} }
@ -928,7 +937,7 @@ internal class DefaultCryptoService @Inject constructor(
signatures = objectSigner.signObject(canonicalJson) signatures = objectSigner.signObject(canonicalJson)
) )
val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null) val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null, null)
uploadKeysTask.execute(uploadDeviceKeysParams) uploadKeysTask.execute(uploadDeviceKeysParams)
cryptoStore.setDeviceKeysUploaded(true) cryptoStore.setDeviceKeysUploaded(true)

View file

@ -136,6 +136,51 @@ internal class MXOlmDevice @Inject constructor(
return store.getOlmAccount().maxOneTimeKeys() return store.getOlmAccount().maxOneTimeKeys()
} }
/**
* Returns an unpublished fallback key
* A call to markKeysAsPublished will mark it as published and this
* call will return null (until a call to generateFallbackKey is made)
*/
fun getFallbackKey(): MutableMap<String, MutableMap<String, String>>? {
try {
return store.getOlmAccount().fallbackKey()
} catch (e: Exception) {
Timber.e("## getFallbackKey() : failed")
}
return null
}
/**
* Generates a new fallback key if there is not already
* an unpublished one.
* @return true if a new key was generated
*/
fun generateFallbackKeyIfNeeded(): Boolean {
try {
if (!hasUnpublishedFallbackKey()) {
store.getOlmAccount().generateFallbackKey()
store.saveOlmAccount()
return true
}
} catch (e: Exception) {
Timber.e("## generateFallbackKey() : failed")
}
return false
}
internal fun hasUnpublishedFallbackKey(): Boolean {
return getFallbackKey()?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty().isNotEmpty()
}
fun forgetFallbackKey() {
try {
store.getOlmAccount().forgetFallbackKey()
store.saveOlmAccount()
} catch (e: Exception) {
Timber.e("## forgetFallbackKey() : failed")
}
}
/** /**
* Release the instance * Release the instance
*/ */

View file

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import android.content.Context
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.model.MXKey import org.matrix.android.sdk.internal.crypto.model.MXKey
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
@ -28,11 +29,16 @@ import javax.inject.Inject
import kotlin.math.floor import kotlin.math.floor
import kotlin.math.min import kotlin.math.min
// The spec recommend a 5mn delay, but due to federation
// or server downtime we give it a bit more time (1 hour)
const val FALLBACK_KEY_FORGET_DELAY = 60 * 60_000L
@SessionScope @SessionScope
internal class OneTimeKeysUploader @Inject constructor( internal class OneTimeKeysUploader @Inject constructor(
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
private val objectSigner: ObjectSigner, private val objectSigner: ObjectSigner,
private val uploadKeysTask: UploadKeysTask private val uploadKeysTask: UploadKeysTask,
context: Context
) { ) {
// tell if there is a OTK check in progress // tell if there is a OTK check in progress
private var oneTimeKeyCheckInProgress = false private var oneTimeKeyCheckInProgress = false
@ -41,6 +47,9 @@ internal class OneTimeKeysUploader @Inject constructor(
private var lastOneTimeKeyCheck: Long = 0 private var lastOneTimeKeyCheck: Long = 0
private var oneTimeKeyCount: Int? = null private var oneTimeKeyCount: Int? = null
// Simple storage to remember when was uploaded the last fallback key
private val storage = context.getSharedPreferences("OneTimeKeysUploader_${olmDevice.deviceEd25519Key.hashCode()}", Context.MODE_PRIVATE)
/** /**
* Stores the current one_time_key count which will be handled later (in a call of * Stores the current one_time_key count which will be handled later (in a call of
* _onSyncCompleted). The count is e.g. coming from a /sync response. * _onSyncCompleted). The count is e.g. coming from a /sync response.
@ -51,6 +60,15 @@ internal class OneTimeKeysUploader @Inject constructor(
oneTimeKeyCount = currentCount oneTimeKeyCount = currentCount
} }
fun needsNewFallback() {
if (olmDevice.generateFallbackKeyIfNeeded()) {
// As we generated a new one, it's already forgetting one
// so we can clear the last publish time
// (in case the network calls fails after to avoid calling forgetKey)
saveLastFallbackKeyPublishTime(0L)
}
}
/** /**
* Check if the OTK must be uploaded. * Check if the OTK must be uploaded.
*/ */
@ -65,9 +83,19 @@ internal class OneTimeKeysUploader @Inject constructor(
return return
} }
lastOneTimeKeyCheck = System.currentTimeMillis()
oneTimeKeyCheckInProgress = true oneTimeKeyCheckInProgress = true
val oneTimeKeyCountFromSync = oneTimeKeyCount
?: fetchOtkCount() // we don't have count from sync so get from server
?: return Unit.also {
oneTimeKeyCheckInProgress = false
Timber.w("maybeUploadOneTimeKeys: Failed to get otk count from server")
}
Timber.d("maybeUploadOneTimeKeys: otk count $oneTimeKeyCountFromSync , unpublished fallback key ${olmDevice.hasUnpublishedFallbackKey()}")
lastOneTimeKeyCheck = System.currentTimeMillis()
// We then check how many keys we can store in the Account object. // We then check how many keys we can store in the Account object.
val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys() val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys()
@ -78,37 +106,37 @@ internal class OneTimeKeysUploader @Inject constructor(
// discard the oldest private keys first. This will eventually clean // discard the oldest private keys first. This will eventually clean
// out stale private keys that won't receive a message. // out stale private keys that won't receive a message.
val keyLimit = floor(maxOneTimeKeys / 2.0).toInt() val keyLimit = floor(maxOneTimeKeys / 2.0).toInt()
if (oneTimeKeyCount == null) {
// Ask the server how many otk he has // We need to keep a pool of one time public keys on the server so that
oneTimeKeyCount = fetchOtkCount() // other devices can start conversations with us. But we can only store
} // a finite number of private keys in the olm Account object.
val oneTimeKeyCountFromSync = oneTimeKeyCount // To complicate things further then can be a delay between a device
if (oneTimeKeyCountFromSync != null) { // claiming a public one time key from the server and it sending us a
// We need to keep a pool of one time public keys on the server so that // message. We need to keep the corresponding private key locally until
// other devices can start conversations with us. But we can only store // we receive the message.
// a finite number of private keys in the olm Account object. // But that message might never arrive leaving us stuck with duff
// To complicate things further then can be a delay between a device // private keys clogging up our local storage.
// claiming a public one time key from the server and it sending us a // So we need some kind of engineering compromise to balance all of
// message. We need to keep the corresponding private key locally until // these factors.
// we receive the message. tryOrNull("Unable to upload OTK") {
// But that message might never arrive leaving us stuck with duff val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit)
// private keys clogging up our local storage. Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent")
// So we need some kind of engineering compromise to balance all of
// these factors.
tryOrNull("Unable to upload OTK") {
val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit)
Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent")
}
} else {
Timber.w("maybeUploadOneTimeKeys: waiting to know the number of OTK from the sync")
lastOneTimeKeyCheck = 0
} }
oneTimeKeyCheckInProgress = false oneTimeKeyCheckInProgress = false
// Check if we need to forget a fallback key
val latestPublishedTime = getLastFallbackKeyPublishTime()
if (latestPublishedTime != 0L && System.currentTimeMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) {
// This should be called once you are reasonably certain that you will not receive any more messages
// that use the old fallback key
Timber.d("## forgetFallbackKey()")
olmDevice.forgetFallbackKey()
}
} }
private suspend fun fetchOtkCount(): Int? { private suspend fun fetchOtkCount(): Int? {
return tryOrNull("Unable to get OTK count") { return tryOrNull("Unable to get OTK count") {
val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null)) val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null, null))
result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
} }
} }
@ -121,24 +149,47 @@ internal class OneTimeKeysUploader @Inject constructor(
* @return the number of uploaded keys * @return the number of uploaded keys
*/ */
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Int { private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Int {
if (keyLimit <= keyCount) { if (keyLimit <= keyCount && !olmDevice.hasUnpublishedFallbackKey()) {
// If we don't need to generate any more keys then we are done. // If we don't need to generate any more keys then we are done.
return 0 return 0
} }
val keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER) var keysThisLoop = 0
olmDevice.generateOneTimeKeys(keysThisLoop) if (keyLimit > keyCount) {
// Creating keys can be an expensive operation so we limit the
// number we generate in one go to avoid blocking the application
// for too long.
keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
olmDevice.generateOneTimeKeys(keysThisLoop)
}
// We check before sending if there is an unpublished key in order to saveLastFallbackKeyPublishTime if needed
val hadUnpublishedFallbackKey = olmDevice.hasUnpublishedFallbackKey()
val response = uploadOneTimeKeys(olmDevice.getOneTimeKeys()) val response = uploadOneTimeKeys(olmDevice.getOneTimeKeys())
olmDevice.markKeysAsPublished() olmDevice.markKeysAsPublished()
if (hadUnpublishedFallbackKey) {
// It had an unpublished fallback key that was published just now
saveLastFallbackKeyPublishTime(System.currentTimeMillis())
}
if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) { if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
// Maybe upload other keys // Maybe upload other keys
return keysThisLoop + uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit) return keysThisLoop +
uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit) +
(if (hadUnpublishedFallbackKey) 1 else 0)
} else { } else {
Timber.e("## uploadOTK() : response for uploading keys does not contain one_time_key_counts.signed_curve25519") Timber.e("## uploadOTK() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519") throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")
} }
} }
private fun saveLastFallbackKeyPublishTime(timeMillis: Long) {
storage.edit().putLong("last_fb_key_publish", timeMillis).apply()
}
private fun getLastFallbackKeyPublishTime(): Long {
return storage.getLong("last_fb_key_publish", 0)
}
/** /**
* Upload curve25519 one time keys. * Upload curve25519 one time keys.
*/ */
@ -159,10 +210,26 @@ internal class OneTimeKeysUploader @Inject constructor(
oneTimeJson["signed_curve25519:$key_id"] = k oneTimeJson["signed_curve25519:$key_id"] = k
} }
val fallbackJson = mutableMapOf<String, Any>()
val fallbackCurve25519Map = olmDevice.getFallbackKey()?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty()
fallbackCurve25519Map.forEach { (key_id, key) ->
val k = mutableMapOf<String, Any>()
k["key"] = key
k["fallback"] = true
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k)
k["signatures"] = objectSigner.signObject(canonicalJson)
fallbackJson["signed_curve25519:$key_id"] = k
}
// For now, we set the device id explicitly, as we may not be using the // For now, we set the device id explicitly, as we may not be using the
// same one as used in login. // same one as used in login.
val uploadParams = UploadKeysTask.Params(null, oneTimeJson) val uploadParams = UploadKeysTask.Params(
return uploadKeysTask.execute(uploadParams) deviceKeys = null,
oneTimeKeys = oneTimeJson,
fallbackKeys = fallbackJson.takeIf { fallbackJson.isNotEmpty() }
)
return uploadKeysTask.executeRetry(uploadParams, 3)
} }
companion object { companion object {
@ -173,6 +240,6 @@ internal class OneTimeKeysUploader @Inject constructor(
private const val ONE_TIME_KEY_GENERATION_MAX_NUMBER = 5 private const val ONE_TIME_KEY_GENERATION_MAX_NUMBER = 5
// frequency with which to check & upload one-time keys // frequency with which to check & upload one-time keys
private const val ONE_TIME_KEY_UPLOAD_PERIOD = (60 * 1000).toLong() // one minute private const val ONE_TIME_KEY_UPLOAD_PERIOD = (60_000).toLong() // one minute
} }
} }

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.Event
import org.matrix.android.sdk.api.session.events.model.EventType 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.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.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest 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 timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class SendGossipRequestWorker(context: Context, internal class SendGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
params: WorkerParameters) : SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( 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.Event
import org.matrix.android.sdk.api.session.events.model.EventType 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.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.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap 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 timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class SendGossipWorker(context: Context, internal class SendGossipWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
params: WorkerParameters) : SessionSafeCoroutineWorker<SendGossipWorker.Params>(context, params, sessionManager, Params::class.java) {
SessionSafeCoroutineWorker<SendGossipWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( 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.crypto.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo 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.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity 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 timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class UpdateTrustWorker(context: Context, internal class UpdateTrustWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
params: WorkerParameters) : SessionSafeCoroutineWorker<UpdateTrustWorker.Params>(context, params, sessionManager, Params::class.java) {
SessionSafeCoroutineWorker<UpdateTrustWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( internal data class Params(

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