Merge branch 'develop' into feature/fre/start_dm_on_first_msg

* develop: (174 commits)
  Bump libphonenumber from 8.12.50 to 8.12.51
  LoadRoomMember: fix presence
  Cleanup
  LoadRoomMembers: add changelog
  LoadRoomMembers: handle room member event a bit more efficiently
  LoadRoomMembers: exclude Membership.Leave
  LoadRoomMembers: divide by chunk
  Bump soloader from 0.10.3 to 0.10.4
  Code review fix.
  Try no using the gradle daemon on CI
  Harmonize values of `CI_GRADLE_ARG_PROPERTIES`
  removing unused dependencies and marking soloader and ignored from dependency check (as it's dynamic)
  Remove non necessary prefix in logs
  Adding changelog entry
  Updating the unit tests
  Stopping existing active live when starting a new one
  Avoid multiple PR from Dependabot when Flipper is upgraded.
  Change context inside the get live summary use case
  Use a TestDispatcher in the FakeSession
  Code review fixes.
  ...
This commit is contained in:
Florian Renaud 2022-06-30 11:48:55 +02:00
commit 3f087eb632
289 changed files with 9514 additions and 1219 deletions

View File

@ -8,8 +8,9 @@ on:
# Enrich gradle.properties for CI/CD # Enrich gradle.properties for CI/CD
env: env:
CI_GRADLE_ARG_PROPERTIES: > CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx2g -Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false -Porg.gradle.parallel=false
--no-daemon
jobs: jobs:
debug: debug:

View File

@ -13,6 +13,7 @@ env:
CI_GRADLE_ARG_PROPERTIES: > CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx4g -Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false -Porg.gradle.parallel=false
--no-daemon
jobs: jobs:

View File

@ -9,6 +9,8 @@ on:
env: env:
CI_GRADLE_ARG_PROPERTIES: > CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx4g -Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false
--no-daemon
jobs: jobs:
check: check:
@ -140,7 +142,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- name: Lint analysis - name: Lint analysis
run: ./gradlew clean :vector:lint --stacktrace run: ./gradlew clean :vector:lint --stacktrace $CI_GRADLE_ARG_PROPERTIES
- name: Upload reports - name: Upload reports
if: always() if: always()
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@ -173,7 +175,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- name: Lint ${{ matrix.target }} release - name: Lint ${{ matrix.target }} release
run: ./gradlew clean lint${{ matrix.target }}Release --stacktrace run: ./gradlew clean lint${{ matrix.target }}Release --stacktrace $CI_GRADLE_ARG_PROPERTIES
- name: Upload ${{ matrix.target }} linting report - name: Upload ${{ matrix.target }} linting report
if: always() if: always()
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@ -193,7 +195,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Run detekt - name: Run detekt
run: | run: |
./gradlew detekt ./gradlew detekt $CI_GRADLE_ARG_PROPERTIES
- name: Upload reports - name: Upload reports
if: always() if: always()
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View File

@ -8,8 +8,9 @@ on:
# Enrich gradle.properties for CI/CD # Enrich gradle.properties for CI/CD
env: env:
CI_GRADLE_ARG_PROPERTIES: > CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx2g -Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false -Porg.gradle.parallel=false
--no-daemon
jobs: jobs:
tests: tests:
@ -49,7 +50,10 @@ jobs:
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true disable-animations: true
emulator-build: 7425822 emulator-build: 7425822
script: ./gradlew theCodeCoverageReport -Pandroid.testInstrumentationRunnerArguments.notPackage=im.vector.app.ui --stacktrace $CI_GRADLE_ARG_PROPERTIES script: |
./gradlew unitTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES
./gradlew instrumentationTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES
./gradlew generateCoverageReport --stacktrace $CI_GRADLE_ARG_PROPERTIES
# NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves stes.tests.outcome = 'failure' # NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves stes.tests.outcome = 'failure'
- name: Run all the codecoverage tests at once (retry if emulator failed) - name: Run all the codecoverage tests at once (retry if emulator failed)
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
@ -62,7 +66,10 @@ jobs:
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true disable-animations: true
emulator-build: 7425822 emulator-build: 7425822
script: ./gradlew theCodeCoverageReport -Pandroid.testInstrumentationRunnerArguments.notPackage=im.vector.app.ui --stacktrace $CI_GRADLE_ARG_PROPERTIES script: |
./gradlew unitTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES
./gradlew instrumentationTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES
./gradlew generateCoverageReport --stacktrace $CI_GRADLE_ARG_PROPERTIES
- run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
if: always() # we may have failed a previous step and retried, that's OK if: always() # we may have failed a previous step and retried, that's OK
env: env:

2
.gitignore vendored
View File

@ -16,4 +16,4 @@
/fastlane/private /fastlane/private
/fastlane/report.xml /fastlane/report.xml
/library/build /**/build

View File

@ -1,3 +1,25 @@
Changes in Element v1.4.25 (2022-06-27)
=======================================
Bugfixes 🐛
----------
- Second attempt to fix session database migration to version 30.
Changes in Element v1.4.24 (2022-06-22)
=======================================
Bugfixes 🐛
----------
- First attempt to fix session database migration to version 30.
Changes in Element v1.4.23 (2022-06-21)
=======================================
Bugfixes 🐛
----------
- Fix loop in timeline and simplify management of chunks and timeline events. ([#6318](https://github.com/vector-im/element-android/issues/6318))
Changes in Element v1.4.22 (2022-06-14) Changes in Element v1.4.22 (2022-06-14)
======================================= =======================================

View File

@ -28,8 +28,8 @@ buildscript {
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
classpath "com.likethesalad.android:stem-plugin:2.1.1" classpath "com.likethesalad.android:stem-plugin:2.1.1"
classpath 'org.owasp:dependency-check-gradle:7.1.0.1' classpath 'org.owasp:dependency-check-gradle:7.1.1'
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.21" classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.0"
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
@ -43,7 +43,7 @@ plugins {
id "io.gitlab.arturbosch.detekt" version "1.20.0" id "io.gitlab.arturbosch.detekt" version "1.20.0"
// Dependency Analysis // Dependency Analysis
id 'com.autonomousapps.dependency-analysis' version "1.5.0" id 'com.autonomousapps.dependency-analysis' version "1.9.0"
} }
// https://github.com/jeremylong/DependencyCheck // https://github.com/jeremylong/DependencyCheck
@ -168,7 +168,7 @@ def launchTask = getGradle()
.toString() .toString()
.toLowerCase() .toLowerCase()
if (launchTask.contains("codeCoverageReport".toLowerCase())) { if (launchTask.contains("coverage".toLowerCase())) {
apply from: 'coverage.gradle' apply from: 'coverage.gradle'
} }
@ -191,7 +191,7 @@ sonarqube {
property "sonar.links.issue", "https://github.com/vector-im/element-android/issues" property "sonar.links.issue", "https://github.com/vector-im/element-android/issues"
property "sonar.organization", "new_vector_ltd_organization" property "sonar.organization", "new_vector_ltd_organization"
property "sonar.java.coveragePlugin", "jacoco" property "sonar.java.coveragePlugin", "jacoco"
property "sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/jacoco/theCodeCoverageReport/theCodeCoverageReport.xml" property "sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/jacoco/generateCoverageReport/generateCoverageReport.xml"
property "sonar.login", project.hasProperty("SONAR_LOGIN") ? SONAR_LOGIN : "invalid" property "sonar.login", project.hasProperty("SONAR_LOGIN") ? SONAR_LOGIN : "invalid"
} }
} }
@ -252,11 +252,7 @@ dependencyAnalysis {
exclude("org.json:json") // Used in unit tests, overwrites the one bundled into Android exclude("org.json:json") // Used in unit tests, overwrites the one bundled into Android
} }
} }
project(":library:ui-styles") { project(":library:ui-styles")
onUnusedDependencies {
exclude("com.github.vector-im:PFLockScreen-Android") // False positive
}
}
project(":matrix-sdk-android") { project(":matrix-sdk-android") {
onUnusedDependencies { onUnusedDependencies {
exclude("io.reactivex.rxjava2:rxkotlin") // Transitively required for mocking realm as monarchy doesn't expose Rx exclude("io.reactivex.rxjava2:rxkotlin") // Transitively required for mocking realm as monarchy doesn't expose Rx
@ -271,6 +267,8 @@ dependencyAnalysis {
onUnusedDependencies { onUnusedDependencies {
// False positives // False positives
exclude( exclude(
"androidx.fragment:fragment-testing",
"com.facebook.soloader:soloader",
"com.vanniktech:emoji-google", "com.vanniktech:emoji-google",
"com.vanniktech:emoji-material", "com.vanniktech:emoji-material",
"org.maplibre.gl:android-plugin-annotation-v9", "org.maplibre.gl:android-plugin-annotation-v9",

1
changelog.d/5821.bugfix Normal file
View File

@ -0,0 +1 @@
Fixes concurrent modification crash when signing out or launching the app

1
changelog.d/5864.sdk Normal file
View File

@ -0,0 +1 @@
Group all location sharing related API into LocationSharingService

1
changelog.d/6101.bugfix Normal file
View File

@ -0,0 +1 @@
Refactor - better naming, return native user id and not sip user id and create a dm with the native user instead of with the sip user.

1
changelog.d/6155.misc Normal file
View File

@ -0,0 +1 @@
Add unit tests for LiveLocationAggregationProcessor code

1
changelog.d/6191.sdk Normal file
View File

@ -0,0 +1 @@
Add support for MSC2457 - opting in or out of logging out all devices when changing password

1
changelog.d/6217.feature Normal file
View File

@ -0,0 +1 @@
Improve lock screen implementation.

1
changelog.d/6315.bugfix Normal file
View File

@ -0,0 +1 @@
[Location sharing] Fix crash when starting/stopping a live when offline

1
changelog.d/6318.bugfix Normal file
View File

@ -0,0 +1 @@
Fix loop in timeline and simplify management of chunks and timeline events.

1
changelog.d/6326.bugfix Normal file
View File

@ -0,0 +1 @@
Update design and behaviour on widget permission bottom sheet

1
changelog.d/6328.bugfix Normal file
View File

@ -0,0 +1 @@
Fix | Some user verification requests couldn't be accepted/declined

1
changelog.d/6329.misc Normal file
View File

@ -0,0 +1 @@
Fix flaky test in voice recording feature.

1
changelog.d/6349.bugfix Normal file
View File

@ -0,0 +1 @@
[Location sharing] Fix stop of a live not possible from another device

1
changelog.d/6350.feature Normal file
View File

@ -0,0 +1 @@
Promote live location labs flag

1
changelog.d/6357.bugfix Normal file
View File

@ -0,0 +1 @@
Fix backslash escapes in formatted messages

1
changelog.d/6364.feature Normal file
View File

@ -0,0 +1 @@
[Location sharing] - Stop any active live before starting a new one

1
changelog.d/6366.misc Normal file
View File

@ -0,0 +1 @@
Poll view state unit tests

2
changelog.d/6369.feature Normal file
View File

@ -0,0 +1,2 @@
Expose pusher profile tag in advanced settings

1
changelog.d/6371.bugfix Normal file
View File

@ -0,0 +1 @@
Fixes wrong error message when signing in with wrong credentials

1
changelog.d/6375.bugfix Normal file
View File

@ -0,0 +1 @@
[Location Share] - Adding missing prefix "u=" for uncertainty in geo URI

1
changelog.d/6394.misc Normal file
View File

@ -0,0 +1 @@
Let LoadRoomMembersTask insert by chunk to release db.

1
changelog.d/6396.doc Normal file
View File

@ -0,0 +1 @@
Update the PR process doc to come back to one reviewer with optional additional reviewers.

View File

@ -24,11 +24,13 @@ def excludes = [
def initializeReport(report, projects, classExcludes) { def initializeReport(report, projects, classExcludes) {
projects.each { project -> project.apply plugin: 'jacoco' } projects.each { project -> project.apply plugin: 'jacoco' }
report.executionData { fileTree(rootProject.rootDir.absolutePath).include(
"**/build/outputs/unit_test_code_coverage/**/*.exec",
"**/build/outputs/code_coverage/**/coverage.ec"
) }
report.executionData {
fileTree(rootProject.rootDir.absolutePath).include(
"**/build/**/*.exec",
"**/build/outputs/code_coverage/**/coverage.ec",
)
}
report.reports { report.reports {
xml.enabled true xml.enabled true
html.enabled true html.enabled true
@ -43,13 +45,11 @@ def initializeReport(report, projects, classExcludes) {
switch (project) { switch (project) {
case { project.plugins.hasPlugin("com.android.application") }: case { project.plugins.hasPlugin("com.android.application") }:
androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/gplayDebug") androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/gplayDebug")
androidSourceDirs.add("${project.buildDir}/generated/source/kapt/gplayDebug")
androidSourceDirs.add("${project.projectDir}/src/main/kotlin") androidSourceDirs.add("${project.projectDir}/src/main/kotlin")
androidSourceDirs.add("${project.projectDir}/src/main/java") androidSourceDirs.add("${project.projectDir}/src/main/java")
break break
case { project.plugins.hasPlugin("com.android.library") }: case { project.plugins.hasPlugin("com.android.library") }:
androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/debug") androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/debug")
androidSourceDirs.add("${project.buildDir}/generated/source/kapt/debug")
androidSourceDirs.add("${project.projectDir}/src/main/kotlin") androidSourceDirs.add("${project.projectDir}/src/main/kotlin")
androidSourceDirs.add("${project.projectDir}/src/main/java") androidSourceDirs.add("${project.projectDir}/src/main/java")
break break
@ -70,18 +70,21 @@ def collectProjects(predicate) {
return subprojects.findAll { it.buildFile.isFile() && predicate(it) } return subprojects.findAll { it.buildFile.isFile() && predicate(it) }
} }
task theCodeCoverageReport(type: JacocoReport) { task generateCoverageReport(type: JacocoReport) {
outputs.upToDateWhen { false } outputs.upToDateWhen { false }
rootProject.apply plugin: 'jacoco' rootProject.apply plugin: 'jacoco'
tasks.withType(Test) { def projects = collectProjects { ['vector', 'matrix-sdk-android'].contains(it.name) }
jacoco.includeNoLocationClasses = true
}
def projects = collectProjects { ['vector','matrix-sdk-android'].contains(it.name) }
dependsOn {
[':vector:testGplayDebugUnitTest'] +
[':vector:connectedGplayDebugAndroidTest'] +
[':matrix-sdk-android:testDebugUnitTest'] +
[':matrix-sdk-android:connectedDebugAndroidTest']
}
initializeReport(it, projects, excludes) initializeReport(it, projects, excludes)
} }
task unitTestsWithCoverage(type: GradleBuild) {
// the 7.1.3 android gradle plugin has a bug where enableTestCoverage generates invalid coverage
startParameter.projectProperties.coverage = [enableTestCoverage: false]
tasks = [':vector:testGplayDebugUnitTest', ':matrix-sdk-android:testDebugUnitTest']
}
task instrumentationTestsWithCoverage(type: GradleBuild) {
startParameter.projectProperties.coverage = [enableTestCoverage: true]
startParameter.projectProperties['android.testInstrumentationRunnerArguments.notPackage'] = 'im.vector.app.ui'
tasks = [':vector:connectedGplayDebugAndroidTest', 'matrix-sdk-android:connectedDebugAndroidTest']
}

View File

@ -13,7 +13,7 @@ ext.versions = [
def gradle = "7.1.3" def gradle = "7.1.3"
// Ref: https://kotlinlang.org/releases.html // Ref: https://kotlinlang.org/releases.html
def kotlin = "1.6.21" def kotlin = "1.6.21"
def kotlinCoroutines = "1.6.2" def kotlinCoroutines = "1.6.3"
def dagger = "2.42" def dagger = "2.42"
def retrofit = "2.9.0" def retrofit = "2.9.0"
def arrow = "0.8.2" def arrow = "0.8.2"
@ -21,20 +21,21 @@ def markwon = "4.6.2"
def moshi = "1.13.0" def moshi = "1.13.0"
def lifecycle = "2.4.1" def lifecycle = "2.4.1"
def flowBinding = "1.2.0" def flowBinding = "1.2.0"
def flipper = "0.151.1"
def epoxy = "4.6.2" def epoxy = "4.6.2"
def mavericks = "2.6.1" def mavericks = "2.7.0"
def glide = "4.13.2" def glide = "4.13.2"
def bigImageViewer = "1.8.1" def bigImageViewer = "1.8.1"
def jjwt = "0.11.5" def jjwt = "0.11.5"
def vanniktechEmoji = "0.15.0" def vanniktechEmoji = "0.15.0"
def fragment = "1.4.1"
// Testing // Testing
def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819 def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819
def espresso = "3.4.0" def espresso = "3.4.0"
def androidxTest = "1.4.0" def androidxTest = "1.4.0"
def androidxOrchestrator = "1.4.1" def androidxOrchestrator = "1.4.1"
ext.libs = [ ext.libs = [
gradle : [ gradle : [
'gradlePlugin' : "com.android.tools.build:gradle:$gradle", 'gradlePlugin' : "com.android.tools.build:gradle:$gradle",
@ -48,13 +49,16 @@ ext.libs = [
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines" 'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
], ],
androidx : [ androidx : [
'annotation' : "androidx.annotation:annotation:1.3.0", 'annotation' : "androidx.annotation:annotation:1.4.0",
'activity' : "androidx.activity:activity:1.4.0", 'activity' : "androidx.activity:activity:1.4.0",
'annotations' : "androidx.annotation:annotation:1.3.0",
'appCompat' : "androidx.appcompat:appcompat:1.4.2", 'appCompat' : "androidx.appcompat:appcompat:1.4.2",
'biometric' : "androidx.biometric:biometric:1.1.0",
'core' : "androidx.core:core-ktx:1.8.0", 'core' : "androidx.core:core-ktx:1.8.0",
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1", 'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3", 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3",
'fragmentKtx' : "androidx.fragment:fragment-ktx:1.4.1", 'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment",
'fragmentTesting' : "androidx.fragment:fragment-testing:$fragment",
'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.4", 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.4",
'work' : "androidx.work:work-runtime-ktx:2.7.1", 'work' : "androidx.work:work-runtime-ktx:2.7.1",
'autoFill' : "androidx.autofill:autofill:1.1.0", 'autoFill' : "androidx.autofill:autofill:1.1.0",
@ -85,8 +89,13 @@ ext.libs = [
'dagger' : "com.google.dagger:dagger:$dagger", 'dagger' : "com.google.dagger:dagger:$dagger",
'daggerCompiler' : "com.google.dagger:dagger-compiler:$dagger", 'daggerCompiler' : "com.google.dagger:dagger-compiler:$dagger",
'hilt' : "com.google.dagger:hilt-android:$dagger", 'hilt' : "com.google.dagger:hilt-android:$dagger",
'hiltAndroidTesting' : "com.google.dagger:hilt-android-testing:$dagger",
'hiltCompiler' : "com.google.dagger:hilt-compiler:$dagger" 'hiltCompiler' : "com.google.dagger:hilt-compiler:$dagger"
], ],
flipper : [
'flipper' : "com.facebook.flipper:flipper:$flipper",
'flipperNetworkPlugin' : "com.facebook.flipper:flipper-network-plugin:$flipper",
],
squareup : [ squareup : [
'moshi' : "com.squareup.moshi:moshi:$moshi", 'moshi' : "com.squareup.moshi:moshi:$moshi",
'moshiKt' : "com.squareup.moshi:moshi-kotlin:$moshi", 'moshiKt' : "com.squareup.moshi:moshi-kotlin:$moshi",
@ -155,3 +164,5 @@ ext.libs = [
'junit' : "junit:junit:4.13.2" 'junit' : "junit:junit:4.13.2"
] ]
] ]

View File

@ -83,15 +83,16 @@ Exceptions can occur:
##### PR Review Assignment ##### PR Review Assignment
We use automatic assignment for PR reviews. A PR is automatically routed by GitHub to 2 team members using the round robin algorithm. The process is the following: We use automatic assignment for PR reviews. **A PR is automatically routed by GitHub to one team member** using the round robin algorithm. Additional reviewers can be used for complex changes or when the first reviewer is not confident enough on the changes.
The process is the following:
- The PR creator can assign specific people if they have another Android developer in their team or they think a specific reviewer should take a look at the PR. - The PR creator selects the [element-android-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) team as a reviewer.
- If there are missing reviewers, the PR creator assigns the [element-android-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) team as a reviewer. - GitHub automatically assign the reviewer. If the reviewer is not available (holiday, etc.), remove them and set again the team, GitHub will select another reviewer.
- GitHub automatically assigns other reviewers. If one of the chosen reviewers is not available (holiday, etc.), remove them and set again the team, GitHub will select another reviewer. - Alternatively, the PR creator can directly assign specific people if they have another Android developer in their team or they think a specific reviewer should take a look at their PR.
- Reviewers get a notification to make the review: they review the code following the good practice (see the rest of this document). - Reviewers get a notification to make the review: they review the code following the good practice (see the rest of this document).
- After making their own review, if they feel not confident enough, they can ask another person for a full review, or they can tag someone within a PR comment to check specific lines. - After making their own review, if they feel not confident enough, they can ask another person for a full review, or they can tag someone within a PR comment to check specific lines.
For PRs coming from the community, the issue wrangler can assign either the team [element-android-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) or any members directly. For PRs coming from the community, the issue wrangler can assign either the team [element-android-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) or any member directly.
##### PR review time ##### PR review time
@ -102,6 +103,7 @@ Some tips to achieve it:
- Set up your GH notifications correctly - Set up your GH notifications correctly
- Check your pulls page: [https://github.com/pulls](https://github.com/pulls) - Check your pulls page: [https://github.com/pulls](https://github.com/pulls)
- Check your pending assigned PRs before starting or resuming your day to day tasks - Check your pending assigned PRs before starting or resuming your day to day tasks
- If you are busy with high priority tasks, inform the author. They will find another developer
It is hard to define a deadline for a review. It depends on the PR size and the complexity. Let's start with a goal of 24h (working day!) for a PR smaller than 500 lines. If bigger, the submitter and the reviewer should discuss. It is hard to define a deadline for a review. It depends on the PR size and the complexity. Let's start with a goal of 24h (working day!) for a PR smaller than 500 lines. If bigger, the submitter and the reviewer should discuss.

View File

@ -0,0 +1,2 @@
Main changes in this version: Various bug fixes and stability improvements.
Full changelog: https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
Main changes in this version: Various bug fixes and stability improvements.
Full changelog: https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
Main changes in this version: Various bug fixes and stability improvements.
Full changelog: https://github.com/vector-im/element-android/releases

View File

@ -56,8 +56,6 @@ dependencies {
implementation libs.google.material implementation libs.google.material
// Pref theme // Pref theme
implementation libs.androidx.preferenceKtx implementation libs.androidx.preferenceKtx
// PFLockScreen attrs
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
// dialpad dimen // dialpad dimen
implementation 'im.dlg:android-dialer:1.2.5' implementation 'im.dlg:android-dialer:1.2.5'
} }

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<gradient
android:type="linear"
android:startColor="#f28433"
android:endColor="#e0574c"
android:angle="270" />
</shape>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid
android:color="#44FFFFFF"/>
<size
android:width="70dp"
android:height="70dp"/>
</shape>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:color="@color/lockscreen_code"
android:width="1px"/>
<size
android:width="@dimen/lockscreen_code_size"
android:height="@dimen/lockscreen_code_size"/>
</shape>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:color="@color/lockscreen_code"
android:width="1px"/>
<solid
android:color="@color/lockscreen_code"/>
<size
android:width="@dimen/lockscreen_code_size"
android:height="@dimen/lockscreen_code_size"/>
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<padding android:padding="1dp" />
<corners android:radius="5dp" />
<solid android:color="#44FFFFFF" />
</shape>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- NOTE: order is important (the first matching state(s) is what is rendered) -->
<item
android:state_checked="true"
android:drawable="@drawable/lockscreen_circle_code_fill"/>
<item
android:drawable="@drawable/lockscreen_circle_code_empty"/>
</selector>

View File

@ -0,0 +1,7 @@
<vector android:height="14.498462dp" android:viewportHeight="589"
android:viewportWidth="975" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:pathData="M951,24H302.88L34,294L302.88,565H951V24Z"
android:strokeColor="#000000" android:strokeWidth="48"/>
<path android:pathData="M411.5,120L757.5,467.5M757.5,120L411.5,467.5"
android:strokeColor="#000000" android:strokeWidth="48"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#000000" android:pathData="M17.81,4.47c-0.08,0 -0.16,-0.02 -0.23,-0.06C15.66,3.42 14,3 12.01,3c-1.98,0 -3.86,0.47 -5.57,1.41 -0.24,0.13 -0.54,0.04 -0.68,-0.2 -0.13,-0.24 -0.04,-0.55 0.2,-0.68C7.82,2.52 9.86,2 12.01,2c2.13,0 3.99,0.47 6.03,1.52 0.25,0.13 0.34,0.43 0.21,0.67 -0.09,0.18 -0.26,0.28 -0.44,0.28zM3.5,9.72c-0.1,0 -0.2,-0.03 -0.29,-0.09 -0.23,-0.16 -0.28,-0.47 -0.12,-0.7 0.99,-1.4 2.25,-2.5 3.75,-3.27C9.98,4.04 14,4.03 17.15,5.65c1.5,0.77 2.76,1.86 3.75,3.25 0.16,0.22 0.11,0.54 -0.12,0.7 -0.23,0.16 -0.54,0.11 -0.7,-0.12 -0.9,-1.26 -2.04,-2.25 -3.39,-2.94 -2.87,-1.47 -6.54,-1.47 -9.4,0.01 -1.36,0.7 -2.5,1.7 -3.4,2.96 -0.08,0.14 -0.23,0.21 -0.39,0.21zM9.75,21.79c-0.13,0 -0.26,-0.05 -0.35,-0.15 -0.87,-0.87 -1.34,-1.43 -2.01,-2.64 -0.69,-1.23 -1.05,-2.73 -1.05,-4.34 0,-2.97 2.54,-5.39 5.66,-5.39s5.66,2.42 5.66,5.39c0,0.28 -0.22,0.5 -0.5,0.5s-0.5,-0.22 -0.5,-0.5c0,-2.42 -2.09,-4.39 -4.66,-4.39 -2.57,0 -4.66,1.97 -4.66,4.39 0,1.44 0.32,2.77 0.93,3.85 0.64,1.15 1.08,1.64 1.85,2.42 0.19,0.2 0.19,0.51 0,0.71 -0.11,0.1 -0.24,0.15 -0.37,0.15zM16.92,19.94c-1.19,0 -2.24,-0.3 -3.1,-0.89 -1.49,-1.01 -2.38,-2.65 -2.38,-4.39 0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5c0,1.41 0.72,2.74 1.94,3.56 0.71,0.48 1.54,0.71 2.54,0.71 0.24,0 0.64,-0.03 1.04,-0.1 0.27,-0.05 0.53,0.13 0.58,0.41 0.05,0.27 -0.13,0.53 -0.41,0.58 -0.57,0.11 -1.07,0.12 -1.21,0.12zM14.91,22c-0.04,0 -0.09,-0.01 -0.13,-0.02 -1.59,-0.44 -2.63,-1.03 -3.72,-2.1 -1.4,-1.39 -2.17,-3.24 -2.17,-5.22 0,-1.62 1.38,-2.94 3.08,-2.94 1.7,0 3.08,1.32 3.08,2.94 0,1.07 0.93,1.94 2.08,1.94s2.08,-0.87 2.08,-1.94c0,-3.77 -3.25,-6.83 -7.25,-6.83 -2.84,0 -5.44,1.58 -6.61,4.03 -0.39,0.81 -0.59,1.76 -0.59,2.8 0,0.78 0.07,2.01 0.67,3.61 0.1,0.26 -0.03,0.55 -0.29,0.64 -0.26,0.1 -0.55,-0.04 -0.64,-0.29 -0.49,-1.31 -0.73,-2.61 -0.73,-3.96 0,-1.2 0.23,-2.29 0.68,-3.24 1.33,-2.79 4.28,-4.6 7.51,-4.6 4.55,0 8.25,3.51 8.25,7.83 0,1.62 -1.38,2.94 -3.08,2.94s-3.08,-1.32 -3.08,-2.94c0,-1.07 -0.93,-1.94 -2.08,-1.94s-2.08,0.87 -2.08,1.94c0,1.71 0.66,3.31 1.87,4.51 0.95,0.94 1.86,1.46 3.27,1.85 0.27,0.07 0.42,0.35 0.35,0.61 -0.05,0.23 -0.26,0.38 -0.47,0.38z"/>
</vector>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/lockscreen_white_selector">
<item android:id="@android:id/mask">
<shape android:shape="oval">
<padding android:padding="1dp" />
<corners android:radius="5dp" />
<solid android:color="@color/lockscreen_white_selector"/>
</shape>
</item>
<item>
<selector>
<item android:state_selected="true">
<color android:color="@android:color/darker_gray"/>
</item>
<item android:state_activated="true">
<color android:color="@android:color/white"/>
</item>
<item>
<color android:color="@android:color/transparent"/>
</item>
</selector>
</item>
</ripple>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_focused="true" android:state_enabled="false" android:state_pressed="true"
android:drawable="@drawable/lockscreen_circle_key_selector" />
<item android:state_focused="true" android:state_enabled="false"
android:drawable="@drawable/lockscreen_circle_key_selector" />
<item android:state_focused="true" android:state_pressed="true"
android:drawable="@drawable/lockscreen_circle_key_selector" />
<item android:state_focused="false" android:state_pressed="true"
android:drawable="@drawable/lockscreen_circle_key_selector" />
<item android:state_focused="true"
android:drawable="@drawable/lockscreen_circle_key_selector" />
<item
android:drawable="@drawable/lockscreen_circle_background" />
</selector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="lockscreen_button_size">60dp</dimen>
<dimen name="lockscreen_button_margin_vertical">15dp</dimen>
</resources>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="lockscreen_key_button_theme" format="reference|integer"/>
<attr name="lockscreen_theme" format="reference|integer"/>
<attr name="lockscreen_fingerprint_button_theme" format="reference|integer"/>
<attr name="lockscreen_delete_button_theme" format="reference|integer"/>
<attr name="lockscreen_code_view_theme" format="reference|integer"/>
<attr name="lockscreen_title_theme" format="reference|integer"/>
<attr name="lockscreen_subtitle_theme" format="reference|integer"/>
<attr name="lockscreen_hint_theme" format="reference|integer"/>
<attr name="lockscreen_next_theme" format="reference|integer"/>
</resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="lockscreen_code">#ffffff</color>
<color name="lockscreen_white_selector">#66ffffff</color>
<color name="lockscreen_hint_color">#42000000</color>
<color name="lockscreen_warning_color">#f4511e</color>
<color name="lockscreen_success_color">#009688</color>
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="lockscreen_button_size">70dp</dimen>
<dimen name="lockscreen_button_margin_vertical">25dp</dimen>
<dimen name="lockscreen_code_size">10dp</dimen>
<dimen name="lockscreen_code_margin">5dp</dimen>
</resources>

View File

@ -0,0 +1,17 @@
<resources>
<string name="lockscreen_cancel">Cancel</string>
<string name="lockscreen_use_pin">Use pin</string>
<string name="lockscreen_sign_in">Sign in</string>
<string name="lockscreen_next">Next</string>
<string name="lockscreen_forgot">Forgot?</string>
<string name="lockscreen_title">Input pin code or use biometric authentication</string>
<string name="lockscreen_fingerprint_not_recognized">Fingerprint not recognized. Try again</string>
<string name="lockscreen_fingerprint_success">Fingerprint recognized</string>
<string name="lockscreen_fingerprint_description">Confirm fingerprint to continue</string>
<string name="lockscreen_fingerprint_hint">Touch sensor</string>
<string name="lockscreen_description_fingerprint_icon">Fingerprint icon</string>
<string name="lockscreen_confirm_pin">Confirm PIN</string>
<string name="lockscreen_description_logo">Logo</string>
</resources>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LockScreenStyle">
<item name="android:background">@drawable/lockscreen_background</item>
</style>
<style name="LockScreenButtonStyle" parent="Theme.AppCompat.Light">
<!-- Customize your theme here. -->
<item name="android:textColor">@android:color/white</item>
<item name="android:background">@drawable/lockscreen_touch_selector</item>
</style>
<style name="LockScreenFingerPrintButtonStyle">
<item name="android:src">@drawable/lockscreen_fingerprint</item>
<item name="android:padding">20dp</item>
</style>
<style name="LockScreenDeleteButtonStyle">
<item name="android:src">@drawable/lockscreen_delete</item>
<item name="android:padding">20dp</item>
</style>
<style name="CheckBox">
<item name="android:checkboxStyle">@style/LockScreenCodeStyle</item>
<item name="checkboxStyle">@style/LockScreenCodeStyle</item>
</style>
<style name="LockScreenCodeStyle">
<item name="android:button">@drawable/lockscreen_code_selector</item>
</style>
<style name="LockScreenNextTextStyle">
<item name="android:textColor">#9FFF</item>
<item name="android:textSize">18sp</item>
<item name="android:backgroundTint">#c66</item>
</style>
<style name="LockScreenHintTextStyle">
<item name="android:textColor">@android:color/white</item>
</style>
<style name="LockScreenTitleTextStyle">
<item name="android:textColor">@android:color/white</item>
<item name="android:textSize">18sp</item>
<item name="android:gravity">center</item>
</style>
</resources>

View File

@ -4,10 +4,13 @@
<!-- BottomSheet theming --> <!-- BottomSheet theming -->
<style name="Theme.Vector.BottomSheetDialog.Light" parent="Theme.MaterialComponents.Light.BottomSheetDialog"> <style name="Theme.Vector.BottomSheetDialog.Light" parent="Theme.MaterialComponents.Light.BottomSheetDialog">
<item name="colorPrimary">@color/element_accent_light</item> <item name="colorPrimary">@color/element_accent_light</item>
<item name="colorOnPrimary">@color/palette_white</item>
<item name="colorSecondary">@color/palette_element_green</item> <item name="colorSecondary">@color/palette_element_green</item>
<item name="colorOnSecondary">@color/palette_white</item>
<item name="colorSurface">@color/element_background_light</item> <item name="colorSurface">@color/element_background_light</item>
<item name="colorOnSurface">@color/element_content_primary_light</item> <item name="colorOnSurface">@color/element_content_primary_light</item>
<item name="colorError">@color/element_alert_light</item> <item name="colorError">@color/element_alert_light</item>
<item name="colorOnError">@color/palette_white</item>
<!-- Default color for text View --> <!-- Default color for text View -->
<item name="android:textColorTertiary">@color/element_content_primary_light</item> <item name="android:textColorTertiary">@color/element_content_primary_light</item>
<item name="android:textColorLink">@color/element_link_light</item> <item name="android:textColorLink">@color/element_link_light</item>
@ -15,10 +18,13 @@
<style name="Theme.Vector.BottomSheetDialog.Dark" parent="Theme.MaterialComponents.BottomSheetDialog"> <style name="Theme.Vector.BottomSheetDialog.Dark" parent="Theme.MaterialComponents.BottomSheetDialog">
<item name="colorPrimary">@color/element_accent_dark</item> <item name="colorPrimary">@color/element_accent_dark</item>
<item name="colorOnPrimary">@color/palette_white</item>
<item name="colorSecondary">@color/palette_element_green</item> <item name="colorSecondary">@color/palette_element_green</item>
<item name="colorOnSecondary">@color/palette_white</item>
<item name="colorSurface">@color/element_background_dark</item> <item name="colorSurface">@color/element_background_dark</item>
<item name="colorOnSurface">@color/element_content_primary_dark</item> <item name="colorOnSurface">@color/element_content_primary_dark</item>
<item name="colorError">@color/element_alert_dark</item> <item name="colorError">@color/element_alert_dark</item>
<item name="colorOnError">@color/palette_white</item>
<!-- Default color for text View --> <!-- Default color for text View -->
<item name="android:textColorTertiary">@color/element_content_primary_dark</item> <item name="android:textColorTertiary">@color/element_content_primary_dark</item>
<item name="android:textColorLink">@color/element_link_dark</item> <item name="android:textColorLink">@color/element_link_dark</item>

View File

@ -22,13 +22,13 @@
</style> </style>
<style name="PinCodeDeleteButtonStyle"> <style name="PinCodeDeleteButtonStyle">
<item name="android:src">@drawable/delete_lockscreen_pf</item> <item name="android:src">@drawable/lockscreen_delete</item>
<item name="android:tint">?vctr_content_primary</item> <item name="android:tint">?vctr_content_primary</item>
<item name="background">@drawable/bg_pin_key</item> <item name="background">@drawable/bg_pin_key</item>
</style> </style>
<style name="PinCodeFingerprintButtonStyle"> <style name="PinCodeFingerprintButtonStyle">
<item name="android:src">@drawable/fingerprint_lockscreen_pf</item> <item name="android:src">@drawable/lockscreen_fingerprint</item>
<item name="android:tint">?vctr_content_primary</item> <item name="android:tint">?vctr_content_primary</item>
<item name="background">@drawable/bg_pin_key</item> <item name="background">@drawable/bg_pin_key</item>
</style> </style>

View File

@ -111,14 +111,14 @@
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item> <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
<item name="pf_lock_screen">@style/PinCodeScreenStyle</item> <item name="lockscreen_theme">@style/PinCodeScreenStyle</item>
<item name="pf_key_button">@style/PinCodeKeyButtonStyle</item> <item name="lockscreen_key_button_theme">@style/PinCodeKeyButtonStyle</item>
<item name="pf_title">@style/PinCodeTitleStyle</item> <item name="lockscreen_title_theme">@style/PinCodeTitleStyle</item>
<item name="pf_hint">@style/PinCodeHintStyle</item> <item name="lockscreen_hint_theme">@style/PinCodeHintStyle</item>
<item name="pf_code_view">@style/PinCodeDotsViewStyle</item> <item name="lockscreen_code_view_theme">@style/PinCodeDotsViewStyle</item>
<item name="pf_delete_button">@style/PinCodeDeleteButtonStyle</item> <item name="lockscreen_delete_button_theme">@style/PinCodeDeleteButtonStyle</item>
<item name="pf_fingerprint_button">@style/PinCodeFingerprintButtonStyle</item> <item name="lockscreen_fingerprint_button_theme">@style/PinCodeFingerprintButtonStyle</item>
<item name="pf_next">@style/PinCodeNextButtonStyle</item> <item name="lockscreen_next_theme">@style/PinCodeNextButtonStyle</item>
<item name="android:statusBarColor">@color/android_status_bar_background_dark</item> <item name="android:statusBarColor">@color/android_status_bar_background_dark</item>
<item name="android:navigationBarColor">@color/android_navigation_bar_background_dark</item> <item name="android:navigationBarColor">@color/android_navigation_bar_background_dark</item>

View File

@ -111,14 +111,14 @@
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item> <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
<item name="pf_lock_screen">@style/PinCodeScreenStyle</item> <item name="lockscreen_theme">@style/PinCodeScreenStyle</item>
<item name="pf_key_button">@style/PinCodeKeyButtonStyle</item> <item name="lockscreen_key_button_theme">@style/PinCodeKeyButtonStyle</item>
<item name="pf_title">@style/PinCodeTitleStyle</item> <item name="lockscreen_title_theme">@style/PinCodeTitleStyle</item>
<item name="pf_hint">@style/PinCodeHintStyle</item> <item name="lockscreen_hint_theme">@style/PinCodeHintStyle</item>
<item name="pf_code_view">@style/PinCodeDotsViewStyle</item> <item name="lockscreen_code_view_theme">@style/PinCodeDotsViewStyle</item>
<item name="pf_delete_button">@style/PinCodeDeleteButtonStyle</item> <item name="lockscreen_delete_button_theme">@style/PinCodeDeleteButtonStyle</item>
<item name="pf_fingerprint_button">@style/PinCodeFingerprintButtonStyle</item> <item name="lockscreen_fingerprint_button_theme">@style/PinCodeFingerprintButtonStyle</item>
<item name="pf_next">@style/PinCodeNextButtonStyle</item> <item name="lockscreen_next_theme">@style/PinCodeNextButtonStyle</item>
<!-- Use dark color, to have enough contrast with icons color. windowLightStatusBar is only available in API 23+ --> <!-- Use dark color, to have enough contrast with icons color. windowLightStatusBar is only available in API 23+ -->
<item name="android:statusBarColor">@color/android_status_bar_background_dark</item> <item name="android:statusBarColor">@color/android_status_bar_background_dark</item>

View File

@ -5,6 +5,10 @@ apply plugin: 'kotlin-parcelize'
apply plugin: 'realm-android' apply plugin: 'realm-android'
apply plugin: "org.jetbrains.dokka" apply plugin: "org.jetbrains.dokka"
if (project.hasProperty("coverage")) {
apply plugin: 'jacoco'
}
buildscript { buildscript {
repositories { repositories {
// Do not use `mavenCentral()`, it prevents Dependabot from working properly // Do not use `mavenCentral()`, it prevents Dependabot from working properly
@ -56,7 +60,7 @@ android {
// that the app's state is completely cleared between tests. // that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true' testInstrumentationRunnerArguments clearPackageData: 'true'
buildConfigField "String", "SDK_VERSION", "\"1.4.24\"" buildConfigField "String", "SDK_VERSION", "\"1.4.26\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
@ -74,7 +78,9 @@ android {
buildTypes { buildTypes {
debug { debug {
testCoverageEnabled true if (project.hasProperty("coverage")) {
testCoverageEnabled = coverage.enableTestCoverage
}
// Set to true to log privacy or sensible data, such as token // Set to true to log privacy or sensible data, such as token
buildConfigField "boolean", "LOG_PRIVATE_DATA", project.property("vector.debugPrivateData") buildConfigField "boolean", "LOG_PRIVATE_DATA", project.property("vector.debugPrivateData")
// Set to BODY instead of NONE to enable logging // Set to BODY instead of NONE to enable logging
@ -193,7 +199,7 @@ dependencies {
implementation libs.apache.commonsImaging implementation libs.apache.commonsImaging
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.50' implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.51'
testImplementation libs.tests.junit testImplementation libs.tests.junit
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C. * Copyright 2022 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package org.matrix.android.sdk.internal.session.securestorage package org.matrix.android.sdk
import org.matrix.android.sdk.internal.util.system.BuildVersionSdkIntProvider import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
class TestBuildVersionSdkIntProvider : BuildVersionSdkIntProvider { class TestBuildVersionSdkIntProvider : BuildVersionSdkIntProvider {
var value: Int = 0 var value: Int = 0

View File

@ -14,40 +14,57 @@
* limitations under the License. * limitations under the License.
*/ */
package org.matrix.android.sdk.internal.session.securestorage package org.matrix.android.sdk.api.securestorage
import android.os.Build import android.os.Build
import android.util.Base64
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.spyk
import org.amshove.kluent.invoking
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeInstanceOf
import org.amshove.kluent.shouldNotThrow
import org.amshove.kluent.shouldThrow
import org.junit.Before
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.TestBuildVersionSdkIntProvider
import org.matrix.android.sdk.api.util.fromBase64
import org.matrix.android.sdk.api.util.toBase64NoPadding
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.security.KeyStore
import java.security.KeyStoreException
import java.util.UUID import java.util.UUID
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
class SecretStoringUtilsTest : InstrumentedTest { class SecretStoringUtilsTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val buildVersionSdkIntProvider = TestBuildVersionSdkIntProvider() private val buildVersionSdkIntProvider = TestBuildVersionSdkIntProvider()
private val secretStoringUtils = SecretStoringUtils(context(), buildVersionSdkIntProvider) private val keyStore = spyk(KeyStore.getInstance("AndroidKeyStore")).also { it.load(null) }
private val secretStoringUtils = SecretStoringUtils(context, keyStore, buildVersionSdkIntProvider)
companion object { companion object {
const val TEST_STR = "This is something I want to store safely!" const val TEST_STR = "This is something I want to store safely!"
} }
@Before
fun setup() {
clearAllMocks()
}
@Test @Test
fun testStringNominalCaseApi21() { fun testStringNominalCaseApi21() {
val alias = generateAlias() val alias = generateAlias()
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
// Encrypt // Encrypt
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias) val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias)
// Decrypt // Decrypt
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias) val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias))
decrypted shouldBeEqualTo TEST_STR decrypted shouldBeEqualTo TEST_STR
secretStoringUtils.safeDeleteKey(alias) secretStoringUtils.safeDeleteKey(alias)
} }
@ -57,9 +74,9 @@ class SecretStoringUtilsTest : InstrumentedTest {
val alias = generateAlias() val alias = generateAlias()
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
// Encrypt // Encrypt
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias) val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias)
// Decrypt // Decrypt
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias) val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias))
decrypted shouldBeEqualTo TEST_STR decrypted shouldBeEqualTo TEST_STR
secretStoringUtils.safeDeleteKey(alias) secretStoringUtils.safeDeleteKey(alias)
} }
@ -69,9 +86,9 @@ class SecretStoringUtilsTest : InstrumentedTest {
val alias = generateAlias() val alias = generateAlias()
buildVersionSdkIntProvider.value = Build.VERSION_CODES.R buildVersionSdkIntProvider.value = Build.VERSION_CODES.R
// Encrypt // Encrypt
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias) val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias)
// Decrypt // Decrypt
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias) val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias))
decrypted shouldBeEqualTo TEST_STR decrypted shouldBeEqualTo TEST_STR
secretStoringUtils.safeDeleteKey(alias) secretStoringUtils.safeDeleteKey(alias)
} }
@ -81,13 +98,13 @@ class SecretStoringUtilsTest : InstrumentedTest {
val alias = generateAlias() val alias = generateAlias()
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
// Encrypt // Encrypt
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias) val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias)
// Simulate a system upgrade // Simulate a system upgrade
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
// Decrypt // Decrypt
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias) val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias))
decrypted shouldBeEqualTo TEST_STR decrypted shouldBeEqualTo TEST_STR
secretStoringUtils.safeDeleteKey(alias) secretStoringUtils.safeDeleteKey(alias)
} }
@ -180,5 +197,56 @@ class SecretStoringUtilsTest : InstrumentedTest {
secretStoringUtils.safeDeleteKey(alias) secretStoringUtils.safeDeleteKey(alias)
} }
@Test
fun testEnsureKeyReturnsSymmetricKeyOnAndroidM() {
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
val alias = generateAlias()
val key = secretStoringUtils.ensureKey(alias)
key shouldBeInstanceOf KeyStore.SecretKeyEntry::class
secretStoringUtils.safeDeleteKey(alias)
}
@Test
fun testEnsureKeyReturnsPrivateKeyOnAndroidL() {
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
val alias = generateAlias()
val key = secretStoringUtils.ensureKey(alias)
key shouldBeInstanceOf KeyStore.PrivateKeyEntry::class
secretStoringUtils.safeDeleteKey(alias)
}
@Test
fun testSafeDeleteCanHandleKeyStoreExceptions() {
every { keyStore.deleteEntry(any()) } throws KeyStoreException()
invoking { secretStoringUtils.safeDeleteKey(generateAlias()) } shouldNotThrow KeyStoreException::class
}
@Test
fun testLoadSecureSecretBytesWillThrowOnInvalidStreamFormat() {
invoking {
secretStoringUtils.loadSecureSecretBytes(byteArrayOf(255.toByte()), generateAlias())
} shouldThrow IllegalArgumentException::class
}
@Test
fun testLoadSecureSecretWillThrowOnInvalidStreamFormat() {
invoking {
secretStoringUtils.loadSecureSecret(byteArrayOf(255.toByte()).inputStream(), generateAlias())
} shouldThrow IllegalArgumentException::class
}
private fun generateAlias() = UUID.randomUUID().toString() private fun generateAlias() = UUID.randomUUID().toString()
} }
private fun ByteArray.toBase64NoPadding(): String {
return Base64.encodeToString(this, Base64.NO_PADDING or Base64.NO_WRAP)
}
private fun String.fromBase64(): ByteArray {
return Base64.decode(this, Base64.DEFAULT)
}

View File

@ -20,6 +20,7 @@ import android.content.Context
import dagger.BindsInstance import dagger.BindsInstance
import dagger.Component import dagger.Component
import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.securestorage.SecureStorageModule
import org.matrix.android.sdk.internal.auth.AuthModule import org.matrix.android.sdk.internal.auth.AuthModule
import org.matrix.android.sdk.internal.debug.DebugModule import org.matrix.android.sdk.internal.debug.DebugModule
import org.matrix.android.sdk.internal.di.MatrixComponent import org.matrix.android.sdk.internal.di.MatrixComponent
@ -39,7 +40,8 @@ import org.matrix.android.sdk.internal.util.system.SystemModule
RawModule::class, RawModule::class,
DebugModule::class, DebugModule::class,
SettingsModule::class, SettingsModule::class,
SystemModule::class SystemModule::class,
SecureStorageModule::class,
] ]
) )
@MatrixScope @MatrixScope
@ -51,7 +53,7 @@ internal interface TestMatrixComponent : MatrixComponent {
interface Factory { interface Factory {
fun create( fun create(
@BindsInstance context: Context, @BindsInstance context: Context,
@BindsInstance matrixConfiguration: MatrixConfiguration @BindsInstance matrixConfiguration: MatrixConfiguration,
): TestMatrixComponent ): TestMatrixComponent
} }
} }

View File

@ -22,7 +22,6 @@ import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import io.realm.kotlin.createObject import io.realm.kotlin.createObject
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -30,13 +29,11 @@ import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
import org.matrix.android.sdk.internal.database.helper.merge
import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.mapper.toEntity
import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.SessionRealmModule import org.matrix.android.sdk.internal.database.model.SessionRealmModule
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
import org.matrix.android.sdk.internal.util.time.DefaultClock import org.matrix.android.sdk.internal.util.time.DefaultClock
import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeListOfEvents
import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeMessageEvent import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeMessageEvent
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@ -97,63 +94,6 @@ internal class ChunkEntityTest : InstrumentedTest {
} }
} }
@Test
fun merge_shouldAddEvents_whenMergingBackward() {
monarchy.runTransactionSync { realm ->
val chunk1: ChunkEntity = realm.createObject()
val chunk2: ChunkEntity = realm.createObject()
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
chunk1.timelineEvents.size shouldBeEqualTo 60
}
}
@Test
fun merge_shouldAddOnlyDifferentEvents_whenMergingBackward() {
monarchy.runTransactionSync { realm ->
val chunk1: ChunkEntity = realm.createObject()
val chunk2: ChunkEntity = realm.createObject()
val eventsForChunk1 = createFakeListOfEvents(30)
val eventsForChunk2 = eventsForChunk1 + createFakeListOfEvents(10)
chunk1.isLastForward = true
chunk2.isLastForward = false
chunk1.addAll(ROOM_ID, eventsForChunk1, PaginationDirection.FORWARDS)
chunk2.addAll(ROOM_ID, eventsForChunk2, PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
chunk1.timelineEvents.size shouldBeEqualTo 40
chunk1.isLastForward.shouldBeTrue()
}
}
@Test
fun merge_shouldPrevTokenMerged_whenMergingForwards() {
monarchy.runTransactionSync { realm ->
val chunk1: ChunkEntity = realm.createObject()
val chunk2: ChunkEntity = realm.createObject()
val prevToken = "prev_token"
chunk1.prevToken = prevToken
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.FORWARDS)
chunk1.prevToken shouldBeEqualTo prevToken
}
}
@Test
fun merge_shouldNextTokenMerged_whenMergingBackwards() {
monarchy.runTransactionSync { realm ->
val chunk1: ChunkEntity = realm.createObject()
val chunk2: ChunkEntity = realm.createObject()
val nextToken = "next_token"
chunk1.nextToken = nextToken
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
chunk1.nextToken shouldBeEqualTo nextToken
}
}
private fun ChunkEntity.addAll( private fun ChunkEntity.addAll(
roomId: String, roomId: String,
events: List<Event>, events: List<Event>,

View File

@ -163,6 +163,8 @@ class TimelineForwardPaginationTest : InstrumentedTest {
// Ask for a forward pagination // Ask for a forward pagination
val snapshot = runBlocking { val snapshot = runBlocking {
aliceTimeline.awaitPaginate(Timeline.Direction.FORWARDS, 50) aliceTimeline.awaitPaginate(Timeline.Direction.FORWARDS, 50)
// We should paginate one more time to check we are at the end now that chunks are not merged.
aliceTimeline.awaitPaginate(Timeline.Direction.FORWARDS, 50)
} }
// 7 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room) // 7 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room)
snapshot.size == 7 + numberOfMessagesToSend && snapshot.size == 7 + numberOfMessagesToSend &&

View File

@ -20,6 +20,7 @@ import androidx.test.filters.LargeTest
import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeFalse
import org.amshove.kluent.shouldBeTrue import org.amshove.kluent.shouldBeTrue
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.JUnit4 import org.junit.runners.JUnit4
@ -39,6 +40,7 @@ import java.util.concurrent.CountDownLatch
@RunWith(JUnit4::class) @RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
@Ignore("This test will be ignored until it is fixed")
@LargeTest @LargeTest
class TimelinePreviousLastForwardTest : InstrumentedTest { class TimelinePreviousLastForwardTest : InstrumentedTest {
@ -229,6 +231,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
bobTimeline.addListener(eventsListener) bobTimeline.addListener(eventsListener)
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50) bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
commonTestHelper.await(lock) commonTestHelper.await(lock)

View File

@ -17,6 +17,8 @@
package org.matrix.android.sdk.api package org.matrix.android.sdk.api
import android.content.Context import android.content.Context
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.Configuration import androidx.work.Configuration
import androidx.work.WorkManager import androidx.work.WorkManager
@ -30,6 +32,7 @@ import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import org.matrix.android.sdk.api.network.ApiInterceptorListener import org.matrix.android.sdk.api.network.ApiInterceptorListener
import org.matrix.android.sdk.api.network.ApiPath import org.matrix.android.sdk.api.network.ApiPath
import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.securestorage.SecureStorageService
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.di.DaggerMatrixComponent import org.matrix.android.sdk.internal.di.DaggerMatrixComponent
@ -64,6 +67,9 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) {
@Inject internal lateinit var apiInterceptor: ApiInterceptor @Inject internal lateinit var apiInterceptor: ApiInterceptor
@Inject internal lateinit var matrixWorkerFactory: MatrixWorkerFactory @Inject internal lateinit var matrixWorkerFactory: MatrixWorkerFactory
@Inject internal lateinit var lightweightSettingsStorage: LightweightSettingsStorage @Inject internal lateinit var lightweightSettingsStorage: LightweightSettingsStorage
@Inject internal lateinit var secureStorageService: SecureStorageService
private val uiHandler = Handler(Looper.getMainLooper())
init { init {
val appContext = context.applicationContext val appContext = context.applicationContext
@ -76,8 +82,10 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) {
.build() .build()
WorkManager.initialize(appContext, configuration) WorkManager.initialize(appContext, configuration)
} }
uiHandler.post {
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver) ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
} }
}
/** /**
* Return the User Agent used for any request that the SDK is making to the homeserver. * Return the User Agent used for any request that the SDK is making to the homeserver.
@ -115,6 +123,11 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) {
*/ */
fun legacySessionImporter() = legacySessionImporter fun legacySessionImporter() = legacySessionImporter
/**
* Returns the SecureStorageService used to encrypt and decrypt sensitive data.
*/
fun secureStorageService(): SecureStorageService = secureStorageService
/** /**
* Get the worker factory. The returned value has to be provided to `WorkConfiguration.Builder()`. * Get the worker factory. The returned value has to be provided to `WorkConfiguration.Builder()`.
*/ */

View File

@ -21,5 +21,6 @@ data class LoginFlowResult(
val ssoIdentityProviders: List<SsoIdentityProvider>?, val ssoIdentityProviders: List<SsoIdentityProvider>?,
val isLoginAndRegistrationSupported: Boolean, val isLoginAndRegistrationSupported: Boolean,
val homeServerUrl: String, val homeServerUrl: String,
val isOutdatedHomeserver: Boolean val isOutdatedHomeserver: Boolean,
val isLogoutDevicesSupported: Boolean
) )

