mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-25 02:45:53 +03:00
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:
commit
3f087eb632
289 changed files with 9514 additions and 1219 deletions
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
|
@ -8,8 +8,9 @@ on:
|
|||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
CI_GRADLE_ARG_PROPERTIES: >
|
||||
-Porg.gradle.jvmargs=-Xmx2g
|
||||
-Porg.gradle.jvmargs=-Xmx4g
|
||||
-Porg.gradle.parallel=false
|
||||
--no-daemon
|
||||
|
||||
jobs:
|
||||
debug:
|
||||
|
|
1
.github/workflows/post-pr.yml
vendored
1
.github/workflows/post-pr.yml
vendored
|
@ -13,6 +13,7 @@ env:
|
|||
CI_GRADLE_ARG_PROPERTIES: >
|
||||
-Porg.gradle.jvmargs=-Xmx4g
|
||||
-Porg.gradle.parallel=false
|
||||
--no-daemon
|
||||
|
||||
jobs:
|
||||
|
||||
|
|
8
.github/workflows/quality.yml
vendored
8
.github/workflows/quality.yml
vendored
|
@ -9,6 +9,8 @@ on:
|
|||
env:
|
||||
CI_GRADLE_ARG_PROPERTIES: >
|
||||
-Porg.gradle.jvmargs=-Xmx4g
|
||||
-Porg.gradle.parallel=false
|
||||
--no-daemon
|
||||
|
||||
jobs:
|
||||
check:
|
||||
|
@ -140,7 +142,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Lint analysis
|
||||
run: ./gradlew clean :vector:lint --stacktrace
|
||||
run: ./gradlew clean :vector:lint --stacktrace $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
|
@ -173,7 +175,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- 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
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
|
@ -193,7 +195,7 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
- name: Run detekt
|
||||
run: |
|
||||
./gradlew detekt
|
||||
./gradlew detekt $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
|
|
13
.github/workflows/tests.yml
vendored
13
.github/workflows/tests.yml
vendored
|
@ -8,8 +8,9 @@ on:
|
|||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
CI_GRADLE_ARG_PROPERTIES: >
|
||||
-Porg.gradle.jvmargs=-Xmx2g
|
||||
-Porg.gradle.jvmargs=-Xmx4g
|
||||
-Porg.gradle.parallel=false
|
||||
--no-daemon
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
|
@ -49,7 +50,10 @@ jobs:
|
|||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||
disable-animations: true
|
||||
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'
|
||||
- name: Run all the codecoverage tests at once (retry if emulator failed)
|
||||
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
|
||||
disable-animations: true
|
||||
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
|
||||
if: always() # we may have failed a previous step and retried, that's OK
|
||||
env:
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -16,4 +16,4 @@
|
|||
/fastlane/private
|
||||
/fastlane/report.xml
|
||||
|
||||
/library/build
|
||||
/**/build
|
||||
|
|
22
CHANGES.md
22
CHANGES.md
|
@ -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)
|
||||
=======================================
|
||||
|
||||
|
|
18
build.gradle
18
build.gradle
|
@ -28,8 +28,8 @@ buildscript {
|
|||
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.likethesalad.android:stem-plugin:2.1.1"
|
||||
classpath 'org.owasp:dependency-check-gradle:7.1.0.1'
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.21"
|
||||
classpath 'org.owasp:dependency-check-gradle:7.1.1'
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.0"
|
||||
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
@ -43,7 +43,7 @@ plugins {
|
|||
id "io.gitlab.arturbosch.detekt" version "1.20.0"
|
||||
|
||||
// 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
|
||||
|
@ -168,7 +168,7 @@ def launchTask = getGradle()
|
|||
.toString()
|
||||
.toLowerCase()
|
||||
|
||||
if (launchTask.contains("codeCoverageReport".toLowerCase())) {
|
||||
if (launchTask.contains("coverage".toLowerCase())) {
|
||||
apply from: 'coverage.gradle'
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,7 @@ sonarqube {
|
|||
property "sonar.links.issue", "https://github.com/vector-im/element-android/issues"
|
||||
property "sonar.organization", "new_vector_ltd_organization"
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
@ -252,11 +252,7 @@ dependencyAnalysis {
|
|||
exclude("org.json:json") // Used in unit tests, overwrites the one bundled into Android
|
||||
}
|
||||
}
|
||||
project(":library:ui-styles") {
|
||||
onUnusedDependencies {
|
||||
exclude("com.github.vector-im:PFLockScreen-Android") // False positive
|
||||
}
|
||||
}
|
||||
project(":library:ui-styles")
|
||||
project(":matrix-sdk-android") {
|
||||
onUnusedDependencies {
|
||||
exclude("io.reactivex.rxjava2:rxkotlin") // Transitively required for mocking realm as monarchy doesn't expose Rx
|
||||
|
@ -271,6 +267,8 @@ dependencyAnalysis {
|
|||
onUnusedDependencies {
|
||||
// False positives
|
||||
exclude(
|
||||
"androidx.fragment:fragment-testing",
|
||||
"com.facebook.soloader:soloader",
|
||||
"com.vanniktech:emoji-google",
|
||||
"com.vanniktech:emoji-material",
|
||||
"org.maplibre.gl:android-plugin-annotation-v9",
|
||||
|
|
1
changelog.d/5821.bugfix
Normal file
1
changelog.d/5821.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fixes concurrent modification crash when signing out or launching the app
|
1
changelog.d/5864.sdk
Normal file
1
changelog.d/5864.sdk
Normal file
|
@ -0,0 +1 @@
|
|||
Group all location sharing related API into LocationSharingService
|
1
changelog.d/6101.bugfix
Normal file
1
changelog.d/6101.bugfix
Normal 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
1
changelog.d/6155.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Add unit tests for LiveLocationAggregationProcessor code
|
1
changelog.d/6191.sdk
Normal file
1
changelog.d/6191.sdk
Normal 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
1
changelog.d/6217.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Improve lock screen implementation.
|
1
changelog.d/6315.bugfix
Normal file
1
changelog.d/6315.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
[Location sharing] Fix crash when starting/stopping a live when offline
|
1
changelog.d/6318.bugfix
Normal file
1
changelog.d/6318.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix loop in timeline and simplify management of chunks and timeline events.
|
1
changelog.d/6326.bugfix
Normal file
1
changelog.d/6326.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Update design and behaviour on widget permission bottom sheet
|
1
changelog.d/6328.bugfix
Normal file
1
changelog.d/6328.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix | Some user verification requests couldn't be accepted/declined
|
1
changelog.d/6329.misc
Normal file
1
changelog.d/6329.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Fix flaky test in voice recording feature.
|
1
changelog.d/6349.bugfix
Normal file
1
changelog.d/6349.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
[Location sharing] Fix stop of a live not possible from another device
|
1
changelog.d/6350.feature
Normal file
1
changelog.d/6350.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Promote live location labs flag
|
1
changelog.d/6357.bugfix
Normal file
1
changelog.d/6357.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix backslash escapes in formatted messages
|
1
changelog.d/6364.feature
Normal file
1
changelog.d/6364.feature
Normal file
|
@ -0,0 +1 @@
|
|||
[Location sharing] - Stop any active live before starting a new one
|
1
changelog.d/6366.misc
Normal file
1
changelog.d/6366.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Poll view state unit tests
|
2
changelog.d/6369.feature
Normal file
2
changelog.d/6369.feature
Normal file
|
@ -0,0 +1,2 @@
|
|||
Expose pusher profile tag in advanced settings
|
||||
|
1
changelog.d/6371.bugfix
Normal file
1
changelog.d/6371.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fixes wrong error message when signing in with wrong credentials
|
1
changelog.d/6375.bugfix
Normal file
1
changelog.d/6375.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
[Location Share] - Adding missing prefix "u=" for uncertainty in geo URI
|
1
changelog.d/6394.misc
Normal file
1
changelog.d/6394.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Let LoadRoomMembersTask insert by chunk to release db.
|
1
changelog.d/6396.doc
Normal file
1
changelog.d/6396.doc
Normal file
|
@ -0,0 +1 @@
|
|||
Update the PR process doc to come back to one reviewer with optional additional reviewers.
|
|
@ -24,11 +24,13 @@ def excludes = [
|
|||
|
||||
def initializeReport(report, projects, classExcludes) {
|
||||
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 {
|
||||
xml.enabled true
|
||||
html.enabled true
|
||||
|
@ -43,13 +45,11 @@ def initializeReport(report, projects, classExcludes) {
|
|||
switch (project) {
|
||||
case { project.plugins.hasPlugin("com.android.application") }:
|
||||
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/java")
|
||||
break
|
||||
case { project.plugins.hasPlugin("com.android.library") }:
|
||||
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/java")
|
||||
break
|
||||
|
@ -70,18 +70,21 @@ def collectProjects(predicate) {
|
|||
return subprojects.findAll { it.buildFile.isFile() && predicate(it) }
|
||||
}
|
||||
|
||||
task theCodeCoverageReport(type: JacocoReport) {
|
||||
task generateCoverageReport(type: JacocoReport) {
|
||||
outputs.upToDateWhen { false }
|
||||
rootProject.apply plugin: 'jacoco'
|
||||
tasks.withType(Test) {
|
||||
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']
|
||||
}
|
||||
def projects = collectProjects { ['vector', 'matrix-sdk-android'].contains(it.name) }
|
||||
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']
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ ext.versions = [
|
|||
def gradle = "7.1.3"
|
||||
// Ref: https://kotlinlang.org/releases.html
|
||||
def kotlin = "1.6.21"
|
||||
def kotlinCoroutines = "1.6.2"
|
||||
def kotlinCoroutines = "1.6.3"
|
||||
def dagger = "2.42"
|
||||
def retrofit = "2.9.0"
|
||||
def arrow = "0.8.2"
|
||||
|
@ -21,20 +21,21 @@ def markwon = "4.6.2"
|
|||
def moshi = "1.13.0"
|
||||
def lifecycle = "2.4.1"
|
||||
def flowBinding = "1.2.0"
|
||||
def flipper = "0.151.1"
|
||||
def epoxy = "4.6.2"
|
||||
def mavericks = "2.6.1"
|
||||
def mavericks = "2.7.0"
|
||||
def glide = "4.13.2"
|
||||
def bigImageViewer = "1.8.1"
|
||||
def jjwt = "0.11.5"
|
||||
def vanniktechEmoji = "0.15.0"
|
||||
|
||||
def fragment = "1.4.1"
|
||||
|
||||
// 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 espresso = "3.4.0"
|
||||
def androidxTest = "1.4.0"
|
||||
def androidxOrchestrator = "1.4.1"
|
||||
|
||||
|
||||
ext.libs = [
|
||||
gradle : [
|
||||
'gradlePlugin' : "com.android.tools.build:gradle:$gradle",
|
||||
|
@ -48,13 +49,16 @@ ext.libs = [
|
|||
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
|
||||
],
|
||||
androidx : [
|
||||
'annotation' : "androidx.annotation:annotation:1.3.0",
|
||||
'annotation' : "androidx.annotation:annotation:1.4.0",
|
||||
'activity' : "androidx.activity:activity:1.4.0",
|
||||
'annotations' : "androidx.annotation:annotation:1.3.0",
|
||||
'appCompat' : "androidx.appcompat:appcompat:1.4.2",
|
||||
'biometric' : "androidx.biometric:biometric:1.1.0",
|
||||
'core' : "androidx.core:core-ktx:1.8.0",
|
||||
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
|
||||
'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",
|
||||
'work' : "androidx.work:work-runtime-ktx:2.7.1",
|
||||
'autoFill' : "androidx.autofill:autofill:1.1.0",
|
||||
|
@ -85,8 +89,13 @@ ext.libs = [
|
|||
'dagger' : "com.google.dagger:dagger:$dagger",
|
||||
'daggerCompiler' : "com.google.dagger:dagger-compiler:$dagger",
|
||||
'hilt' : "com.google.dagger:hilt-android:$dagger",
|
||||
'hiltAndroidTesting' : "com.google.dagger:hilt-android-testing:$dagger",
|
||||
'hiltCompiler' : "com.google.dagger:hilt-compiler:$dagger"
|
||||
],
|
||||
flipper : [
|
||||
'flipper' : "com.facebook.flipper:flipper:$flipper",
|
||||
'flipperNetworkPlugin' : "com.facebook.flipper:flipper-network-plugin:$flipper",
|
||||
],
|
||||
squareup : [
|
||||
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
||||
'moshiKt' : "com.squareup.moshi:moshi-kotlin:$moshi",
|
||||
|
@ -155,3 +164,5 @@ ext.libs = [
|
|||
'junit' : "junit:junit:4.13.2"
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -83,15 +83,16 @@ Exceptions can occur:
|
|||
|
||||
##### 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.
|
||||
- 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 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.
|
||||
- The PR creator selects 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.
|
||||
- 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).
|
||||
- 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
|
||||
|
||||
|
@ -102,6 +103,7 @@ Some tips to achieve it:
|
|||
- Set up your GH notifications correctly
|
||||
- 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
|
||||
- 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.
|
||||
|
||||
|
|
2
fastlane/metadata/android/en-US/changelogs/40104230.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40104230.txt
Normal 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
|
2
fastlane/metadata/android/en-US/changelogs/40104240.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40104240.txt
Normal 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
|
2
fastlane/metadata/android/en-US/changelogs/40104250.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40104250.txt
Normal 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
|
|
@ -56,8 +56,6 @@ dependencies {
|
|||
implementation libs.google.material
|
||||
// Pref theme
|
||||
implementation libs.androidx.preferenceKtx
|
||||
// PFLockScreen attrs
|
||||
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
|
||||
// dialpad dimen
|
||||
implementation 'im.dlg:android-dialer:1.2.5'
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
12
library/ui-styles/src/main/res/values/lockscreen_attr.xml
Normal file
12
library/ui-styles/src/main/res/values/lockscreen_attr.xml
Normal 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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -4,10 +4,13 @@
|
|||
<!-- BottomSheet theming -->
|
||||
<style name="Theme.Vector.BottomSheetDialog.Light" parent="Theme.MaterialComponents.Light.BottomSheetDialog">
|
||||
<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="colorOnSecondary">@color/palette_white</item>
|
||||
<item name="colorSurface">@color/element_background_light</item>
|
||||
<item name="colorOnSurface">@color/element_content_primary_light</item>
|
||||
<item name="colorError">@color/element_alert_light</item>
|
||||
<item name="colorOnError">@color/palette_white</item>
|
||||
<!-- Default color for text View -->
|
||||
<item name="android:textColorTertiary">@color/element_content_primary_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">
|
||||
<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="colorOnSecondary">@color/palette_white</item>
|
||||
<item name="colorSurface">@color/element_background_dark</item>
|
||||
<item name="colorOnSurface">@color/element_content_primary_dark</item>
|
||||
<item name="colorError">@color/element_alert_dark</item>
|
||||
<item name="colorOnError">@color/palette_white</item>
|
||||
<!-- Default color for text View -->
|
||||
<item name="android:textColorTertiary">@color/element_content_primary_dark</item>
|
||||
<item name="android:textColorLink">@color/element_link_dark</item>
|
||||
|
@ -59,4 +65,4 @@
|
|||
<item name="android:textSize">12sp</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -65,4 +65,4 @@
|
|||
<item name="colorPrimary">?colorOnPrimary</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -22,13 +22,13 @@
|
|||
</style>
|
||||
|
||||
<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="background">@drawable/bg_pin_key</item>
|
||||
</style>
|
||||
|
||||
<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="background">@drawable/bg_pin_key</item>
|
||||
</style>
|
||||
|
|
|
@ -111,14 +111,14 @@
|
|||
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
|
||||
|
||||
<item name="pf_lock_screen">@style/PinCodeScreenStyle</item>
|
||||
<item name="pf_key_button">@style/PinCodeKeyButtonStyle</item>
|
||||
<item name="pf_title">@style/PinCodeTitleStyle</item>
|
||||
<item name="pf_hint">@style/PinCodeHintStyle</item>
|
||||
<item name="pf_code_view">@style/PinCodeDotsViewStyle</item>
|
||||
<item name="pf_delete_button">@style/PinCodeDeleteButtonStyle</item>
|
||||
<item name="pf_fingerprint_button">@style/PinCodeFingerprintButtonStyle</item>
|
||||
<item name="pf_next">@style/PinCodeNextButtonStyle</item>
|
||||
<item name="lockscreen_theme">@style/PinCodeScreenStyle</item>
|
||||
<item name="lockscreen_key_button_theme">@style/PinCodeKeyButtonStyle</item>
|
||||
<item name="lockscreen_title_theme">@style/PinCodeTitleStyle</item>
|
||||
<item name="lockscreen_hint_theme">@style/PinCodeHintStyle</item>
|
||||
<item name="lockscreen_code_view_theme">@style/PinCodeDotsViewStyle</item>
|
||||
<item name="lockscreen_delete_button_theme">@style/PinCodeDeleteButtonStyle</item>
|
||||
<item name="lockscreen_fingerprint_button_theme">@style/PinCodeFingerprintButtonStyle</item>
|
||||
<item name="lockscreen_next_theme">@style/PinCodeNextButtonStyle</item>
|
||||
|
||||
<item name="android:statusBarColor">@color/android_status_bar_background_dark</item>
|
||||
<item name="android:navigationBarColor">@color/android_navigation_bar_background_dark</item>
|
||||
|
|
|
@ -111,14 +111,14 @@
|
|||
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
|
||||
|
||||
<item name="pf_lock_screen">@style/PinCodeScreenStyle</item>
|
||||
<item name="pf_key_button">@style/PinCodeKeyButtonStyle</item>
|
||||
<item name="pf_title">@style/PinCodeTitleStyle</item>
|
||||
<item name="pf_hint">@style/PinCodeHintStyle</item>
|
||||
<item name="pf_code_view">@style/PinCodeDotsViewStyle</item>
|
||||
<item name="pf_delete_button">@style/PinCodeDeleteButtonStyle</item>
|
||||
<item name="pf_fingerprint_button">@style/PinCodeFingerprintButtonStyle</item>
|
||||
<item name="pf_next">@style/PinCodeNextButtonStyle</item>
|
||||
<item name="lockscreen_theme">@style/PinCodeScreenStyle</item>
|
||||
<item name="lockscreen_key_button_theme">@style/PinCodeKeyButtonStyle</item>
|
||||
<item name="lockscreen_title_theme">@style/PinCodeTitleStyle</item>
|
||||
<item name="lockscreen_hint_theme">@style/PinCodeHintStyle</item>
|
||||
<item name="lockscreen_code_view_theme">@style/PinCodeDotsViewStyle</item>
|
||||
<item name="lockscreen_delete_button_theme">@style/PinCodeDeleteButtonStyle</item>
|
||||
<item name="lockscreen_fingerprint_button_theme">@style/PinCodeFingerprintButtonStyle</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+ -->
|
||||
<item name="android:statusBarColor">@color/android_status_bar_background_dark</item>
|
||||
|
|
|
@ -5,6 +5,10 @@ apply plugin: 'kotlin-parcelize'
|
|||
apply plugin: 'realm-android'
|
||||
apply plugin: "org.jetbrains.dokka"
|
||||
|
||||
if (project.hasProperty("coverage")) {
|
||||
apply plugin: 'jacoco'
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
// 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.
|
||||
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_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
||||
|
@ -74,7 +78,9 @@ android {
|
|||
|
||||
buildTypes {
|
||||
debug {
|
||||
testCoverageEnabled true
|
||||
if (project.hasProperty("coverage")) {
|
||||
testCoverageEnabled = coverage.enableTestCoverage
|
||||
}
|
||||
// Set to true to log privacy or sensible data, such as token
|
||||
buildConfigField "boolean", "LOG_PRIVATE_DATA", project.property("vector.debugPrivateData")
|
||||
// Set to BODY instead of NONE to enable logging
|
||||
|
@ -193,7 +199,7 @@ dependencies {
|
|||
implementation libs.apache.commonsImaging
|
||||
|
||||
// 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
|
||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||
|
|
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,9 +14,9 @@
|
|||
* 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 {
|
||||
var value: Int = 0
|
|
@ -14,40 +14,57 @@
|
|||
* 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.util.Base64
|
||||
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.shouldBeInstanceOf
|
||||
import org.amshove.kluent.shouldNotThrow
|
||||
import org.amshove.kluent.shouldThrow
|
||||
import org.junit.Before
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.util.fromBase64
|
||||
import org.matrix.android.sdk.api.util.toBase64NoPadding
|
||||
import org.matrix.android.sdk.TestBuildVersionSdkIntProvider
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.KeyStore
|
||||
import java.security.KeyStoreException
|
||||
import java.util.UUID
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class SecretStoringUtilsTest : InstrumentedTest {
|
||||
class SecretStoringUtilsTest {
|
||||
|
||||
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
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 {
|
||||
const val TEST_STR = "This is something I want to store safely!"
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
clearAllMocks()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStringNominalCaseApi21() {
|
||||
val alias = generateAlias()
|
||||
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
|
||||
// Encrypt
|
||||
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias)
|
||||
val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias)
|
||||
// Decrypt
|
||||
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias)
|
||||
val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias))
|
||||
decrypted shouldBeEqualTo TEST_STR
|
||||
secretStoringUtils.safeDeleteKey(alias)
|
||||
}
|
||||
|
@ -57,9 +74,9 @@ class SecretStoringUtilsTest : InstrumentedTest {
|
|||
val alias = generateAlias()
|
||||
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
|
||||
// Encrypt
|
||||
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias)
|
||||
val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias)
|
||||
// Decrypt
|
||||
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias)
|
||||
val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias))
|
||||
decrypted shouldBeEqualTo TEST_STR
|
||||
secretStoringUtils.safeDeleteKey(alias)
|
||||
}
|
||||
|
@ -69,9 +86,9 @@ class SecretStoringUtilsTest : InstrumentedTest {
|
|||
val alias = generateAlias()
|
||||
buildVersionSdkIntProvider.value = Build.VERSION_CODES.R
|
||||
// Encrypt
|
||||
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias)
|
||||
val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias)
|
||||
// Decrypt
|
||||
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias)
|
||||
val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias))
|
||||
decrypted shouldBeEqualTo TEST_STR
|
||||
secretStoringUtils.safeDeleteKey(alias)
|
||||
}
|
||||
|
@ -81,13 +98,13 @@ class SecretStoringUtilsTest : InstrumentedTest {
|
|||
val alias = generateAlias()
|
||||
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
|
||||
// Encrypt
|
||||
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias)
|
||||
val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias)
|
||||
|
||||
// Simulate a system upgrade
|
||||
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
|
||||
|
||||
// Decrypt
|
||||
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias)
|
||||
val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias))
|
||||
decrypted shouldBeEqualTo TEST_STR
|
||||
secretStoringUtils.safeDeleteKey(alias)
|
||||
}
|
||||
|
@ -180,5 +197,56 @@ class SecretStoringUtilsTest : InstrumentedTest {
|
|||
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 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)
|
||||
}
|
|
@ -20,6 +20,7 @@ import android.content.Context
|
|||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
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.debug.DebugModule
|
||||
import org.matrix.android.sdk.internal.di.MatrixComponent
|
||||
|
@ -39,7 +40,8 @@ import org.matrix.android.sdk.internal.util.system.SystemModule
|
|||
RawModule::class,
|
||||
DebugModule::class,
|
||||
SettingsModule::class,
|
||||
SystemModule::class
|
||||
SystemModule::class,
|
||||
SecureStorageModule::class,
|
||||
]
|
||||
)
|
||||
@MatrixScope
|
||||
|
@ -51,7 +53,7 @@ internal interface TestMatrixComponent : MatrixComponent {
|
|||
interface Factory {
|
||||
fun create(
|
||||
@BindsInstance context: Context,
|
||||
@BindsInstance matrixConfiguration: MatrixConfiguration
|
||||
@BindsInstance matrixConfiguration: MatrixConfiguration,
|
||||
): TestMatrixComponent
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import io.realm.Realm
|
|||
import io.realm.RealmConfiguration
|
||||
import io.realm.kotlin.createObject
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
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.room.send.SendState
|
||||
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.model.ChunkEntity
|
||||
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.util.time.DefaultClock
|
||||
import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeListOfEvents
|
||||
import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeMessageEvent
|
||||
|
||||
@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(
|
||||
roomId: String,
|
||||
events: List<Event>,
|
||||
|
|
|
@ -163,6 +163,8 @@ class TimelineForwardPaginationTest : InstrumentedTest {
|
|||
// Ask for a forward pagination
|
||||
val snapshot = runBlocking {
|
||||
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)
|
||||
snapshot.size == 7 + numberOfMessagesToSend &&
|
||||
|
|
|
@ -20,6 +20,7 @@ import androidx.test.filters.LargeTest
|
|||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
|
@ -39,6 +40,7 @@ import java.util.concurrent.CountDownLatch
|
|||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
@LargeTest
|
||||
class TimelinePreviousLastForwardTest : InstrumentedTest {
|
||||
|
||||
|
@ -229,6 +231,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
|
|||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package org.matrix.android.sdk.api
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.work.Configuration
|
||||
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.ApiPath
|
||||
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.internal.SessionManager
|
||||
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 matrixWorkerFactory: MatrixWorkerFactory
|
||||
@Inject internal lateinit var lightweightSettingsStorage: LightweightSettingsStorage
|
||||
@Inject internal lateinit var secureStorageService: SecureStorageService
|
||||
|
||||
private val uiHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
init {
|
||||
val appContext = context.applicationContext
|
||||
|
@ -76,7 +82,9 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) {
|
|||
.build()
|
||||
WorkManager.initialize(appContext, configuration)
|
||||
}
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||
uiHandler.post {
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,6 +123,11 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) {
|
|||
*/
|
||||
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()`.
|
||||
*/
|
||||
|
|
|
@ -21,5 +21,6 @@ data class LoginFlowResult(
|
|||
val ssoIdentityProviders: List<SsoIdentityProvider>?,
|
||||
val isLoginAndRegistrationSupported: Boolean,
|
||||
val homeServerUrl: String,
|
||||
val isOutdatedHomeserver: Boolean
|
||||
val isOutdatedHomeserver: Boolean,
|
||||
val isLogoutDevicesSupported: Boolean
|
||||
)
|
||||
|
|
|
@ -72,7 +72,9 @@ interface LoginWizard {
|
|||
* Confirm the new password, once the user has checked their email
|
||||
* 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)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package org.matrix.android.sdk.internal.session.securestorage
|
||||
package org.matrix.android.sdk.api.securestorage
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
|
@ -25,7 +25,7 @@ import android.security.KeyPairGeneratorSpec
|
|||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
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 java.io.ByteArrayInputStream
|
||||
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
|
||||
* 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 buildVersionSdkIntProvider: BuildVersionSdkIntProvider
|
||||
private val keyStore: KeyStore,
|
||||
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
|
||||
private val keyNeedsUserAuthentication: Boolean = false,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
@ -94,14 +96,24 @@ internal class SecretStoringUtils @Inject constructor(
|
|||
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()
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
try {
|
||||
keyStore.deleteEntry(keyAlias)
|
||||
|
@ -121,24 +133,24 @@ internal class SecretStoringUtils @Inject constructor(
|
|||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Throws(Exception::class)
|
||||
fun securelyStoreString(secret: String, keyAlias: String): ByteArray {
|
||||
fun securelyStoreBytes(secret: ByteArray, keyAlias: String): ByteArray {
|
||||
return when {
|
||||
buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> encryptStringM(secret, keyAlias)
|
||||
else -> encryptString(secret, keyAlias)
|
||||
buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> encryptBytesM(secret, keyAlias)
|
||||
else -> encryptBytes(secret, keyAlias)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a secret that was encrypted by #securelyStoreString().
|
||||
* Decrypt a secret that was encrypted by [securelyStoreBytes].
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Throws(Exception::class)
|
||||
fun loadSecureSecret(encrypted: ByteArray, keyAlias: String): String {
|
||||
fun loadSecureSecretBytes(encrypted: ByteArray, keyAlias: String): ByteArray {
|
||||
encrypted.inputStream().use { inputStream ->
|
||||
// First get the format
|
||||
return when (val format = inputStream.read().toByte()) {
|
||||
FORMAT_API_M -> decryptStringM(inputStream, keyAlias)
|
||||
FORMAT_1 -> decryptString(inputStream, keyAlias)
|
||||
FORMAT_API_M -> decryptBytesM(inputStream, keyAlias)
|
||||
FORMAT_1 -> decryptBytes(inputStream, keyAlias)
|
||||
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)
|
||||
private fun getOrGenerateSymmetricKeyForAliasM(alias: String): SecretKey {
|
||||
val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry)
|
||||
|
@ -176,6 +204,13 @@ internal class SecretStoringUtils @Inject constructor(
|
|||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.setKeySize(128)
|
||||
.apply {
|
||||
setUserAuthenticationRequired(keyNeedsUserAuthentication)
|
||||
if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.N) {
|
||||
setInvalidatedByBiometricEnrollment(true)
|
||||
}
|
||||
}
|
||||
.setUserAuthenticationRequired(keyNeedsUserAuthentication)
|
||||
.build()
|
||||
generator.init(keyGenSpec)
|
||||
return generator.generateKey()
|
||||
|
@ -216,19 +251,16 @@ internal class SecretStoringUtils @Inject constructor(
|
|||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun encryptStringM(text: String, keyAlias: String): ByteArray {
|
||||
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
|
||||
|
||||
val cipher = Cipher.getInstance(AES_MODE)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
|
||||
private fun encryptBytesM(byteArray: ByteArray, keyAlias: String): ByteArray {
|
||||
val cipher = getEncryptCipher(keyAlias)
|
||||
val iv = cipher.iv
|
||||
// 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)
|
||||
}
|
||||
|
||||
@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 secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
|
||||
|
@ -237,10 +269,10 @@ internal class SecretStoringUtils @Inject constructor(
|
|||
val spec = GCMParameterSpec(128, iv)
|
||||
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
|
||||
val key = ByteArray(16)
|
||||
secureRandom.nextBytes(key)
|
||||
|
@ -252,12 +284,12 @@ internal class SecretStoringUtils @Inject constructor(
|
|||
val cipher = Cipher.getInstance(AES_MODE)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, sKey)
|
||||
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)
|
||||
}
|
||||
|
||||
private fun decryptString(inputStream: InputStream, keyAlias: String): String {
|
||||
private fun decryptBytes(inputStream: InputStream, keyAlias: String): ByteArray {
|
||||
val (encryptedKey, iv, encrypted) = format1Extract(inputStream)
|
||||
|
||||
// we need to decrypt the key
|
||||
|
@ -266,16 +298,13 @@ internal class SecretStoringUtils @Inject constructor(
|
|||
val spec = GCMParameterSpec(128, iv)
|
||||
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)
|
||||
@Throws(IOException::class)
|
||||
private fun saveSecureObjectM(keyAlias: String, output: OutputStream, writeObject: Any) {
|
||||
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
|
||||
|
||||
val cipher = Cipher.getInstance(AES_MODE)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey/*, spec*/)
|
||||
val cipher = getEncryptCipher(keyAlias)
|
||||
val iv = cipher.iv
|
||||
|
||||
val bos1 = ByteArrayOutputStream()
|
||||
|
@ -362,10 +391,8 @@ internal class SecretStoringUtils @Inject constructor(
|
|||
|
||||
@Throws(Exception::class)
|
||||
private fun rsaEncrypt(alias: String, secret: ByteArray): ByteArray {
|
||||
val privateKeyEntry = getOrGenerateKeyPairForAlias(alias)
|
||||
// Encrypt the text
|
||||
val inputCipher = Cipher.getInstance(RSA_MODE)
|
||||
inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.certificate.publicKey)
|
||||
val inputCipher = getEncryptCipher(alias)
|
||||
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
CipherOutputStream(outputStream, inputCipher).use {
|
|
@ -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
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* 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.OutputStream
|
|
@ -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.RoomService
|
||||
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.signout.SignOutService
|
||||
import org.matrix.android.sdk.api.session.space.SpaceService
|
||||
|
@ -200,11 +199,6 @@ interface Session {
|
|||
*/
|
||||
fun syncService(): SyncService
|
||||
|
||||
/**
|
||||
* Returns the SecureStorageService associated with the session.
|
||||
*/
|
||||
fun secureStorageService(): SecureStorageService
|
||||
|
||||
/**
|
||||
* Returns the ProfileService associated with the session.
|
||||
*/
|
||||
|
|
|
@ -24,13 +24,13 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
|||
interface AccountService {
|
||||
/**
|
||||
* Ask the homeserver to change the password.
|
||||
*
|
||||
* @param password Current 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(
|
||||
password: String,
|
||||
newPassword: String
|
||||
)
|
||||
suspend fun changePassword(password: String, newPassword: String, logoutAllDevices: Boolean = true)
|
||||
|
||||
/**
|
||||
* Deactivate the account.
|
||||
|
|
|
@ -54,7 +54,12 @@ data class HomeServerCapabilities(
|
|||
/**
|
||||
* 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 {
|
||||
|
|
|
@ -16,12 +16,58 @@
|
|||
|
||||
package org.matrix.android.sdk.api.session.room.location
|
||||
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.lifecycle.LiveData
|
||||
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.
|
||||
*/
|
||||
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>>
|
||||
|
||||
/**
|
||||
* 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>>
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
|
|||
@JsonClass(generateAdapter = true)
|
||||
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,
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ data class MessageLocationContent(
|
|||
@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,
|
||||
|
||||
|
|
|
@ -25,4 +25,7 @@ data class PollCreationInfo(
|
|||
@Json(name = "kind") val kind: PollType? = PollType.DISCLOSED_UNSTABLE,
|
||||
@Json(name = "max_selections") val maxSelections: Int = 1,
|
||||
@Json(name = "answers") val answers: List<PollAnswer>? = null
|
||||
)
|
||||
) {
|
||||
|
||||
fun isUndisclosed() = kind in listOf(PollType.UNDISCLOSED_UNSTABLE, PollType.UNDISCLOSED)
|
||||
}
|
||||
|
|
|
@ -142,24 +142,6 @@ interface SendService {
|
|||
*/
|
||||
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.
|
||||
* @param localEcho the unsent local echo
|
||||
|
|
|
@ -66,19 +66,6 @@ interface StateService {
|
|||
*/
|
||||
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.
|
||||
* @param eventType The type of event to send.
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
* 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.
|
||||
*/
|
|
@ -14,12 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.util.system
|
||||
package org.matrix.android.sdk.api.util
|
||||
|
||||
import android.os.Build
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultBuildVersionSdkIntProvider @Inject constructor() :
|
||||
class DefaultBuildVersionSdkIntProvider @Inject constructor() :
|
||||
BuildVersionSdkIntProvider {
|
||||
override fun get() = Build.VERSION.SDK_INT
|
||||
}
|
|
@ -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.registration.DefaultRegistrationWizard
|
||||
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.isSupportedBySdk
|
||||
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,
|
||||
isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(),
|
||||
homeServerUrl = homeServerUrl,
|
||||
isOutdatedHomeserver = !versions.isSupportedBySdk()
|
||||
isOutdatedHomeserver = !versions.isSupportedBySdk(),
|
||||
isLogoutDevicesSupported = versions.doesServerSupportLogoutDevices()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -121,12 +121,13 @@ internal class DefaultLoginWizard(
|
|||
.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 param = ResetPasswordMailConfirmed.create(
|
||||
pendingSessionData.clientSecret,
|
||||
resetPasswordData.addThreePidRegistrationResponse.sid,
|
||||
newPassword
|
||||
newPassword,
|
||||
logoutAllDevices
|
||||
)
|
||||
|
||||
executeRequest(null) {
|
||||
|
|
|
@ -30,13 +30,17 @@ internal data class ResetPasswordMailConfirmed(
|
|||
|
||||
// the new password
|
||||
@Json(name = "new_password")
|
||||
val newPassword: String? = null
|
||||
val newPassword: String? = null,
|
||||
|
||||
@Json(name = "logout_devices")
|
||||
val logoutDevices: Boolean? = null
|
||||
) {
|
||||
companion object {
|
||||
fun create(clientSecret: String, sid: String, newPassword: String): ResetPasswordMailConfirmed {
|
||||
fun create(clientSecret: String, sid: String, newPassword: String, logoutDevices: Boolean?): ResetPasswordMailConfirmed {
|
||||
return ResetPasswordMailConfirmed(
|
||||
auth = AuthParams.createForResetPassword(clientSecret, sid),
|
||||
newPassword = newPassword
|
||||
newPassword = newPassword,
|
||||
logoutDevices = logoutDevices
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ internal data class HomeServerVersion(
|
|||
val r0_4_0 = HomeServerVersion(major = 0, minor = 4, 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_1 = HomeServerVersion(major = 0, minor = 6, patch = 1)
|
||||
val v1_3_0 = HomeServerVersion(major = 1, minor = 3, patch = 0)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,6 +111,15 @@ private fun Versions.doesServerSeparatesAddAndBind(): Boolean {
|
|||
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 {
|
||||
return supportedVersions
|
||||
?.mapNotNull { HomeServerVersion.parse(it) }
|
||||
|
|
|
@ -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,
|
||||
// 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")
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import androidx.core.content.edit
|
|||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
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 java.security.SecureRandom
|
||||
import javax.inject.Inject
|
||||
|
@ -40,7 +40,7 @@ import javax.inject.Inject
|
|||
*/
|
||||
internal class RealmKeysUtils @Inject constructor(
|
||||
context: Context,
|
||||
private val secretStoringUtils: SecretStoringUtils
|
||||
private val secretStoringUtils: SecretStoringUtils,
|
||||
) {
|
||||
|
||||
private val rng = SecureRandom()
|
||||
|
@ -71,7 +71,7 @@ internal class RealmKeysUtils @Inject constructor(
|
|||
private fun createAndSaveKeyForDatabase(alias: String): ByteArray {
|
||||
val key = generateKeyForRealm()
|
||||
val encodedKey = Base64.encodeToString(key, Base64.NO_PADDING)
|
||||
val toStore = secretStoringUtils.securelyStoreString(encodedKey, alias)
|
||||
val toStore = secretStoringUtils.securelyStoreBytes(encodedKey.toByteArray(), alias)
|
||||
sharedPreferences.edit {
|
||||
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 {
|
||||
val encryptedB64 = sharedPreferences.getString("${ENCRYPTED_KEY_PREFIX}_$alias", null)
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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.MigrateSessionTo028
|
||||
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 timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
@ -61,7 +63,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||
override fun equals(other: Any?) = other is RealmSessionStoreMigration
|
||||
override fun hashCode() = 1000
|
||||
|
||||
val schemaVersion = 29L
|
||||
val schemaVersion = 31L
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
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 < 28) MigrateSessionTo028(realm).perform()
|
||||
if (oldVersion < 29) MigrateSessionTo029(realm).perform()
|
||||
if (oldVersion < 30) MigrateSessionTo030(realm).perform()
|
||||
if (oldVersion < 31) MigrateSessionTo031(realm).perform()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package org.matrix.android.sdk.internal.database.helper
|
||||
|
||||
import io.realm.Realm
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.createObject
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||
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.getOrCreate
|
||||
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 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) {
|
||||
if (direction == PaginationDirection.BACKWARDS) {
|
||||
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 {
|
||||
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventEntity.eventId).findFirst()
|
||||
?: realm.createObject<ReadReceiptsSummaryEntity>(eventEntity.eventId).apply {
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -130,7 +130,7 @@ internal fun EventEntity.asDomain(castJsonNumbers: Boolean = false): Event {
|
|||
internal fun Event.toEntity(
|
||||
roomId: String,
|
||||
sendState: SendState,
|
||||
ageLocalTs: Long?,
|
||||
ageLocalTs: Long,
|
||||
contentToInject: String? = null
|
||||
): EventEntity {
|
||||
return EventMapper.map(this, roomId).apply {
|
||||
|
|
|
@ -42,7 +42,8 @@ internal object HomeServerCapabilitiesMapper {
|
|||
lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
|
||||
defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
|
||||
roomVersions = mapRoomVersion(entity.roomVersionsJson),
|
||||
canUseThreading = entity.canUseThreading
|
||||
canUseThreading = entity.canUseThreading,
|
||||
canControlLogoutDevices = entity.canControlLogoutDevices
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -16,15 +16,17 @@
|
|||
|
||||
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.room.model.livelocation.LiveLocationShareAggregatedSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
|
||||
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||
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(
|
||||
userId = entity.userId,
|
||||
isActive = entity.isActive,
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
|||
* Migrating to:
|
||||
* 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) {
|
||||
realm.schema.get("LiveLocationShareAggregatedSummaryEntity")
|
||||
|
|
|
@ -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)."
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -29,7 +29,8 @@ internal open class HomeServerCapabilitiesEntity(
|
|||
var lastVersionIdentityServerSupported: Boolean = false,
|
||||
var defaultIdentityServerUrl: String? = null,
|
||||
var lastUpdatedTimestamp: Long = 0L,
|
||||
var canUseThreading: Boolean = false
|
||||
var canUseThreading: Boolean = false,
|
||||
var canControlLogoutDevices: Boolean = false
|
||||
) : RealmObject() {
|
||||
|
||||
companion object
|
||||
|
|
|
@ -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? {
|
||||
val query = where(realm, roomId)
|
||||
if (prevToken == null && nextToken == null) return null
|
||||
if (prevToken != null) {
|
||||
query.equalTo(ChunkEntityFields.PREV_TOKEN, prevToken)
|
||||
}
|
||||
|
@ -40,7 +41,7 @@ internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken:
|
|||
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)
|
||||
if (prevToken != null) {
|
||||
query.equalTo(ChunkEntityFields.PREV_TOKEN, prevToken)
|
||||
|
|
|
@ -76,7 +76,7 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveIn
|
|||
realm: Realm,
|
||||
roomId: String,
|
||||
userId: String,
|
||||
ignoredEventId: String
|
||||
ignoredEventId: String,
|
||||
): List<LiveLocationShareAggregatedSummaryEntity> {
|
||||
return LiveLocationShareAggregatedSummaryEntity
|
||||
.whereRoomId(realm, roomId = roomId)
|
||||
|
@ -84,6 +84,7 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveIn
|
|||
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
|
||||
.notEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, ignoredEventId)
|
||||
.findAll()
|
||||
.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue