mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Merge branch 'develop' of github.com:vector-im/element-android into feature/dla/fix_reply_and_quote_newlines
This commit is contained in:
commit
20b5742227
23
.github/workflows/sync-from-external-sources.yml
vendored
23
.github/workflows/sync-from-external-sources.yml
vendored
@ -70,4 +70,27 @@ jobs:
|
||||
body: |
|
||||
- Update SAS Strings from matrix-doc.
|
||||
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
|
104
.github/workflows/triage-move-labelled.yml
vendored
104
.github/workflows/triage-move-labelled.yml
vendored
@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
move_needs_info_issues:
|
||||
name: Move X-Needs-Info issues to Need info on triage board
|
||||
name: X-Needs-Info issues to Need info column on triage board
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338
|
||||
@ -17,22 +17,24 @@ jobs:
|
||||
label-name: "X-Needs-Info"
|
||||
|
||||
add_priority_design_issues_to_project:
|
||||
name: Move priority X-Needs-Design issues to Design project board
|
||||
name: P1 X-Needs-Design to Design project board
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'X-Needs-Design') &&
|
||||
(contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
contains(github.event.issue.labels.*.name, 'O-Occasional')) &&
|
||||
(contains(github.event.issue.labels.*.name, 'S-Critical') ||
|
||||
contains(github.event.issue.labels.*.name, 'S-Major') ||
|
||||
contains(github.event.issue.labels.*.name, 'S-Minor'))
|
||||
(contains(github.event.issue.labels.*.name, 'S-Critical') &&
|
||||
(contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
contains(github.event.issue.labels.*.name, 'O-Occasional')) ||
|
||||
contains(github.event.issue.labels.*.name, 'S-Major') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||
contains(github.event.issue.labels.*.name, 'A11y') &&
|
||||
contains(github.event.issue.labels.*.name, 'O-Frequent'))
|
||||
steps:
|
||||
- uses: octokit/graphql-action@v2.x
|
||||
id: add_to_project
|
||||
with:
|
||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||
query: |
|
||||
mutation add_to_project($projectid:String!,$contentid:String!) {
|
||||
mutation add_to_project($projectid:ID!,$contentid:ID!) {
|
||||
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||
projectNextItem {
|
||||
id
|
||||
@ -45,40 +47,33 @@ jobs:
|
||||
PROJECT_ID: "PN_kwDOAM0swc0sUA"
|
||||
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
move_spaces_issues:
|
||||
name: Move Spaces issues to Delight project board
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'A-Spaces') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-Space-Settings') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-Subspaces')
|
||||
steps:
|
||||
- uses: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338
|
||||
with:
|
||||
action-token: "${{ secrets.ELEMENT_BOT_TOKEN }}"
|
||||
project-url: "https://github.com/orgs/vector-im/projects/6"
|
||||
column-name: "📥 Inbox"
|
||||
label-name: "A-Spaces"
|
||||
- uses: octokit/graphql-action@v2.x
|
||||
id: add_to_delight2
|
||||
with:
|
||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||
query: |
|
||||
mutation add_to_project($projectid:String!,$contentid:String!) {
|
||||
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||
projectNextItem {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
projectid: ${{ env.PROJECT_ID }}
|
||||
contentid: ${{ github.event.issue.node_id }}
|
||||
env:
|
||||
PROJECT_ID: "PN_kwDOAM0swc1HvQ"
|
||||
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
# delight_issues_to_board:
|
||||
# name: Spaces issues to new Delight project board
|
||||
# runs-on: ubuntu-latest
|
||||
# if: >
|
||||
# contains(github.event.issue.labels.*.name, 'A-Spaces') ||
|
||||
# contains(github.event.issue.labels.*.name, 'A-Space-Settings') ||
|
||||
# contains(github.event.issue.labels.*.name, 'A-Subspaces')
|
||||
# steps:
|
||||
# - uses: octokit/graphql-action@v2.x
|
||||
# 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_kwDOAM0swc1HvQ"
|
||||
# GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
move_voice-message_issues:
|
||||
name: Move A-Voice Messages to Voice message board
|
||||
name: A-Voice Messages to voice message board
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'A-Voice Messages')
|
||||
@ -87,7 +82,7 @@ jobs:
|
||||
with:
|
||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||
query: |
|
||||
mutation add_to_project($projectid:String!,$contentid:String!) {
|
||||
mutation add_to_project($projectid:ID!,$contentid:ID!) {
|
||||
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||
projectNextItem {
|
||||
id
|
||||
@ -101,7 +96,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
move_threads_issues:
|
||||
name: Move A-Threads to Thread board
|
||||
name: A-Threads to Thread board
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'A-Threads')
|
||||
@ -110,7 +105,7 @@ jobs:
|
||||
with:
|
||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||
query: |
|
||||
mutation add_to_project($projectid:String!,$contentid:String!) {
|
||||
mutation add_to_project($projectid:ID!,$contentid:ID!) {
|
||||
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||
projectNextItem {
|
||||
id
|
||||
@ -122,3 +117,26 @@ jobs:
|
||||
env:
|
||||
PROJECT_ID: "PN_kwDOAM0swc0rRA"
|
||||
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 }}
|
||||
|
2
.github/workflows/triage-priority-bugs.yml
vendored
2
.github/workflows/triage-priority-bugs.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: Move P1 issues into the P1 column for the App Team and Crypto team
|
||||
name: Move P1 bugs to boards
|
||||
|
||||
on:
|
||||
issues:
|
||||
|
@ -24,6 +24,7 @@
|
||||
<w>pbkdf</w>
|
||||
<w>pids</w>
|
||||
<w>pkcs</w>
|
||||
<w>posthog</w>
|
||||
<w>previewable</w>
|
||||
<w>previewables</w>
|
||||
<w>pstn</w>
|
||||
|
32
CHANGES.md
32
CHANGES.md
@ -1,3 +1,35 @@
|
||||
Changes in Element v1.3.9 (2021-12-01)
|
||||
======================================
|
||||
|
||||
Features ✨
|
||||
----------
|
||||
- Voice messages: Persist drafts of voice messages when navigating between rooms ([#3922](https://github.com/vector-im/element-android/issues/3922))
|
||||
- Make Element Android Thread aware ([#4246](https://github.com/vector-im/element-android/issues/4246))
|
||||
- Iterate on the consent dialog of the identity server. ([#4577](https://github.com/vector-im/element-android/issues/4577))
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Fixes left over text when inserting emojis via the ':' menu and replaces the last typed ':' rather than the one at the end of the message ([#3449](https://github.com/vector-im/element-android/issues/3449))
|
||||
- Fixing queued voice message failing to send or retry ([#3833](https://github.com/vector-im/element-android/issues/3833))
|
||||
- Keeping device screen on whilst recording and playing back voice messages ([#4022](https://github.com/vector-im/element-android/issues/4022))
|
||||
- Allow voice messages to continue recording during device rotation ([#4067](https://github.com/vector-im/element-android/issues/4067))
|
||||
- Allowing users to hang up VOIP calls during the initialisation phase (avoids getting stuck in the call screen if something goes wrong) ([#4144](https://github.com/vector-im/element-android/issues/4144))
|
||||
- Make the verification shields the same in Element Web and Element Android ([#4338](https://github.com/vector-im/element-android/issues/4338))
|
||||
- Fix a display issue in the composer when the replied message is changed. ([#4343](https://github.com/vector-im/element-android/issues/4343))
|
||||
- Dismissing the Fdroid variant Listening for notifications on sign out, fixes crash when tapping the notification when signed out ([#4488](https://github.com/vector-im/element-android/issues/4488))
|
||||
- Fix a crash when displaying the bootstrap bottom sheet ([#4520](https://github.com/vector-im/element-android/issues/4520))
|
||||
- Remove duplicated settings declaration ([#4539](https://github.com/vector-im/element-android/issues/4539))
|
||||
- Fixes .ogg files failing to upload to rooms ([#4552](https://github.com/vector-im/element-android/issues/4552))
|
||||
- Add robustness when getting data from cursors ([#4605](https://github.com/vector-im/element-android/issues/4605))
|
||||
|
||||
Other changes
|
||||
-------------
|
||||
- Upgrade Jitsi lib (and so webrtc) from Jitsi android-sdk-3.1.0 to android-sdk-3.10.0 ([#4504](https://github.com/vector-im/element-android/issues/4504))
|
||||
- Improve crypto logs to help debug decryption failures ([#4507](https://github.com/vector-im/element-android/issues/4507))
|
||||
- Voice recording mic button refactor with small animation tweaks in preparation for voice drafts ([#4515](https://github.com/vector-im/element-android/issues/4515))
|
||||
- Remove requestModelBuild() from epoxy Controllers init{} block ([#4591](https://github.com/vector-im/element-android/issues/4591))
|
||||
|
||||
|
||||
Changes in Element v1.3.8 (2021-11-17)
|
||||
======================================
|
||||
|
||||
|
64
build.gradle
64
build.gradle
@ -1,12 +1,11 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
|
||||
apply from: 'dependencies.gradle'
|
||||
apply from: 'dependencies_groups.gradle'
|
||||
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
@ -37,45 +36,50 @@ allprojects {
|
||||
apply plugin: "org.jlleitschuh.gradle.ktlint"
|
||||
|
||||
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 {
|
||||
url 'https://jitpack.io'
|
||||
content {
|
||||
// Use this repo only for olm library
|
||||
includeGroupByRegex "org\\.matrix\\.gitlab\\.matrix-org"
|
||||
// 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'
|
||||
|
||||
// Chat effects
|
||||
includeGroupByRegex 'com\\.github\\.jetradarmobile'
|
||||
includeGroupByRegex 'nl\\.dionsegijn'
|
||||
|
||||
// Voice RecordView
|
||||
includeGroupByRegex 'com\\.github\\.Armen101'
|
||||
groups.jitpack.regex.each { includeGroupByRegex it }
|
||||
groups.jitpack.group.each { includeGroup it }
|
||||
}
|
||||
}
|
||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||
// Jitsi repo
|
||||
maven {
|
||||
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:
|
||||
// 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 {
|
||||
|
1
changelog.d/3473.bugfix
Normal file
1
changelog.d/3473.bugfix
Normal file
@ -0,0 +1 @@
|
||||
MSC2732: Olm fallback keys
|
@ -1 +0,0 @@
|
||||
Make Element Android Thread aware
|
1
changelog.d/4278.feature
Normal file
1
changelog.d/4278.feature
Normal file
@ -0,0 +1 @@
|
||||
Updates URL previews to match latest designs
|
1
changelog.d/4324.bugfix
Normal file
1
changelog.d/4324.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Fixes message menu showing when copying message urls
|
@ -1 +0,0 @@
|
||||
Make the verification shields the same in Element Web and Element Android
|
@ -1 +0,0 @@
|
||||
Fix a display issue in the composer when the replied message is changed.
|
@ -1 +0,0 @@
|
||||
Upgrade Jitsi lib (and so webrtc) from Jitsi android-sdk-3.1.0 to android-sdk-3.10.0
|
@ -1 +0,0 @@
|
||||
Voice recording mic button refactor with small animation tweaks in preparation for voice drafts
|
@ -1 +0,0 @@
|
||||
Fix a crash when displaying the bootstrap bottom sheet
|
1
changelog.d/4546.bugfix
Normal file
1
changelog.d/4546.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Fix lots of integration tests by introducing TestMatrix class and MatrixWorkerFactory.
|
1
changelog.d/4559.feature
Normal file
1
changelog.d/4559.feature
Normal file
@ -0,0 +1 @@
|
||||
Setup Analytics framework using PostHog. Analytics are disabled by default. Opt-in screen not automatically displayed yet.
|
1
changelog.d/4592.bugfix
Normal file
1
changelog.d/4592.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Fix empty Dev Tools screen issue.
|
1
changelog.d/4600.misc
Normal file
1
changelog.d/4600.misc
Normal file
@ -0,0 +1 @@
|
||||
At the very first room search after opening the app sometimes no results are displayed
|
1
changelog.d/4602.misc
Normal file
1
changelog.d/4602.misc
Normal file
@ -0,0 +1 @@
|
||||
There is no need to call job.cancel() when we are using viewModelScope()
|
1
changelog.d/4604.misc
Normal file
1
changelog.d/4604.misc
Normal file
@ -0,0 +1 @@
|
||||
Cleanup the layout files
|
1
changelog.d/4617.misc
Normal file
1
changelog.d/4617.misc
Normal file
@ -0,0 +1 @@
|
||||
Improve issue automation workflows
|
1
changelog.d/4621.bugfix
Normal file
1
changelog.d/4621.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Fix for outgoing voip call via sip bridge failing after 1 minute.
|
1
changelog.d/4626.misc
Normal file
1
changelog.d/4626.misc
Normal file
@ -0,0 +1 @@
|
||||
Introducing feature flagging to the login and notification settings flows
|
1
changelog.d/4636.bugfix
Normal file
1
changelog.d/4636.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Update log warning for call selection during voip calls.
|
1
changelog.d/4638.feature
Normal file
1
changelog.d/4638.feature
Normal file
@ -0,0 +1 @@
|
||||
Add a help section in the settings.
|
1
changelog.d/4645.misc
Normal file
1
changelog.d/4645.misc
Normal file
@ -0,0 +1 @@
|
||||
Debounce some clicks
|
1
changelog.d/4647.misc
Normal file
1
changelog.d/4647.misc
Normal file
@ -0,0 +1 @@
|
||||
Upgrade OLM to v3.2.7 and get it from our maven repository.
|
1
changelog.d/4650.misc
Normal file
1
changelog.d/4650.misc
Normal file
@ -0,0 +1 @@
|
||||
Cleanup id ref. Use type views instead
|
1
changelog.d/4653.feature
Normal file
1
changelog.d/4653.feature
Normal file
@ -0,0 +1 @@
|
||||
Poll Feature - Render in timeline
|
1
changelog.d/4660.feature
Normal file
1
changelog.d/4660.feature
Normal file
@ -0,0 +1 @@
|
||||
Create a legal screen in the setting to group all the different policies.
|
1
changelog.d/4666.misc
Normal file
1
changelog.d/4666.misc
Normal file
@ -0,0 +1 @@
|
||||
Add automation to move message bubbles issues to message bubbles board.
|
1
changelog.d/4670.misc
Normal file
1
changelog.d/4670.misc
Normal file
@ -0,0 +1 @@
|
||||
Add explicit dependency location, regarding the several maven repository. Also update some libraries (flexbox and alerter), and do some cleanup.
|
1
changelog.d/4671.misc
Normal file
1
changelog.d/4671.misc
Normal file
@ -0,0 +1 @@
|
||||
Fix graphql warning in issue workflow automation
|
1
changelog.d/4693.bugfix
Normal file
1
changelog.d/4693.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Fix possible crash when having identical subspaces in multiple root spaces
|
1
changelog.d/4698.bugfix
Normal file
1
changelog.d/4698.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Fix a crash in the timeline with some Emojis. Also migrate to androidx.emoji2
|
@ -7,11 +7,11 @@ ext.versions = [
|
||||
'targetCompat' : JavaVersion.VERSION_11,
|
||||
]
|
||||
|
||||
def gradle = "7.0.3"
|
||||
def gradle = "7.0.4"
|
||||
// Ref: https://kotlinlang.org/releases.html
|
||||
def kotlin = "1.5.31"
|
||||
def kotlinCoroutines = "1.5.2"
|
||||
def dagger = "2.40.1"
|
||||
def dagger = "2.40.5"
|
||||
def retrofit = "2.9.0"
|
||||
def arrow = "0.8.2"
|
||||
def markwon = "4.6.2"
|
||||
@ -19,7 +19,7 @@ def moshi = "1.12.0"
|
||||
def lifecycle = "2.4.0"
|
||||
def flowBinding = "1.2.0"
|
||||
def epoxy = "4.6.2"
|
||||
def mavericks = "2.4.0"
|
||||
def mavericks = "2.5.0"
|
||||
def glide = "4.12.0"
|
||||
def bigImageViewer = "1.8.1"
|
||||
def jjwt = "0.11.2"
|
||||
|
201
dependencies_groups.gradle
Normal file
201
dependencies_groups.gradle
Normal file
@ -0,0 +1,201 @@
|
||||
ext.groups = [
|
||||
jitpack : [
|
||||
regex: [
|
||||
],
|
||||
group: [
|
||||
'com.github.Armen101',
|
||||
'com.github.BillCarsonFr',
|
||||
'com.github.chrisbanes',
|
||||
'com.github.hyuwah',
|
||||
'com.github.jetradarmobile',
|
||||
'com.github.tapadoo',
|
||||
'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
16
docs/analytics.md
Normal 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.
|
2
fastlane/metadata/android/cs-CZ/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
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-RC2
|
2
fastlane/metadata/android/cs-CZ/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40103080.txt
Normal 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
|
2
fastlane/metadata/android/de-DE/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Hauptänderungen: Fehler bei Benachrichtigungen gefixt
|
||||
Ganze Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
2
fastlane/metadata/android/de-DE/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40103080.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Änderungen: Verschiedene Fehler behoben
|
||||
Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
2
fastlane/metadata/android/en-US/changelogs/40103090.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40103090.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Main changes in this version: Add support for voice message draft. Many bugfixes!
|
||||
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.9
|
2
fastlane/metadata/android/et/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
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-RC2
|
2
fastlane/metadata/android/et/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40103080.txt
Normal 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
|
2
fastlane/metadata/android/fa/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40103050.txt
Normal file
@ -0,0 +1,2 @@
|
||||
تغییرات اصلی در این نگارش: افزودن پشتیبانی حضور برای اتاقهای پیام مستقیم (یادداشت: حضور روی matrix.org از کار افتاده است). افزودن دوبارهٔ پشتیبانی اندروید خودرو.
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/fa/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40103060.txt
Normal file
@ -0,0 +1,2 @@
|
||||
تغییرات اصلی در این نگارش: افزودن پشتیبانی حضور برای اتاقهای پیام مستقیم (یادداشت: حضور روی matrix.org از کار افتاده است). افزودن دوبارهٔ پشتیبانی اندروید خودرو.
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
2
fastlane/metadata/android/fa/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
تغییرات اصلی در این نگارش: رفع اشکالهایی عمدتاً مربوط به آگاهیها.
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
2
fastlane/metadata/android/fa/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40103080.txt
Normal file
@ -0,0 +1,2 @@
|
||||
تغییرات عمده در این نگارش: رفع مشکلها!
|
||||
گزارش دگرکونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
2
fastlane/metadata/android/fr-FR/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
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-RC2
|
2
fastlane/metadata/android/fr-FR/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40103080.txt
Normal 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
|
2
fastlane/metadata/android/hu-HU/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/hu-HU/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
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-RC2
|
2
fastlane/metadata/android/hu-HU/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/hu-HU/changelogs/40103080.txt
Normal 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
|
2
fastlane/metadata/android/id/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Perubahan utama di versi ini: Perbaikan bug terutama untuk notifikasinya.
|
||||
Changelog lengkap: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
2
fastlane/metadata/android/id/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40103080.txt
Normal 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
|
@ -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
|
||||
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.
|
||||
|
||||
<b>Sangat aman</b>
|
||||
|
2
fastlane/metadata/android/it-IT/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/it-IT/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Modifiche principali in questa versione: correzioni riguardo le notifiche.
|
||||
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
2
fastlane/metadata/android/it-IT/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/it-IT/changelogs/40103080.txt
Normal 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
|
2
fastlane/metadata/android/pl/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/pl/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Główne zmiany w tej wersji: Poprawki błędów dotyczące głównie powiadomień
|
||||
Pełny changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
42
fastlane/metadata/android/pl/full_description.txt
Normal file
42
fastlane/metadata/android/pl/full_description.txt
Normal file
@ -0,0 +1,42 @@
|
||||
Element jest bezpiecznym komunikatorem oraz narzędziem do komunikacji w zespole które jest idealna do pracy zdalnej. Nasza aplikacja korzysta z szyfrowania end-to-end aby rozmowy wideo, udostępnianie plików oraz rozmowy głosowe były bezpieczne.
|
||||
|
||||
<b>Funkcje Element'a:</b>
|
||||
- Zaawansowane narzędzia komunikacji online
|
||||
- W pełni szyfrowane wiadomości które umożliwiają bezpieczniejszą komunikacje dla firm, a nawet dla pracowników zdalnych.
|
||||
- Zdecentralizowany czat bazowany na otwartym protokole Matrix
|
||||
- Bezpieczne udostępnianie plików wraz z szyfrowaniem danych podczas zarządzania projektami
|
||||
- Rozmowy z Voice over IP wraz z udostępnianiem ekranu
|
||||
- Prosta konfiguracja z ulubionymi narzędziami do kolaboracji, narzędziami do zarządzania projektami usługami VoIP oraz innymi aplikacjami do komunikacji w grupie
|
||||
|
||||
Element jest całkowicie inny od innych komunikatorów i aplikacji do kolaboracji. Pracuje na protokole Matrix, otwarto źródłowej sieci stworzonej dla bezpiecznych wiadomości i zdecentralizowanej komunikacji. Pozwala ona na własny hosting serwera dla maksymalnej własności i kontroli nad danymi oraz wiadomościami.
|
||||
|
||||
<b>Prywatność i szyfrowane wiadomości</b>
|
||||
Element broni cie przez niechcianymi wiadomościami, kopaniem informacji oraz cenzurą. Zabezpiecza wszystkie twoje dane, wideo które pozostaje wiadome tylko dla rozmawiających przez szyfrowanie end-to-end i weryfikacją krzyżową urządzeń.
|
||||
|
||||
Element daje kontrole nad twoją prywatnością i umożliwia bezpieczną komunikacje z kimkolwiek w sieci Matrix, lub z innymi firmami przez narzędzia do komunikacji integrując aplikacje takie jak Slack.
|
||||
|
||||
<b>Element może być hostowany samemu</b>
|
||||
Pozwala to na kontrolę nad twoimi wrażliwymi danymi oraz rozmowami, Element może być hostowany samemu lub pozwala wybrać dowolnego hosta bazowanego na Matrix'ie - otwarto-źródłowym standardzie, dla zdecentralizowanej komunikacji. Element daje tobie prywatność, bezpieczeństwo oraz elastyczność w integracji.
|
||||
|
||||
<b>Posiadaj naprawdę swoje dane</b>
|
||||
Ty decydujesz gdzie trzymasz swoje dane i wiadomości. Bez ryzyka wycieku lub dostępu firm trzecich.
|
||||
|
||||
Element daje ci kontrolę na wiele sposobów:
|
||||
1. Utwórz darmowe konto na publicznym serwerze matrix.org hostowanym przez twórców Matrix'a lub wybierz którykolwiek z tysięcy serwerów hostowanych przez wolontariuszy
|
||||
2. Hostuj samemu swoje konto przez własny serwer na twojej infrastrukturze
|
||||
3. Zarejestruj się na specjalnym serwerze poprzez subskrybowanie hostingu na platformie Element Martix Services
|
||||
|
||||
<b>Otwarte wiadomości i kolaboracja</b>
|
||||
Możesz rozmawiać z kimkolwiek w sieci Matrix, nie ważne czy korzystają z Element'a, czy z innej aplikacji wspierającej protokół Matrix, a nawet z osobami korzystającymi z innych komunikatorów.
|
||||
|
||||
<b>Niesamowicie bezpieczny</b>
|
||||
Prawdziwe szyfrowanie end-to-end (tylko osoby w konwersacji mogą odszyfrować wiadomości), a także krzyżowa weryfikacja urządzeń.
|
||||
|
||||
<b>Pełna komunikacja i integracja</b>
|
||||
Wiadomości, rozmowy głosowe i wideo, udostępnianie plików, ekranu, a nawet integracja z botami i widżetami. Twórz pokoje, społeczności, pozostań w kontakcie i załatwiaj to co chcesz.
|
||||
|
||||
<b>Kontynuuj gdzie skończyłeś</b>
|
||||
Pozostań zawsze w kontakcie poprzez pełną synchornizację między urządzeniami oraz w sieci na https://app.element.io
|
||||
|
||||
<b>Otwarto źródłowy</b>
|
||||
Element Android jest otwarto-źródłowym projektem, hostowanym na platformie GitHub. Prosimy o zgłaszanie wszelkich błędów i/lub wsparcie w tworzeniu naszego projektu na https://github.com/vector-im/element-android
|
1
fastlane/metadata/android/pl/short_description.txt
Normal file
1
fastlane/metadata/android/pl/short_description.txt
Normal file
@ -0,0 +1 @@
|
||||
Grupowy komunikator - szyfrowane wiadomosci, grupowe czaty oraz rozmowy wideo
|
1
fastlane/metadata/android/pl/title.txt
Normal file
1
fastlane/metadata/android/pl/title.txt
Normal file
@ -0,0 +1 @@
|
||||
Element - Bezpieczny Komunikator
|
2
fastlane/metadata/android/pt-BR/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
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-RC2
|
2
fastlane/metadata/android/pt-BR/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40103080.txt
Normal 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
|
2
fastlane/metadata/android/sk/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40103070.txt
Normal 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
|
2
fastlane/metadata/android/sk/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40103080.txt
Normal 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
|
@ -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
|
||||
|
2
fastlane/metadata/android/sq/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40103070.txt
Normal 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
|
2
fastlane/metadata/android/sq/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40103080.txt
Normal 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
|
2
fastlane/metadata/android/sv-SE/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/sv-SE/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
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-RC2
|
2
fastlane/metadata/android/sv-SE/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/sv-SE/changelogs/40103080.txt
Normal 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
|
2
fastlane/metadata/android/uk/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Основні зміни в цій версії: виправлення помилок в основному у повідомленнях.
|
||||
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
2
fastlane/metadata/android/uk/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40103080.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Основні зміни у цій версії: Виправлення помилок!
|
||||
Повний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
@ -1,7 +1,7 @@
|
||||
Element — це і безпечний месенджер, і застосунок для співпраці команди, який ідеально підходить для групових бесід під час віддаленої роботи. Цей застосунок для спілкування застосовує наскрізне шифрування для забезпечення відеоконференцій, обміну файлами та голосових викликів.
|
||||
Element — це і безпечний месенджер, і застосунок для співпраці команди, який ідеально підходить спілкування групами під час віддаленої роботи. Цей застосунок для спілкування використовує наскрізне шифрування для забезпечення відеоконференцій, обміну файлами та голосових викликів.
|
||||
|
||||
<b>Можливості Element включають:</b>
|
||||
- Розширені засоби спілкування в Інтернеті
|
||||
- Розширені засоби онлайн-спілкування
|
||||
- Повністю зашифровані повідомлення для надання можливості безпечнішого корпоративного спілкування, навіть для віддалених працівників
|
||||
- Децентралізований чат на основі відкритого коду Matrix
|
||||
- Безпечний обмін файлами із зашифрованими даними для керування проєктами
|
||||
@ -33,10 +33,10 @@ Element надає такі можливості на вибір:
|
||||
Справжнє наскрізне шифрування (лише учасники бесіди можуть розшифровувати повідомлення) та взаємне підписування пристроїв.
|
||||
|
||||
<b>Повноцінні спілкування та інтеграція</b>
|
||||
Обмін повідомленнями, голосові та відеовиклики, обмін файлами, спільний доступ до екрана та ціла купа інтеграцій, ботів та розширень. Створюйте кімнати, спільноти, залишайтеся на зв’язку та виконуйте завдання.
|
||||
Обмін повідомленнями, голосові та відеовиклики, обмін файлами, спільний доступ до екрана та ціла купа інтеграцій, ботів та віджетів. Створюйте кімнати, спільноти, залишайтеся на зв’язку та виконуйте завдання.
|
||||
|
||||
<b>Продовжуйте, де зупинилися</b>
|
||||
Залишайтеся на зв'язку, де б ви не знаходились, з повністю синхронізованою історією повідомлень на всіх своїх пристроях та в Інтернеті за адресою https://app.element.io
|
||||
|
||||
<b>Відкритий код</b>
|
||||
Element для Android це проєкт з відкритим кодом, розміщений GitHub. Будь ласка, повідомте про помилки та/або сприяйте його розвитку на https://github.com/vector-im/element-android
|
||||
Element для Android — це проєкт з відкритим кодом, розміщений на GitHub. Повідомляйте про помилки та/або допомагайте його розвитку на https://github.com/vector-im/element-android
|
||||
|
2
fastlane/metadata/android/zh-CN/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/zh-CN/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
此版本的主要变化:主要关于通知的错误修复。
|
||||
完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
2
fastlane/metadata/android/zh-CN/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/zh-CN/changelogs/40103080.txt
Normal file
@ -0,0 +1,2 @@
|
||||
此版本主要变化:Bug 修复!
|
||||
完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
2
fastlane/metadata/android/zh-TW/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40103070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
此版本中的主要變動:主要關於通知的臭蟲修復。
|
||||
完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
2
fastlane/metadata/android/zh-TW/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40103080.txt
Normal file
@ -0,0 +1,2 @@
|
||||
此版本中的主要變動:臭蟲修復!
|
||||
完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=00b273629df4ce46e68df232161d5a7c4e495b9a029ce6e0420f071e21316867
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-all.zip
|
||||
distributionSha256Sum=b75392c5625a88bccd58a574552a5a323edca82dab5942d2d41097f809c6bcce
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -34,7 +34,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/appBarLayout">
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -334,7 +334,6 @@
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -5,7 +5,6 @@
|
||||
<item name="android:visibility">visible</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="Theme.Debug.Light" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||
<!-- Keep all default value -->
|
||||
</style>
|
||||
|
@ -11,5 +11,3 @@
|
||||
<changeImageTransform />
|
||||
|
||||
</transitionSet>
|
||||
|
||||
|
||||
|
6
library/ui-styles/src/main/res/values-sw600dp/tablet.xml
Normal file
6
library/ui-styles/src/main/res/values-sw600dp/tablet.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<dimen name="width_percent">0.6</dimen>
|
||||
|
||||
</resources>
|
6
library/ui-styles/src/main/res/values-sw720dp/tablet.xml
Normal file
6
library/ui-styles/src/main/res/values-sw720dp/tablet.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<dimen name="width_percent">0.5</dimen>
|
||||
|
||||
</resources>
|
@ -32,7 +32,6 @@
|
||||
<dimen name="call_pip_width">88dp</dimen>
|
||||
<dimen name="call_pip_radius">8dp</dimen>
|
||||
|
||||
|
||||
<dimen name="item_form_min_height">76dp</dimen>
|
||||
|
||||
<!-- Max width for some buttons -->
|
||||
@ -41,4 +40,6 @@
|
||||
<!-- Navigation Drawer -->
|
||||
<dimen name="navigation_drawer_max_width">320dp</dimen>
|
||||
|
||||
<!-- Preview Url -->
|
||||
<dimen name="preview_url_view_corner_radius">8dp</dimen>
|
||||
</resources>
|
@ -20,7 +20,6 @@
|
||||
<color name="palette_prune">#5C56F5</color>
|
||||
<color name="palette_links">#0086E6</color>
|
||||
|
||||
|
||||
<!-- For light themes -->
|
||||
<color name="palette_gray_25">#F4F6FA</color>
|
||||
<color name="palette_gray_50">#E3E8F0</color>
|
||||
|
@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="BadgeFloatingActionButton">
|
||||
|
@ -49,7 +49,6 @@
|
||||
<item name="android:backgroundTint">@android:color/black</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="Widget.Vector.Button.Outlined.SocialLogin.Facebook">
|
||||
<item name="icon">@drawable/ic_social_facebook</item>
|
||||
</style>
|
||||
@ -68,7 +67,6 @@
|
||||
<item name="android:backgroundTint">#3877EA</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="Widget.Vector.Button.Outlined.SocialLogin.Twitter">
|
||||
<item name="icon">@drawable/ic_social_twitter</item>
|
||||
</style>
|
||||
@ -85,7 +83,6 @@
|
||||
<item name="android:backgroundTint">#5D9EC9</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="Widget.Vector.Button.Outlined.SocialLogin.Apple">
|
||||
<item name="icon">@drawable/ic_social_apple</item>
|
||||
</style>
|
||||
@ -118,5 +115,4 @@
|
||||
<item name="android:backgroundTint">@android:color/black</item>
|
||||
</style>
|
||||
|
||||
|
||||
</resources>
|
6
library/ui-styles/src/main/res/values/tablet.xml
Normal file
6
library/ui-styles/src/main/res/values/tablet.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<dimen name="width_percent">1</dimen>
|
||||
|
||||
</resources>
|
@ -9,7 +9,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
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.
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
|
||||
buildConfigField "String", "SDK_VERSION", "\"1.3.9\""
|
||||
buildConfigField "String", "SDK_VERSION", "\"1.3.10\""
|
||||
|
||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
||||
@ -115,7 +115,7 @@ dependencies {
|
||||
implementation libs.squareup.retrofit
|
||||
implementation libs.squareup.retrofitMoshi
|
||||
|
||||
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.2"))
|
||||
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3"))
|
||||
implementation 'com.squareup.okhttp3:okhttp'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor'
|
||||
implementation 'com.squareup.okhttp3:okhttp-urlconnection'
|
||||
@ -140,8 +140,8 @@ dependencies {
|
||||
implementation libs.arrow.core
|
||||
implementation libs.arrow.instances
|
||||
|
||||
// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
|
||||
implementation 'org.matrix.gitlab.matrix-org:olm:3.2.4'
|
||||
// olm lib is now hosted by maven at https://gitlab.matrix.org/api/v4/projects/27/packages/maven
|
||||
implementation 'org.matrix.android:olm:3.2.7'
|
||||
|
||||
// DI
|
||||
implementation libs.dagger.dagger
|
||||
@ -158,10 +158,10 @@ dependencies {
|
||||
implementation libs.apache.commonsImaging
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.37'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.39'
|
||||
|
||||
testImplementation libs.tests.junit
|
||||
testImplementation 'org.robolectric:robolectric:4.7.1'
|
||||
testImplementation 'org.robolectric:robolectric:4.7.3'
|
||||
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||
testImplementation libs.mockk.mockk
|
||||
|
@ -20,9 +20,10 @@ import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
|
||||
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@ -30,7 +31,6 @@ import kotlinx.coroutines.withTimeout
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
@ -45,7 +45,7 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||
import java.util.ArrayList
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -56,13 +56,14 @@ import java.util.concurrent.TimeUnit
|
||||
*/
|
||||
class CommonTestHelper(context: Context) {
|
||||
|
||||
val matrix: Matrix
|
||||
internal val matrix: TestMatrix
|
||||
val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
|
||||
fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestNetworkModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
|
||||
fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
|
||||
|
||||
init {
|
||||
UiThreadStatement.runOnUiThread {
|
||||
Matrix.initialize(
|
||||
TestMatrix.initialize(
|
||||
context,
|
||||
MatrixConfiguration(
|
||||
applicationFlavor = "TestFlavor",
|
||||
@ -70,7 +71,7 @@ class CommonTestHelper(context: Context) {
|
||||
)
|
||||
)
|
||||
}
|
||||
matrix = Matrix.getInstance(context)
|
||||
matrix = TestMatrix.getInstance(context)
|
||||
}
|
||||
|
||||
fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session {
|
||||
@ -95,33 +96,47 @@ class CommonTestHelper(context: Context) {
|
||||
*
|
||||
* @param session the session to sync
|
||||
*/
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis) {
|
||||
fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis * 10) {
|
||||
val lock = CountDownLatch(1)
|
||||
|
||||
val job = GlobalScope.launch(Dispatchers.Main) {
|
||||
session.open()
|
||||
}
|
||||
runBlocking { job.join() }
|
||||
|
||||
session.startSync(true)
|
||||
|
||||
val syncLiveData = runBlocking(Dispatchers.Main) {
|
||||
session.getSyncStateLive()
|
||||
}
|
||||
val syncObserver = object : Observer<SyncState> {
|
||||
override fun onChanged(t: SyncState?) {
|
||||
if (session.hasAlreadySynced()) {
|
||||
lock.countDown()
|
||||
syncLiveData.removeObserver(this)
|
||||
coroutineScope.launch {
|
||||
session.startSync(true)
|
||||
val syncLiveData = session.getSyncStateLive()
|
||||
val syncObserver = object : Observer<SyncState> {
|
||||
override fun onChanged(t: SyncState?) {
|
||||
if (session.hasAlreadySynced()) {
|
||||
lock.countDown()
|
||||
syncLiveData.removeObserver(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
syncLiveData.observeForever(syncObserver)
|
||||
}
|
||||
GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) }
|
||||
|
||||
await(lock, timeout)
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods clear the cache and waits for initialSync
|
||||
*
|
||||
* @param session the session to sync
|
||||
*/
|
||||
fun clearCacheAndSync(session: Session, timeout: Long = TestConstants.timeOutMillis) {
|
||||
waitWithLatch(timeout) { latch ->
|
||||
session.clearCache()
|
||||
val syncLiveData = session.getSyncStateLive()
|
||||
val syncObserver = object : Observer<SyncState> {
|
||||
override fun onChanged(t: SyncState?) {
|
||||
if (session.hasAlreadySynced()) {
|
||||
Timber.v("Clear cache and synced")
|
||||
syncLiveData.removeObserver(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
syncLiveData.observeForever(syncObserver)
|
||||
session.startSync(true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends text messages in a room
|
||||
*
|
||||
@ -130,46 +145,57 @@ class CommonTestHelper(context: Context) {
|
||||
* @param nbOfMessages the number of time the message will be sent
|
||||
*/
|
||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
|
||||
val timeline = room.createTimeline(null, TimelineSettings(10))
|
||||
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
|
||||
val latch = CountDownLatch(1)
|
||||
val timelineListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
val timeline = room.createTimeline(null, TimelineSettings(10))
|
||||
timeline.start()
|
||||
waitWithLatch(timeout + 1_000L * nbOfMessages) { latch ->
|
||||
val timelineListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
// noop
|
||||
}
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val newMessages = snapshot
|
||||
.filter { it.root.sendState == SendState.SYNCED }
|
||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val newMessages = snapshot
|
||||
.filter { it.root.sendState == SendState.SYNCED }
|
||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
|
||||
|
||||
if (newMessages.size == nbOfMessages) {
|
||||
sentEvents.addAll(newMessages)
|
||||
// Remove listener now, if not at the next update sendEvents could change
|
||||
timeline.removeListener(this)
|
||||
latch.countDown()
|
||||
Timber.v("New synced message size: ${newMessages.size}")
|
||||
if (newMessages.size == nbOfMessages) {
|
||||
sentEvents.addAll(newMessages)
|
||||
// Remove listener now, if not at the next update sendEvents could change
|
||||
timeline.removeListener(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
timeline.addListener(timelineListener)
|
||||
sendTextMessagesBatched(room, message, nbOfMessages)
|
||||
}
|
||||
timeline.start()
|
||||
timeline.addListener(timelineListener)
|
||||
for (i in 0 until nbOfMessages) {
|
||||
room.sendTextMessage(message + " #" + (i + 1))
|
||||
}
|
||||
// Wait 3 second more per message
|
||||
await(latch, timeout = timeout + 3_000L * nbOfMessages)
|
||||
timeline.dispose()
|
||||
|
||||
// Check that all events has been created
|
||||
assertEquals("Message number do not match $sentEvents", nbOfMessages.toLong(), sentEvents.size.toLong())
|
||||
|
||||
return sentEvents
|
||||
}
|
||||
|
||||
/**
|
||||
* Will send nb of messages provided by count parameter but waits a bit every 10 messages to avoid gap in sync
|
||||
*/
|
||||
private fun sendTextMessagesBatched(room: Room, message: String, count: Int) {
|
||||
(1 until count + 1)
|
||||
.map { "$message #$it" }
|
||||
.chunked(10)
|
||||
.forEach { batchedMessages ->
|
||||
batchedMessages.forEach { formattedMessage ->
|
||||
room.sendTextMessage(formattedMessage)
|
||||
}
|
||||
Thread.sleep(1_000L)
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
/**
|
||||
@ -239,10 +265,10 @@ class CommonTestHelper(context: Context) {
|
||||
|
||||
assertTrue(registrationResult is RegistrationResult.Success)
|
||||
val session = (registrationResult as RegistrationResult.Success).session
|
||||
session.open()
|
||||
if (sessionTestParams.withInitialSync) {
|
||||
syncSession(session, 60_000)
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
@ -267,7 +293,7 @@ class CommonTestHelper(context: Context) {
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice")
|
||||
}
|
||||
|
||||
session.open()
|
||||
if (sessionTestParams.withInitialSync) {
|
||||
syncSession(session)
|
||||
}
|
||||
@ -332,22 +358,21 @@ class CommonTestHelper(context: Context) {
|
||||
assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
|
||||
}
|
||||
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
|
||||
GlobalScope.launch {
|
||||
while (true) {
|
||||
delay(1000)
|
||||
if (condition()) {
|
||||
latch.countDown()
|
||||
return@launch
|
||||
}
|
||||
suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
|
||||
while (true) {
|
||||
delay(1000)
|
||||
if (condition()) {
|
||||
latch.countDown()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, block: (CountDownLatch) -> Unit) {
|
||||
fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, dispatcher: CoroutineDispatcher = Dispatchers.Main, block: suspend (CountDownLatch) -> Unit) {
|
||||
val latch = CountDownLatch(1)
|
||||
block(latch)
|
||||
coroutineScope.launch(dispatcher) {
|
||||
block(latch)
|
||||
}
|
||||
await(latch, timeout)
|
||||
}
|
||||
|
||||
|
@ -19,10 +19,6 @@ package org.matrix.android.sdk.common
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Observer
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
@ -31,6 +27,7 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
||||
@ -44,16 +41,16 @@ import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
||||
|
||||
private val messagesFromAlice: List<String> = listOf("0 - Hello I'm Alice!", "4 - Go!")
|
||||
private val messagesFromBob: List<String> = listOf("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.")
|
||||
@ -64,27 +61,33 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
* @return alice session
|
||||
*/
|
||||
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
|
||||
val roomId = mTestHelper.runBlockingTest {
|
||||
val roomId = testHelper.runBlockingTest {
|
||||
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
|
||||
}
|
||||
|
||||
if (encryptedRoom) {
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
|
||||
mTestHelper.runBlockingTest {
|
||||
testHelper.waitWithLatch { latch ->
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
room.enableEncryption()
|
||||
val roomSummaryLive = room.getRoomSummaryLive()
|
||||
val roomSummaryObserver = object : Observer<Optional<RoomSummary>> {
|
||||
override fun onChanged(roomSummary: Optional<RoomSummary>) {
|
||||
if (roomSummary.getOrNull()?.isEncrypted.orFalse()) {
|
||||
roomSummaryLive.removeObserver(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
roomSummaryLive.observeForever(roomSummaryObserver)
|
||||
}
|
||||
}
|
||||
|
||||
return CryptoTestData(roomId, listOf(aliceSession))
|
||||
}
|
||||
|
||||
/**
|
||||
* @return alice and bob sessions
|
||||
*/
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData {
|
||||
val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
@ -92,54 +95,37 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
|
||||
val aliceRoom = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
||||
val bobSession = testHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
||||
|
||||
val lock1 = CountDownLatch(1)
|
||||
|
||||
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
|
||||
bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
}
|
||||
|
||||
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (t?.isNotEmpty() == true) {
|
||||
lock1.countDown()
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
testHelper.waitWithLatch { latch ->
|
||||
val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (t?.isNotEmpty() == true) {
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||
}
|
||||
|
||||
mTestHelper.runBlockingTest {
|
||||
aliceRoom.invite(bobSession.myUserId)
|
||||
}
|
||||
|
||||
mTestHelper.await(lock1)
|
||||
|
||||
val lock = CountDownLatch(1)
|
||||
|
||||
val roomJoinedObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (bobSession.getRoom(aliceRoomId)
|
||||
?.getRoomMember(aliceSession.myUserId)
|
||||
?.membership == Membership.JOIN) {
|
||||
lock.countDown()
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
testHelper.waitWithLatch { latch ->
|
||||
val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
val roomJoinedObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (bobSession.getRoom(aliceRoomId)
|
||||
?.getRoomMember(bobSession.myUserId)
|
||||
?.membership == Membership.JOIN) {
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
bobRoomSummariesLive.observeForever(roomJoinedObserver)
|
||||
bobSession.joinRoom(aliceRoomId)
|
||||
}
|
||||
|
||||
mTestHelper.runBlockingTest { bobSession.joinRoom(aliceRoomId) }
|
||||
|
||||
mTestHelper.await(lock)
|
||||
|
||||
// Ensure bob can send messages to the room
|
||||
// val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
// assertNotNull(roomFromBobPOV.powerLevels)
|
||||
@ -171,13 +157,13 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
* @Return Sam session
|
||||
*/
|
||||
fun createSamAccountAndInviteToTheRoom(room: Room): Session {
|
||||
val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
|
||||
val samSession = testHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
|
||||
|
||||
mTestHelper.runBlockingTest {
|
||||
testHelper.runBlockingTest {
|
||||
room.invite(samSession.myUserId, null)
|
||||
}
|
||||
|
||||
mTestHelper.runBlockingTest {
|
||||
testHelper.runBlockingTest {
|
||||
samSession.joinRoom(room.roomId, null, emptyList())
|
||||
}
|
||||
|
||||
@ -194,23 +180,20 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
bobSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
// Alice sends a message
|
||||
mTestHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1)
|
||||
// roomFromAlicePOV.sendTextMessage(messagesFromAlice[0])
|
||||
testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1)
|
||||
|
||||
// Bob send 3 messages
|
||||
mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1)
|
||||
mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1)
|
||||
mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1)
|
||||
testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1)
|
||||
testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1)
|
||||
testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1)
|
||||
// Alice sends a message
|
||||
mTestHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1)
|
||||
|
||||
testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1)
|
||||
return cryptoTestData
|
||||
}
|
||||
|
||||
@ -256,60 +239,44 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
fun createDM(alice: Session, bob: Session): String {
|
||||
val roomId = mTestHelper.runBlockingTest {
|
||||
alice.createDirectRoom(bob.myUserId)
|
||||
}
|
||||
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
|
||||
bob.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
}
|
||||
|
||||
var roomId: String = ""
|
||||
testHelper.waitWithLatch { latch ->
|
||||
roomId = alice.createDirectRoom(bob.myUserId)
|
||||
val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
val indexOfFirst = t?.indexOfFirst { it.roomId == roomId } ?: -1
|
||||
if (indexOfFirst != -1) {
|
||||
latch.countDown()
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||
}
|
||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||
}
|
||||
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
|
||||
bob.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (bob.getRoom(roomId)
|
||||
?.getRoomMember(bob.myUserId)
|
||||
?.membership == Membership.JOIN) {
|
||||
latch.countDown()
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||
}
|
||||
|
||||
mTestHelper.runBlockingTest { bob.joinRoom(roomId) }
|
||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||
bob.joinRoom(roomId)
|
||||
}
|
||||
|
||||
return roomId
|
||||
}
|
||||
|
||||
fun initializeCrossSigning(session: Session) {
|
||||
mTestHelper.doSync<Unit> {
|
||||
testHelper.doSync<Unit> {
|
||||
session.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
@ -346,8 +313,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
var bobPovTx: IncomingSasVerificationTransaction? = null
|
||||
|
||||
// wait for alice to get the ready
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
||||
@ -359,16 +326,16 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
}
|
||||
}
|
||||
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? OutgoingSasVerificationTransaction
|
||||
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}")
|
||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
}
|
||||
// wait for alice to get the ready
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
||||
@ -383,38 +350,38 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
bobPovTx!!.userHasVerifiedShortCode()
|
||||
alicePovTx!!.userHasVerifiedShortCode()
|
||||
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
|
||||
}
|
||||
}
|
||||
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomId = mTestHelper.runBlockingTest {
|
||||
val roomId = testHelper.runBlockingTest {
|
||||
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
|
||||
}
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
|
||||
mTestHelper.runBlockingTest {
|
||||
testHelper.runBlockingTest {
|
||||
room.enableEncryption()
|
||||
}
|
||||
|
||||
val sessions = mutableListOf(aliceSession)
|
||||
for (index in 1 until numberOfMembers) {
|
||||
val session = mTestHelper.createAccount("User_$index", defaultSessionParams)
|
||||
mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
|
||||
val session = testHelper.createAccount("User_$index", defaultSessionParams)
|
||||
testHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
|
||||
println("TEST -> " + session.myUserId + " invited")
|
||||
mTestHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) }
|
||||
testHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) }
|
||||
println("TEST -> " + session.myUserId + " joined")
|
||||
sessions.add(session)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api
|
||||
package org.matrix.android.sdk.common
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
@ -24,27 +24,27 @@ import androidx.work.Configuration
|
||||
import androidx.work.WorkManager
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.BuildConfig
|
||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||
import org.matrix.android.sdk.api.network.ApiInterceptorListener
|
||||
import org.matrix.android.sdk.api.network.ApiPath
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.common.DaggerTestMatrixComponent
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.network.ApiInterceptor
|
||||
import org.matrix.android.sdk.internal.network.UserAgentHolder
|
||||
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
||||
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This is the main entry point to the matrix sdk.
|
||||
* To get the singleton instance, use getInstance static method.
|
||||
* This mimics the Matrix class but using TestMatrixComponent internally instead of regular MatrixComponent.
|
||||
*/
|
||||
class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
internal class TestMatrix constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
|
||||
@Inject internal lateinit var legacySessionImporter: LegacySessionImporter
|
||||
@Inject internal lateinit var authenticationService: AuthenticationService
|
||||
@ -55,15 +55,18 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||
@Inject internal lateinit var sessionManager: SessionManager
|
||||
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
|
||||
@Inject internal lateinit var apiInterceptor: ApiInterceptor
|
||||
@Inject internal lateinit var matrixWorkerFactory: MatrixWorkerFactory
|
||||
|
||||
private val uiHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
init {
|
||||
Monarchy.init(context)
|
||||
DaggerTestMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
|
||||
if (context.applicationContext !is Configuration.Provider) {
|
||||
WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build())
|
||||
}
|
||||
val configuration = Configuration.Builder()
|
||||
.setExecutor(Executors.newCachedThreadPool())
|
||||
.setWorkerFactory(matrixWorkerFactory)
|
||||
.build()
|
||||
WorkManager.initialize(context, configuration)
|
||||
uiHandler.post {
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||
}
|
||||
@ -93,21 +96,21 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||
|
||||
companion object {
|
||||
|
||||
private lateinit var instance: Matrix
|
||||
private lateinit var instance: TestMatrix
|
||||
private val isInit = AtomicBoolean(false)
|
||||
|
||||
fun initialize(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
if (isInit.compareAndSet(false, true)) {
|
||||
instance = Matrix(context.applicationContext, matrixConfiguration)
|
||||
instance = TestMatrix(context.applicationContext, matrixConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstance(context: Context): Matrix {
|
||||
fun getInstance(context: Context): TestMatrix {
|
||||
if (isInit.compareAndSet(false, true)) {
|
||||
val appContext = context.applicationContext
|
||||
if (appContext is MatrixConfiguration.Provider) {
|
||||
val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration()
|
||||
instance = Matrix(appContext, matrixConfiguration)
|
||||
instance = TestMatrix(appContext, matrixConfiguration)
|
||||
} else {
|
||||
throw IllegalStateException("Matrix is not initialized properly." +
|
||||
" You should call Matrix.initialize or let your application implements MatrixConfiguration.Provider.")
|
@ -34,12 +34,13 @@ import org.matrix.android.sdk.internal.util.system.SystemModule
|
||||
NetworkModule::class,
|
||||
AuthModule::class,
|
||||
RawModule::class,
|
||||
SystemModule::class,
|
||||
TestNetworkModule::class
|
||||
SystemModule::class
|
||||
])
|
||||
@MatrixScope
|
||||
internal interface TestMatrixComponent : MatrixComponent {
|
||||
|
||||
fun inject(matrix: TestMatrix)
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(@BindsInstance context: Context,
|
||||
|
@ -18,10 +18,39 @@ package org.matrix.android.sdk.common
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import org.matrix.android.sdk.internal.di.MatrixComponent
|
||||
import org.matrix.android.sdk.internal.di.MatrixScope
|
||||
import org.matrix.android.sdk.internal.session.MockHttpInterceptor
|
||||
import org.matrix.android.sdk.internal.session.TestInterceptor
|
||||
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
||||
|
||||
@Module
|
||||
internal abstract class TestModule {
|
||||
@Binds
|
||||
abstract fun providesMatrixComponent(testMatrixComponent: TestMatrixComponent): MatrixComponent
|
||||
|
||||
@Module
|
||||
companion object {
|
||||
|
||||
val interceptors = ArrayList<TestInterceptor>()
|
||||
|
||||
fun interceptorForSession(sessionId: String): TestInterceptor? = interceptors.firstOrNull { it.sessionId == sessionId }
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
@MockHttpInterceptor
|
||||
fun providesTestInterceptor(): TestInterceptor? {
|
||||
return MockOkHttpInterceptor().also {
|
||||
interceptors.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
@MatrixScope
|
||||
fun providesBackgroundDetectionObserver(): BackgroundDetectionObserver {
|
||||
return TestBackgroundDetectionObserver()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,12 +36,12 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class PreShareKeysTest : InstrumentedTest {
|
||||
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||
private val testHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
@Test
|
||||
fun ensure_outbound_session_happy_path() {
|
||||
val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val e2eRoomID = testData.roomId
|
||||
val aliceSession = testData.firstSession
|
||||
val bobSession = testData.secondSession!!
|
||||
@ -58,12 +58,12 @@ class PreShareKeysTest : InstrumentedTest {
|
||||
Log.d("#Test", "Room Key Received from alice $preShareCount")
|
||||
|
||||
// Force presharing of new outbound key
|
||||
mTestHelper.doSync<Unit> {
|
||||
testHelper.doSync<Unit> {
|
||||
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID, it)
|
||||
}
|
||||
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val newGossipCount = bobSession.cryptoService().getGossipingEvents().count {
|
||||
it.senderId == aliceSession.myUserId &&
|
||||
it.getClearType() == EventType.ROOM_KEY
|
||||
@ -88,16 +88,16 @@ class PreShareKeysTest : InstrumentedTest {
|
||||
assertEquals("The session received by bob should match what alice sent", 0, sharedIndex)
|
||||
|
||||
// Just send a real message as test
|
||||
val sentEvent = mTestHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first()
|
||||
val sentEvent = testHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first()
|
||||
|
||||
assertEquals(megolmSessionId, sentEvent.root.content.toModel<EncryptedEventContent>()?.sessionId, "Unexpected megolm session")
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
bobSession.getRoom(e2eRoomID)?.getTimeLineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
|
||||
mTestHelper.signOutAndClose(aliceSession)
|
||||
mTestHelper.signOutAndClose(bobSession)
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
testHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
}
|
||||
|
@ -62,8 +62,8 @@ import kotlin.coroutines.resume
|
||||
class UnwedgingTest : InstrumentedTest {
|
||||
|
||||
private lateinit var messagesReceivedByBob: List<TimelineEvent>
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||
private val testHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
@ -85,7 +85,7 @@ class UnwedgingTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun testUnwedging() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
@ -133,7 +133,7 @@ class UnwedgingTest : InstrumentedTest {
|
||||
roomFromAlicePOV.sendTextMessage("First message")
|
||||
|
||||
// Wait for the message to be received by Bob
|
||||
mTestHelper.await(latch)
|
||||
testHelper.await(latch)
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 1
|
||||
@ -161,7 +161,7 @@ class UnwedgingTest : InstrumentedTest {
|
||||
roomFromAlicePOV.sendTextMessage("Second message")
|
||||
|
||||
// Wait for the message to be received by Bob
|
||||
mTestHelper.await(latch)
|
||||
testHelper.await(latch)
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 2
|
||||
@ -179,7 +179,7 @@ class UnwedgingTest : InstrumentedTest {
|
||||
aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
|
||||
|
||||
// Wait for the message to be received by Bob
|
||||
mTestHelper.waitWithLatch {
|
||||
testHelper.waitWithLatch {
|
||||
bobEventsListener = createEventListener(it, 3)
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
messagesReceivedByBob = emptyList()
|
||||
@ -201,11 +201,11 @@ class UnwedgingTest : InstrumentedTest {
|
||||
Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[1].root.getClearType())
|
||||
Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[2].root.getClearType())
|
||||
// Bob Should not be able to decrypt last message, because session could not be sent as the olm channel was wedged
|
||||
mTestHelper.await(bobFinalLatch)
|
||||
testHelper.await(bobFinalLatch)
|
||||
bobTimeline.removeListener(bobHasThreeDecryptedEventsListener)
|
||||
|
||||
// It's a trick to force key request on fail to decrypt
|
||||
mTestHelper.doSync<Unit> {
|
||||
testHelper.doSync<Unit> {
|
||||
bobSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
@ -222,8 +222,8 @@ class UnwedgingTest : InstrumentedTest {
|
||||
}
|
||||
|
||||
// Wait until we received back the key
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
// we should get back the key and be able to decrypt
|
||||
val result = tryOrNull {
|
||||
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
|
||||
@ -235,7 +235,7 @@ class UnwedgingTest : InstrumentedTest {
|
||||
|
||||
bobTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
private fun createEventListener(latch: CountDownLatch, expectedNumberOfMessages: Int): Timeline.Listener {
|
||||
|
@ -45,14 +45,14 @@ import kotlin.coroutines.resume
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class XSigningTest : InstrumentedTest {
|
||||
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||
private val testHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
@Test
|
||||
fun test_InitializeAndStoreKeys() {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
testHelper.doSync<Unit> {
|
||||
aliceSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
@ -79,12 +79,12 @@ class XSigningTest : InstrumentedTest {
|
||||
|
||||
assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified())
|
||||
|
||||
mTestHelper.signOutAndClose(aliceSession)
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_CrossSigningCheckBobSeesTheKeys() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
@ -98,21 +98,21 @@ class XSigningTest : InstrumentedTest {
|
||||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
testHelper.doSync<Unit> {
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
}, it) }
|
||||
|
||||
// Check that alice can see bob keys
|
||||
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
|
||||
testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
|
||||
|
||||
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
|
||||
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
|
||||
@ -124,13 +124,13 @@ class XSigningTest : InstrumentedTest {
|
||||
|
||||
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
|
||||
|
||||
mTestHelper.signOutAndClose(aliceSession)
|
||||
mTestHelper.signOutAndClose(bobSession)
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
testHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_CrossSigningTestAliceTrustBobNewDevice() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
@ -144,12 +144,12 @@ class XSigningTest : InstrumentedTest {
|
||||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
}, it) }
|
||||
mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
@ -157,21 +157,21 @@ class XSigningTest : InstrumentedTest {
|
||||
|
||||
// Check that alice can see bob keys
|
||||
val bobUserId = bobSession.myUserId
|
||||
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) }
|
||||
testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) }
|
||||
|
||||
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
|
||||
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
|
||||
|
||||
mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) }
|
||||
testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) }
|
||||
|
||||
// Now bobs logs in on a new device and verifies it
|
||||
// We will want to test that in alice POV, this new device would be trusted by cross signing
|
||||
|
||||
val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true))
|
||||
val bobSession2 = testHelper.logIntoAccount(bobUserId, SessionTestParams(true))
|
||||
val bobSecondDeviceId = bobSession2.sessionParams.deviceId!!
|
||||
|
||||
// Check that bob first session sees the new login
|
||||
val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
val data = testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
|
||||
}
|
||||
|
||||
@ -183,12 +183,12 @@ class XSigningTest : InstrumentedTest {
|
||||
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
|
||||
|
||||
// Manually mark it as trusted from first session
|
||||
mTestHelper.doSync<Unit> {
|
||||
testHelper.doSync<Unit> {
|
||||
bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId, it)
|
||||
}
|
||||
|
||||
// Now alice should cross trust bob's second device
|
||||
val data2 = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
val data2 = testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
|
||||
}
|
||||
|
||||
@ -200,8 +200,8 @@ class XSigningTest : InstrumentedTest {
|
||||
val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
|
||||
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
|
||||
|
||||
mTestHelper.signOutAndClose(aliceSession)
|
||||
mTestHelper.signOutAndClose(bobSession)
|
||||
mTestHelper.signOutAndClose(bobSession2)
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
testHelper.signOutAndClose(bobSession)
|
||||
testHelper.signOutAndClose(bobSession2)
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user