View File

@ -72,7 +72,9 @@ interface LoginWizard {
* Confirm the new password, once the user has checked their email * Confirm the new password, once the user has checked their email
* When this method succeed, tha account password will be effectively modified. * When this method succeed, tha account password will be effectively modified.
* *
* @param newPassword the desired new password * @param newPassword the desired new password.
* @param logoutAllDevices defaults to true, all devices will be logged out. False values will only be taken into account
* if [org.matrix.android.sdk.api.auth.data.LoginFlowResult.isLogoutDevicesSupported] is true.
*/ */
suspend fun resetPasswordMailConfirmed(newPassword: String) suspend fun resetPasswordMailConfirmed(newPassword: String, logoutAllDevices: Boolean = true)
} }

View File

@ -16,7 +16,7 @@
@file:Suppress("DEPRECATION") @file:Suppress("DEPRECATION")
package org.matrix.android.sdk.internal.session.securestorage package org.matrix.android.sdk.api.securestorage
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
@ -25,7 +25,7 @@ import android.security.KeyPairGeneratorSpec
import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties import android.security.keystore.KeyProperties
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import org.matrix.android.sdk.internal.util.system.BuildVersionSdkIntProvider import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
import timber.log.Timber import timber.log.Timber
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@ -80,9 +80,11 @@ import javax.security.auth.x500.X500Principal
* Important: Keys stored in the keystore can be wiped out (depends of the OS version, like for example if you * Important: Keys stored in the keystore can be wiped out (depends of the OS version, like for example if you
* add a pin or change the schema); So you might and with a useless pile of bytes. * add a pin or change the schema); So you might and with a useless pile of bytes.
*/ */
internal class SecretStoringUtils @Inject constructor( class SecretStoringUtils @Inject constructor(
private val context: Context, private val context: Context,
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider private val keyStore: KeyStore,
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
private val keyNeedsUserAuthentication: Boolean = false,
) { ) {
companion object { companion object {
@ -94,14 +96,24 @@ internal class SecretStoringUtils @Inject constructor(
private const val FORMAT_1: Byte = 1 private const val FORMAT_1: Byte = 1
} }
private val keyStore: KeyStore by lazy {
KeyStore.getInstance(ANDROID_KEY_STORE).apply {
load(null)
}
}
private val secureRandom = SecureRandom() private val secureRandom = SecureRandom()
/**
* Allows creation of the crypto keys associated witht he [alias] before encrypting some value with it.
* @return A [KeyStore.Entry] with the keys.
*/
@SuppressLint("NewApi")
fun ensureKey(alias: String): KeyStore.Entry {
when {
buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> getOrGenerateSymmetricKeyForAliasM(alias)
else -> getOrGenerateKeyPairForAlias(alias).privateKey
}
return keyStore.getEntry(alias, null)
}
/**
* Deletes the key associated with the [keyAlias] and logs any [KeyStoreException] that could happen.
*/
fun safeDeleteKey(keyAlias: String) { fun safeDeleteKey(keyAlias: String) {
try { try {
keyStore.deleteEntry(keyAlias) keyStore.deleteEntry(keyAlias)
@ -121,24 +133,24 @@ internal class SecretStoringUtils @Inject constructor(
*/ */
@SuppressLint("NewApi") @SuppressLint("NewApi")
@Throws(Exception::class) @Throws(Exception::class)
fun securelyStoreString(secret: String, keyAlias: String): ByteArray { fun securelyStoreBytes(secret: ByteArray, keyAlias: String): ByteArray {
return when { return when {
buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> encryptStringM(secret, keyAlias) buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> encryptBytesM(secret, keyAlias)
else -> encryptString(secret, keyAlias) else -> encryptBytes(secret, keyAlias)
} }
} }
/** /**
* Decrypt a secret that was encrypted by #securelyStoreString(). * Decrypt a secret that was encrypted by [securelyStoreBytes].
*/ */
@SuppressLint("NewApi") @SuppressLint("NewApi")
@Throws(Exception::class) @Throws(Exception::class)
fun loadSecureSecret(encrypted: ByteArray, keyAlias: String): String { fun loadSecureSecretBytes(encrypted: ByteArray, keyAlias: String): ByteArray {
encrypted.inputStream().use { inputStream -> encrypted.inputStream().use { inputStream ->
// First get the format // First get the format
return when (val format = inputStream.read().toByte()) { return when (val format = inputStream.read().toByte()) {
FORMAT_API_M -> decryptStringM(inputStream, keyAlias) FORMAT_API_M -> decryptBytesM(inputStream, keyAlias)
FORMAT_1 -> decryptString(inputStream, keyAlias) FORMAT_1 -> decryptBytes(inputStream, keyAlias)
else -> throw IllegalArgumentException("Unknown format $format") else -> throw IllegalArgumentException("Unknown format $format")
} }
} }
@ -162,6 +174,22 @@ internal class SecretStoringUtils @Inject constructor(
} }
} }
fun getEncryptCipher(alias: String): Cipher {
val key = when (val keyEntry = ensureKey(alias)) {
is KeyStore.SecretKeyEntry -> keyEntry.secretKey
is KeyStore.PrivateKeyEntry -> keyEntry.certificate.publicKey
else -> throw IllegalStateException("Unknown KeyEntry type.")
}
val cipherMode = when {
buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> AES_MODE
else -> RSA_MODE
}
val cipher = Cipher.getInstance(cipherMode)
cipher.init(Cipher.ENCRYPT_MODE, key)
return cipher
}
@SuppressLint("NewApi")
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
private fun getOrGenerateSymmetricKeyForAliasM(alias: String): SecretKey { private fun getOrGenerateSymmetricKeyForAliasM(alias: String): SecretKey {
val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry) val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry)
@ -176,6 +204,13 @@ internal class SecretStoringUtils @Inject constructor(
.setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(128) .setKeySize(128)
.apply {
setUserAuthenticationRequired(keyNeedsUserAuthentication)
if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.N) {
setInvalidatedByBiometricEnrollment(true)
}
}
.setUserAuthenticationRequired(keyNeedsUserAuthentication)
.build() .build()
generator.init(keyGenSpec) generator.init(keyGenSpec)
return generator.generateKey() return generator.generateKey()
@ -216,19 +251,16 @@ internal class SecretStoringUtils @Inject constructor(
} }
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
private fun encryptStringM(text: String, keyAlias: String): ByteArray { private fun encryptBytesM(byteArray: ByteArray, keyAlias: String): ByteArray {
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias) val cipher = getEncryptCipher(keyAlias)
val cipher = Cipher.getInstance(AES_MODE)
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val iv = cipher.iv val iv = cipher.iv
// we happen the iv to the final result // we happen the iv to the final result
val encryptedBytes: ByteArray = cipher.doFinal(text.toByteArray(Charsets.UTF_8)) val encryptedBytes: ByteArray = cipher.doFinal(byteArray)
return formatMMake(iv, encryptedBytes) return formatMMake(iv, encryptedBytes)
} }
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
private fun decryptStringM(inputStream: InputStream, keyAlias: String): String { private fun decryptBytesM(inputStream: InputStream, keyAlias: String): ByteArray {
val (iv, encryptedText) = formatMExtract(inputStream) val (iv, encryptedText) = formatMExtract(inputStream)
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias) val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
@ -237,10 +269,10 @@ internal class SecretStoringUtils @Inject constructor(
val spec = GCMParameterSpec(128, iv) val spec = GCMParameterSpec(128, iv)
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec) cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
return String(cipher.doFinal(encryptedText), Charsets.UTF_8) return cipher.doFinal(encryptedText)
} }
private fun encryptString(text: String, keyAlias: String): ByteArray { private fun encryptBytes(byteArray: ByteArray, keyAlias: String): ByteArray {
// we generate a random symmetric key // we generate a random symmetric key
val key = ByteArray(16) val key = ByteArray(16)
secureRandom.nextBytes(key) secureRandom.nextBytes(key)
@ -252,12 +284,12 @@ internal class SecretStoringUtils @Inject constructor(
val cipher = Cipher.getInstance(AES_MODE) val cipher = Cipher.getInstance(AES_MODE)
cipher.init(Cipher.ENCRYPT_MODE, sKey) cipher.init(Cipher.ENCRYPT_MODE, sKey)
val iv = cipher.iv val iv = cipher.iv
val encryptedBytes: ByteArray = cipher.doFinal(text.toByteArray(Charsets.UTF_8)) val encryptedBytes: ByteArray = cipher.doFinal(byteArray)
return format1Make(encryptedKey, iv, encryptedBytes) return format1Make(encryptedKey, iv, encryptedBytes)
} }
private fun decryptString(inputStream: InputStream, keyAlias: String): String { private fun decryptBytes(inputStream: InputStream, keyAlias: String): ByteArray {
val (encryptedKey, iv, encrypted) = format1Extract(inputStream) val (encryptedKey, iv, encrypted) = format1Extract(inputStream)
// we need to decrypt the key // we need to decrypt the key
@ -266,16 +298,13 @@ internal class SecretStoringUtils @Inject constructor(
val spec = GCMParameterSpec(128, iv) val spec = GCMParameterSpec(128, iv)
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sKeyBytes, "AES"), spec) cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sKeyBytes, "AES"), spec)
return String(cipher.doFinal(encrypted), Charsets.UTF_8) return cipher.doFinal(encrypted)
} }
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
@Throws(IOException::class) @Throws(IOException::class)
private fun saveSecureObjectM(keyAlias: String, output: OutputStream, writeObject: Any) { private fun saveSecureObjectM(keyAlias: String, output: OutputStream, writeObject: Any) {
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias) val cipher = getEncryptCipher(keyAlias)
val cipher = Cipher.getInstance(AES_MODE)
cipher.init(Cipher.ENCRYPT_MODE, secretKey/*, spec*/)
val iv = cipher.iv val iv = cipher.iv
val bos1 = ByteArrayOutputStream() val bos1 = ByteArrayOutputStream()
@ -362,10 +391,8 @@ internal class SecretStoringUtils @Inject constructor(
@Throws(Exception::class) @Throws(Exception::class)
private fun rsaEncrypt(alias: String, secret: ByteArray): ByteArray { private fun rsaEncrypt(alias: String, secret: ByteArray): ByteArray {
val privateKeyEntry = getOrGenerateKeyPairForAlias(alias)
// Encrypt the text // Encrypt the text
val inputCipher = Cipher.getInstance(RSA_MODE) val inputCipher = getEncryptCipher(alias)
inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.certificate.publicKey)
val outputStream = ByteArrayOutputStream() val outputStream = ByteArrayOutputStream()
CipherOutputStream(outputStream, inputCipher).use { CipherOutputStream(outputStream, inputCipher).use {

View File

@ -0,0 +1,45 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.securestorage
import android.content.Context
import dagger.Binds
import dagger.Module
import dagger.Provides
import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
import org.matrix.android.sdk.api.util.DefaultBuildVersionSdkIntProvider
import java.security.KeyStore
@Module
internal abstract class SecureStorageModule {
@Module
companion object {
@Provides
fun provideKeyStore(): KeyStore = KeyStore.getInstance("AndroidKeyStore").also { it.load(null) }
@Provides
fun provideSecretStoringUtils(
context: Context,
keyStore: KeyStore,
buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
): SecretStoringUtils = SecretStoringUtils(context, keyStore, buildVersionSdkIntProvider)
}
@Binds
abstract fun bindBuildVersionSdkIntProvider(provider: DefaultBuildVersionSdkIntProvider): BuildVersionSdkIntProvider
}

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.matrix.android.sdk.api.session.securestorage package org.matrix.android.sdk.api.securestorage
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream

View File

@ -47,7 +47,6 @@ import org.matrix.android.sdk.api.session.pushrules.PushRuleService
import org.matrix.android.sdk.api.session.room.RoomDirectoryService import org.matrix.android.sdk.api.session.room.RoomDirectoryService
import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.api.session.room.RoomService
import org.matrix.android.sdk.api.session.search.SearchService import org.matrix.android.sdk.api.session.search.SearchService
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
import org.matrix.android.sdk.api.session.signout.SignOutService import org.matrix.android.sdk.api.session.signout.SignOutService
import org.matrix.android.sdk.api.session.space.SpaceService import org.matrix.android.sdk.api.session.space.SpaceService
@ -200,11 +199,6 @@ interface Session {
*/ */
fun syncService(): SyncService fun syncService(): SyncService
/**
* Returns the SecureStorageService associated with the session.
*/
fun secureStorageService(): SecureStorageService
/** /**
* Returns the ProfileService associated with the session. * Returns the ProfileService associated with the session.
*/ */

View File

@ -24,13 +24,13 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
interface AccountService { interface AccountService {
/** /**
* Ask the homeserver to change the password. * Ask the homeserver to change the password.
*
* @param password Current password. * @param password Current password.
* @param newPassword New password * @param newPassword New password
* @param logoutAllDevices defaults to true, all devices will be logged out. False values will only be taken into account
* if [org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities.canControlLogoutDevices] is true.
*/ */
suspend fun changePassword( suspend fun changePassword(password: String, newPassword: String, logoutAllDevices: Boolean = true)
password: String,
newPassword: String
)
/** /**
* Deactivate the account. * Deactivate the account.

View File

@ -54,7 +54,12 @@ data class HomeServerCapabilities(
/** /**
* True if the home server support threading. * True if the home server support threading.
*/ */
val canUseThreading: Boolean = false val canUseThreading: Boolean = false,
/**
* True if the home server supports controlling the logout of all devices when changing password.
*/
val canControlLogoutDevices: Boolean = false
) { ) {
enum class RoomCapabilitySupport { enum class RoomCapabilitySupport {

View File

@ -16,12 +16,58 @@
package org.matrix.android.sdk.api.session.room.location package org.matrix.android.sdk.api.session.room.location
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.Optional
/** /**
* Manage all location sharing related features. * Manage all location sharing related features.
*/ */
interface LocationSharingService { interface LocationSharingService {
/**
* Send a static location event to the room.
* @param latitude required latitude of the location
* @param longitude required longitude of the location
* @param uncertainty Accuracy of the location in meters
* @param isUserLocation indicates whether the location data corresponds to the user location or not (pinned location)
*/
suspend fun sendStaticLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable
/**
* Send a live location event to the room.
* To get the beacon info event id, [startLiveLocationShare] must be called before sending live location updates.
* @param beaconInfoEventId event id of the initial beacon info state event
* @param latitude required latitude of the location
* @param longitude required longitude of the location
* @param uncertainty Accuracy of the location in meters
*/
suspend fun sendLiveLocation(beaconInfoEventId: String, latitude: Double, longitude: Double, uncertainty: Double?): Cancelable
/**
* Starts sharing live location in the room.
* @param timeoutMillis timeout of the live in milliseconds
* @return the result of the update of the live
*/
suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult
/**
* Stops sharing live location in the room.
* @return the result of the update of the live
*/
suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult
/**
* Returns a LiveData on the list of current running live location shares.
*/
@MainThread
fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>> fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>>
/**
* Returns a LiveData on the live location share summary with the given eventId.
* @param beaconInfoEventId event id of the initial beacon info state event
*/
@MainThread
fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData<Optional<LiveLocationShareAggregatedSummary>>
} }

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.room.location
/**
* Represents the result of an update of live location share like a start or a stop.
*/
sealed interface UpdateLiveLocationShareResult {
data class Success(val beaconEventId: String) : UpdateLiveLocationShareResult
data class Failure(val error: Throwable) : UpdateLiveLocationShareResult
}

View File

@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class LocationInfo( data class LocationInfo(
/** /**
* Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;u=uncertainty' like 'geo:40.05,29.24;u=30' representing this location.
*/ */
@Json(name = "uri") val geoUri: String? = null, @Json(name = "uri") val geoUri: String? = null,

View File

@ -35,7 +35,7 @@ data class MessageLocationContent(
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
/** /**
* Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;u=uncertainty' like 'geo:40.05,29.24;u=30' representing this location.
*/ */
@Json(name = "geo_uri") val geoUri: String, @Json(name = "geo_uri") val geoUri: String,

View File

@ -25,4 +25,7 @@ data class PollCreationInfo(
@Json(name = "kind") val kind: PollType? = PollType.DISCLOSED_UNSTABLE, @Json(name = "kind") val kind: PollType? = PollType.DISCLOSED_UNSTABLE,
@Json(name = "max_selections") val maxSelections: Int = 1, @Json(name = "max_selections") val maxSelections: Int = 1,
@Json(name = "answers") val answers: List<PollAnswer>? = null @Json(name = "answers") val answers: List<PollAnswer>? = null
) ) {
fun isUndisclosed() = kind in listOf(PollType.UNDISCLOSED_UNSTABLE, PollType.UNDISCLOSED)
}

View File

@ -142,24 +142,6 @@ interface SendService {
*/ */
fun resendMediaMessage(localEcho: TimelineEvent): Cancelable fun resendMediaMessage(localEcho: TimelineEvent): Cancelable
/**
* Send a location event to the room.
* @param latitude required latitude of the location
* @param longitude required longitude of the location
* @param uncertainty Accuracy of the location in meters
* @param isUserLocation indicates whether the location data corresponds to the user location or not
*/
fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable
/**
* Send a live location event to the room. beacon_info state event has to be sent before sending live location updates.
* @param beaconInfoEventId event id of the initial beacon info state event
* @param latitude required latitude of the location
* @param longitude required longitude of the location
* @param uncertainty Accuracy of the location in meters
*/
fun sendLiveLocation(beaconInfoEventId: String, latitude: Double, longitude: Double, uncertainty: Double?): Cancelable
/** /**
* Remove this failed message from the timeline. * Remove this failed message from the timeline.
* @param localEcho the unsent local echo * @param localEcho the unsent local echo

View File

@ -66,19 +66,6 @@ interface StateService {
*/ */
suspend fun deleteAvatar() suspend fun deleteAvatar()
/**
* Stops sharing live location in the room.
* @param userId user id
*/
suspend fun stopLiveLocation(userId: String)
/**
* Returns beacon info state event of a user.
* @param userId user id who is sharing location
* @param filterOnlyLive filters only ongoing live location sharing beacons if true else ended event is included
*/
suspend fun getLiveLocationBeaconInfo(userId: String, filterOnlyLive: Boolean): Event?
/** /**
* Send a state event to the room. * Send a state event to the room.
* @param eventType The type of event to send. * @param eventType The type of event to send.

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package org.matrix.android.sdk.internal.util.system package org.matrix.android.sdk.api.util
internal interface BuildVersionSdkIntProvider { interface BuildVersionSdkIntProvider {
/** /**
* Return the current version of the Android SDK. * Return the current version of the Android SDK.
*/ */

View File

@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package org.matrix.android.sdk.internal.util.system package org.matrix.android.sdk.api.util
import android.os.Build import android.os.Build
import javax.inject.Inject import javax.inject.Inject
internal class DefaultBuildVersionSdkIntProvider @Inject constructor() : class DefaultBuildVersionSdkIntProvider @Inject constructor() :
BuildVersionSdkIntProvider { BuildVersionSdkIntProvider {
override fun get() = Build.VERSION.SDK_INT override fun get() = Build.VERSION.SDK_INT
} }

View File

@ -40,6 +40,7 @@ import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard
import org.matrix.android.sdk.internal.auth.login.DirectLoginTask import org.matrix.android.sdk.internal.auth.login.DirectLoginTask
import org.matrix.android.sdk.internal.auth.registration.DefaultRegistrationWizard import org.matrix.android.sdk.internal.auth.registration.DefaultRegistrationWizard
import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.auth.version.Versions
import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices
import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk
import org.matrix.android.sdk.internal.auth.version.isSupportedBySdk import org.matrix.android.sdk.internal.auth.version.isSupportedBySdk
import org.matrix.android.sdk.internal.di.Unauthenticated import org.matrix.android.sdk.internal.di.Unauthenticated
@ -292,7 +293,8 @@ internal class DefaultAuthenticationService @Inject constructor(
ssoIdentityProviders = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider, ssoIdentityProviders = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(), isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(),
homeServerUrl = homeServerUrl, homeServerUrl = homeServerUrl,
isOutdatedHomeserver = !versions.isSupportedBySdk() isOutdatedHomeserver = !versions.isSupportedBySdk(),
isLogoutDevicesSupported = versions.doesServerSupportLogoutDevices()
) )
} }

View File

@ -121,12 +121,13 @@ internal class DefaultLoginWizard(
.also { pendingSessionStore.savePendingSessionData(it) } .also { pendingSessionStore.savePendingSessionData(it) }
} }
override suspend fun resetPasswordMailConfirmed(newPassword: String) { override suspend fun resetPasswordMailConfirmed(newPassword: String, logoutAllDevices: Boolean) {
val resetPasswordData = pendingSessionData.resetPasswordData ?: throw IllegalStateException("Developer error - Must call resetPassword first") val resetPasswordData = pendingSessionData.resetPasswordData ?: throw IllegalStateException("Developer error - Must call resetPassword first")
val param = ResetPasswordMailConfirmed.create( val param = ResetPasswordMailConfirmed.create(
pendingSessionData.clientSecret, pendingSessionData.clientSecret,
resetPasswordData.addThreePidRegistrationResponse.sid, resetPasswordData.addThreePidRegistrationResponse.sid,
newPassword newPassword,
logoutAllDevices
) )
executeRequest(null) { executeRequest(null) {

View File

@ -30,13 +30,17 @@ internal data class ResetPasswordMailConfirmed(
// the new password // the new password
@Json(name = "new_password") @Json(name = "new_password")
val newPassword: String? = null val newPassword: String? = null,
@Json(name = "logout_devices")
val logoutDevices: Boolean? = null
) { ) {
companion object { companion object {
fun create(clientSecret: String, sid: String, newPassword: String): ResetPasswordMailConfirmed { fun create(clientSecret: String, sid: String, newPassword: String, logoutDevices: Boolean?): ResetPasswordMailConfirmed {
return ResetPasswordMailConfirmed( return ResetPasswordMailConfirmed(
auth = AuthParams.createForResetPassword(clientSecret, sid), auth = AuthParams.createForResetPassword(clientSecret, sid),
newPassword = newPassword newPassword = newPassword,
logoutDevices = logoutDevices
) )
} }
} }

View File

@ -58,6 +58,7 @@ internal data class HomeServerVersion(
val r0_4_0 = HomeServerVersion(major = 0, minor = 4, patch = 0) val r0_4_0 = HomeServerVersion(major = 0, minor = 4, patch = 0)
val r0_5_0 = HomeServerVersion(major = 0, minor = 5, patch = 0) val r0_5_0 = HomeServerVersion(major = 0, minor = 5, patch = 0)
val r0_6_0 = HomeServerVersion(major = 0, minor = 6, patch = 0) val r0_6_0 = HomeServerVersion(major = 0, minor = 6, patch = 0)
val r0_6_1 = HomeServerVersion(major = 0, minor = 6, patch = 1)
val v1_3_0 = HomeServerVersion(major = 1, minor = 3, patch = 0) val v1_3_0 = HomeServerVersion(major = 1, minor = 3, patch = 0)
} }
} }

View File

@ -111,6 +111,15 @@ private fun Versions.doesServerSeparatesAddAndBind(): Boolean {
unstableFeatures?.get(FEATURE_SEPARATE_ADD_AND_BIND) ?: false unstableFeatures?.get(FEATURE_SEPARATE_ADD_AND_BIND) ?: false
} }
/**
* Indicate if the server supports MSC2457 `logout_devices` parameter when setting a new password.
*
* @return true if logout_devices is supported
*/
internal fun Versions.doesServerSupportLogoutDevices(): Boolean {
return getMaxVersion() >= HomeServerVersion.r0_6_1
}
private fun Versions.getMaxVersion(): HomeServerVersion { private fun Versions.getMaxVersion(): HomeServerVersion {
return supportedVersions return supportedVersions
?.mapNotNull { HomeServerVersion.parse(it) } ?.mapNotNull { HomeServerVersion.parse(it) }

View File

@ -62,7 +62,7 @@ internal class VerificationMessageProcessor @Inject constructor(
// If the request is in the future by more than 5 minutes or more than 10 minutes in the past, // If the request is in the future by more than 5 minutes or more than 10 minutes in the past,
// the message should be ignored by the receiver. // the message should be ignored by the receiver.
if (event.ageLocalTs != null && !VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also { if (!VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also {
Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated age:$event.ageLocalTs ms") Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated age:$event.ageLocalTs ms")
} }

View File

@ -21,7 +21,7 @@ import androidx.core.content.edit
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.internal.session.securestorage.SecretStoringUtils import org.matrix.android.sdk.api.securestorage.SecretStoringUtils
import timber.log.Timber import timber.log.Timber
import java.security.SecureRandom import java.security.SecureRandom
import javax.inject.Inject import javax.inject.Inject
@ -40,7 +40,7 @@ import javax.inject.Inject
*/ */
internal class RealmKeysUtils @Inject constructor( internal class RealmKeysUtils @Inject constructor(
context: Context, context: Context,
private val secretStoringUtils: SecretStoringUtils private val secretStoringUtils: SecretStoringUtils,
) { ) {
private val rng = SecureRandom() private val rng = SecureRandom()
@ -71,7 +71,7 @@ internal class RealmKeysUtils @Inject constructor(
private fun createAndSaveKeyForDatabase(alias: String): ByteArray { private fun createAndSaveKeyForDatabase(alias: String): ByteArray {
val key = generateKeyForRealm() val key = generateKeyForRealm()
val encodedKey = Base64.encodeToString(key, Base64.NO_PADDING) val encodedKey = Base64.encodeToString(key, Base64.NO_PADDING)
val toStore = secretStoringUtils.securelyStoreString(encodedKey, alias) val toStore = secretStoringUtils.securelyStoreBytes(encodedKey.toByteArray(), alias)
sharedPreferences.edit { sharedPreferences.edit {
putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore, Base64.NO_PADDING)) putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore, Base64.NO_PADDING))
} }
@ -85,7 +85,7 @@ internal class RealmKeysUtils @Inject constructor(
private fun extractKeyForDatabase(alias: String): ByteArray { private fun extractKeyForDatabase(alias: String): ByteArray {
val encryptedB64 = sharedPreferences.getString("${ENCRYPTED_KEY_PREFIX}_$alias", null) val encryptedB64 = sharedPreferences.getString("${ENCRYPTED_KEY_PREFIX}_$alias", null)
val encryptedKey = Base64.decode(encryptedB64, Base64.NO_PADDING) val encryptedKey = Base64.decode(encryptedB64, Base64.NO_PADDING)
val b64 = secretStoringUtils.loadSecureSecret(encryptedKey, alias) val b64 = secretStoringUtils.loadSecureSecretBytes(encryptedKey, alias)
return Base64.decode(b64, Base64.NO_PADDING) return Base64.decode(b64, Base64.NO_PADDING)
} }

View File

@ -47,6 +47,8 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo028 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo028
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo029 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo029
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031
import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.Normalizer
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -61,7 +63,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun equals(other: Any?) = other is RealmSessionStoreMigration
override fun hashCode() = 1000 override fun hashCode() = 1000
val schemaVersion = 29L val schemaVersion = 31L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.d("Migrating Realm Session from $oldVersion to $newVersion") Timber.d("Migrating Realm Session from $oldVersion to $newVersion")
@ -95,5 +97,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 27) MigrateSessionTo027(realm).perform() if (oldVersion < 27) MigrateSessionTo027(realm).perform()
if (oldVersion < 28) MigrateSessionTo028(realm).perform() if (oldVersion < 28) MigrateSessionTo028(realm).perform()
if (oldVersion < 29) MigrateSessionTo029(realm).perform() if (oldVersion < 29) MigrateSessionTo029(realm).perform()
if (oldVersion < 30) MigrateSessionTo030(realm).perform()
if (oldVersion < 31) MigrateSessionTo031(realm).perform()
} }
} }

View File

@ -17,7 +17,6 @@
package org.matrix.android.sdk.internal.database.helper package org.matrix.android.sdk.internal.database.helper
import io.realm.Realm import io.realm.Realm
import io.realm.Sort
import io.realm.kotlin.createObject import io.realm.kotlin.createObject
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity
@ -34,32 +33,9 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.find
import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.database.query.whereRoomId
import org.matrix.android.sdk.internal.extensions.assertIsManaged
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
import timber.log.Timber import timber.log.Timber
internal fun ChunkEntity.merge(roomId: String, chunkToMerge: ChunkEntity, direction: PaginationDirection) {
assertIsManaged()
val localRealm = this.realm
val eventsToMerge: List<TimelineEventEntity>
if (direction == PaginationDirection.FORWARDS) {
this.nextToken = chunkToMerge.nextToken
this.isLastForward = chunkToMerge.isLastForward
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
} else {
this.prevToken = chunkToMerge.prevToken
this.isLastBackward = chunkToMerge.isLastBackward
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
}
chunkToMerge.stateEvents.forEach { stateEvent ->
addStateEvent(roomId, stateEvent, direction)
}
eventsToMerge.forEach {
addTimelineEventFromMerge(localRealm, it, direction)
}
}
internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity, direction: PaginationDirection) { internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity, direction: PaginationDirection) {
if (direction == PaginationDirection.BACKWARDS) { if (direction == PaginationDirection.BACKWARDS) {
Timber.v("We don't keep chunk state events when paginating backward") Timber.v("We don't keep chunk state events when paginating backward")
@ -144,40 +120,6 @@ internal fun computeIsUnique(
} }
} }
private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEntity: TimelineEventEntity, direction: PaginationDirection) {
val eventId = timelineEventEntity.eventId
if (timelineEvents.find(eventId) != null) {
return
}
val displayIndex = nextDisplayIndex(direction)
val localId = TimelineEventEntity.nextId(realm)
val copied = realm.createObject<TimelineEventEntity>().apply {
this.localId = localId
this.root = timelineEventEntity.root
this.eventId = timelineEventEntity.eventId
this.roomId = timelineEventEntity.roomId
this.annotations = timelineEventEntity.annotations
this.readReceipts = timelineEventEntity.readReceipts
this.displayIndex = displayIndex
this.senderAvatar = timelineEventEntity.senderAvatar
this.senderName = timelineEventEntity.senderName
this.isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName
}
handleThreadSummary(realm, eventId, copied)
timelineEvents.add(copied)
}
/**
* Upon copy of the timeline events we should update the latestMessage TimelineEventEntity with the new one.
*/
private fun handleThreadSummary(realm: Realm, oldEventId: String, newTimelineEventEntity: TimelineEventEntity) {
EventEntity
.whereRoomId(realm, newTimelineEventEntity.roomId)
.equalTo(EventEntityFields.IS_ROOT_THREAD, true)
.equalTo(EventEntityFields.THREAD_SUMMARY_LATEST_MESSAGE.EVENT_ID, oldEventId)
.findFirst()?.threadSummaryLatestMessage = newTimelineEventEntity
}
private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventEntity, senderId: String): ReadReceiptsSummaryEntity { private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventEntity, senderId: String): ReadReceiptsSummaryEntity {
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventEntity.eventId).findFirst() val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventEntity.eventId).findFirst()
?: realm.createObject<ReadReceiptsSummaryEntity>(eventEntity.eventId).apply { ?: realm.createObject<ReadReceiptsSummaryEntity>(eventEntity.eventId).apply {

View File

@ -271,7 +271,7 @@ private fun HashMap<String, RoomMemberContent?>.addSenderState(realm: Realm, roo
* Create an EventEntity for the root thread event or get an existing one. * Create an EventEntity for the root thread event or get an existing one.
*/ */
private fun createEventEntity(realm: Realm, roomId: String, event: Event, currentTimeMillis: Long): EventEntity { private fun createEventEntity(realm: Realm, roomId: String, event: Event, currentTimeMillis: Long): EventEntity {
val ageLocalTs = event.unsignedData?.age?.let { currentTimeMillis - it } val ageLocalTs = currentTimeMillis - (event.unsignedData?.age ?: 0)
return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
} }

View File

@ -130,7 +130,7 @@ internal fun EventEntity.asDomain(castJsonNumbers: Boolean = false): Event {
internal fun Event.toEntity( internal fun Event.toEntity(
roomId: String, roomId: String,
sendState: SendState, sendState: SendState,
ageLocalTs: Long?, ageLocalTs: Long,
contentToInject: String? = null contentToInject: String? = null
): EventEntity { ): EventEntity {
return EventMapper.map(this, roomId).apply { return EventMapper.map(this, roomId).apply {

View File

@ -42,7 +42,8 @@ internal object HomeServerCapabilitiesMapper {
lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported, lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
defaultIdentityServerUrl = entity.defaultIdentityServerUrl, defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
roomVersions = mapRoomVersion(entity.roomVersionsJson), roomVersions = mapRoomVersion(entity.roomVersionsJson),
canUseThreading = entity.canUseThreading canUseThreading = entity.canUseThreading,
canControlLogoutDevices = entity.canControlLogoutDevices
) )
} }

View File

@ -16,15 +16,17 @@
package org.matrix.android.sdk.internal.database.mapper package org.matrix.android.sdk.internal.database.mapper
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import javax.inject.Inject import javax.inject.Inject
internal class LiveLocationShareAggregatedSummaryMapper @Inject constructor() { internal class LiveLocationShareAggregatedSummaryMapper @Inject constructor() :
Monarchy.Mapper<LiveLocationShareAggregatedSummary, LiveLocationShareAggregatedSummaryEntity> {
fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary { override fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary {
return LiveLocationShareAggregatedSummary( return LiveLocationShareAggregatedSummary(
userId = entity.userId, userId = entity.userId,
isActive = entity.isActive, isActive = entity.isActive,

View File

@ -25,7 +25,7 @@ import org.matrix.android.sdk.internal.util.database.RealmMigrator
* Migrating to: * Migrating to:
* Live location sharing aggregated summary: adding new field userId. * Live location sharing aggregated summary: adding new field userId.
*/ */
internal class MigrateSessionTo029(realm: DynamicRealm) : RealmMigrator(realm, 28) { internal class MigrateSessionTo029(realm: DynamicRealm) : RealmMigrator(realm, 29) {
override fun doMigrate(realm: DynamicRealm) { override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("LiveLocationShareAggregatedSummaryEntity") realm.schema.get("LiveLocationShareAggregatedSummaryEntity")

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
import org.matrix.android.sdk.internal.database.model.EventEntityFields
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
/**
* Migrating to:
* Cleaning old chunks which may have broken links.
*/
internal class MigrateSessionTo030(realm: DynamicRealm) : RealmMigrator(realm, 30) {
override fun doMigrate(realm: DynamicRealm) {
// Delete all previous chunks
val chunks = realm.where("ChunkEntity")
.equalTo(ChunkEntityFields.IS_LAST_FORWARD, false)
.findAll()
val nbOfDeletedChunks = chunks.size
var nbOfDeletedTimelineEvents = 0
var nbOfDeletedEvents = 0
chunks.forEach { chunk ->
val timelineEvents = chunk.getList(ChunkEntityFields.TIMELINE_EVENTS.`$`)
timelineEvents.forEach { timelineEvent ->
// Don't delete state events
val event = timelineEvent.getObject(TimelineEventEntityFields.ROOT.`$`)
if (event?.isNull(EventEntityFields.STATE_KEY) == true) {
nbOfDeletedEvents++
event.deleteFromRealm()
}
}
nbOfDeletedTimelineEvents += timelineEvents.size
timelineEvents.deleteAllFromRealm()
}
chunks.deleteAllFromRealm()
Timber.d(
"MigrateSessionTo030: $nbOfDeletedChunks deleted chunk(s)," +
" $nbOfDeletedTimelineEvents deleted TimelineEvent(s)" +
" and $nbOfDeletedEvents deleted Event(s)."
)
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
import org.matrix.android.sdk.internal.util.database.RealmMigrator
internal class MigrateSessionTo031(realm: DynamicRealm) : RealmMigrator(realm, 31) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("HomeServerCapabilitiesEntity")
?.addField(HomeServerCapabilitiesEntityFields.CAN_CONTROL_LOGOUT_DEVICES, Boolean::class.java)
?.forceRefreshOfHomeServerCapabilities()
}
}

View File

@ -29,7 +29,8 @@ internal open class HomeServerCapabilitiesEntity(
var lastVersionIdentityServerSupported: Boolean = false, var lastVersionIdentityServerSupported: Boolean = false,
var defaultIdentityServerUrl: String? = null, var defaultIdentityServerUrl: String? = null,
var lastUpdatedTimestamp: Long = 0L, var lastUpdatedTimestamp: Long = 0L,
var canUseThreading: Boolean = false var canUseThreading: Boolean = false,
var canControlLogoutDevices: Boolean = false
) : RealmObject() { ) : RealmObject() {
companion object companion object

View File

@ -31,6 +31,7 @@ internal fun ChunkEntity.Companion.where(realm: Realm, roomId: String): RealmQue
internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken: String? = null, nextToken: String? = null): ChunkEntity? { internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken: String? = null, nextToken: String? = null): ChunkEntity? {
val query = where(realm, roomId) val query = where(realm, roomId)
if (prevToken == null && nextToken == null) return null
if (prevToken != null) { if (prevToken != null) {
query.equalTo(ChunkEntityFields.PREV_TOKEN, prevToken) query.equalTo(ChunkEntityFields.PREV_TOKEN, prevToken)
} }
@ -40,7 +41,7 @@ internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken:
return query.findFirst() return query.findFirst()
} }
internal fun ChunkEntity.Companion.findAll(realm: Realm, roomId: String, prevToken: String? = null, nextToken: String? = null): RealmResults<ChunkEntity>? { internal fun ChunkEntity.Companion.findAll(realm: Realm, roomId: String, prevToken: String? = null, nextToken: String? = null): RealmResults<ChunkEntity> {
val query = where(realm, roomId) val query = where(realm, roomId)
if (prevToken != null) { if (prevToken != null) {
query.equalTo(ChunkEntityFields.PREV_TOKEN, prevToken) query.equalTo(ChunkEntityFields.PREV_TOKEN, prevToken)

View File

@ -76,7 +76,7 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveIn
realm: Realm, realm: Realm,
roomId: String, roomId: String,
userId: String, userId: String,
ignoredEventId: String ignoredEventId: String,
): List<LiveLocationShareAggregatedSummaryEntity> { ): List<LiveLocationShareAggregatedSummaryEntity> {
return LiveLocationShareAggregatedSummaryEntity return LiveLocationShareAggregatedSummaryEntity
.whereRoomId(realm, roomId = roomId) .whereRoomId(realm, roomId = roomId)
@ -84,6 +84,7 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveIn
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true) .equalTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
.notEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, ignoredEventId) .notEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, ignoredEventId)
.findAll() .findAll()
.toList()
} }
/** /**

View File

@ -28,6 +28,8 @@ import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.securestorage.SecureStorageModule
import org.matrix.android.sdk.api.securestorage.SecureStorageService
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.auth.AuthModule import org.matrix.android.sdk.internal.auth.AuthModule
@ -53,7 +55,8 @@ import java.io.File
DebugModule::class, DebugModule::class,
SettingsModule::class, SettingsModule::class,
SystemModule::class, SystemModule::class,
NoOpTestModule::class NoOpTestModule::class,
SecureStorageModule::class,
] ]
) )
@MatrixScope @MatrixScope
@ -96,6 +99,8 @@ internal interface MatrixComponent {
fun sessionManager(): SessionManager fun sessionManager(): SessionManager
fun secureStorageService(): SecureStorageService
fun matrixWorkerFactory(): MatrixWorkerFactory fun matrixWorkerFactory(): MatrixWorkerFactory
fun inject(matrix: Matrix) fun inject(matrix: Matrix)

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