diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml index 8fe51eb8d5..49669e4201 100644 --- a/.github/workflows/post-pr.yml +++ b/.github/workflows/post-pr.yml @@ -29,200 +29,6 @@ jobs: steps: - run: echo "Run those tests!" # no-op success - # Run Android Tests - integration-tests: - name: Matrix SDK - Running Integration Tests - needs: should-i-run - runs-on: macos-latest - strategy: - fail-fast: false - matrix: - api-level: [ 28 ] - steps: - - uses: actions/checkout@v3 - - uses: gradle/wrapper-validation-action@v1 - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: 11 - - name: Set up Python 3.8 - uses: actions/setup-python@v3 - with: - python-version: 3.8 - - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Start synapse server - uses: michaelkaye/setup-matrix-synapse@v1.0.3 - with: - uploadLogs: true - httpPort: 8080 - disableRateLimiting: true - public_baseurl: "http://10.0.2.2:8080/" - # package: org.matrix.android.sdk.session - - name: Run integration tests for Matrix SDK [org.matrix.android.sdk.session] API[${{ matrix.api-level }}] - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86 - profile: Nexus 5X - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - emulator-build: 7425822 - script: | - adb root - adb logcat -c - touch emulator-session.log - chmod 777 emulator-session.log - adb logcat >> emulator-session.log & - ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.package='org.matrix.android.sdk.session' matrix-sdk-android:connectedDebugAndroidTest - - name: Read Results [org.matrix.android.sdk.session] - if: always() - id: get-comment-body-session - run: python3 ./tools/ci/render_test_output.py session ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml - - name: Remove adb logcat - if: always() - run: pkill -9 adb - - name: Run integration tests for Matrix SDK [org.matrix.android.sdk.account] API[${{ matrix.api-level }}] - if: always() - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86 - profile: Nexus 5X - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - emulator-build: 7425822 - script: | - adb root - adb logcat -c - touch emulator-account.log - chmod 777 emulator-account.log - adb logcat >> emulator-account.log & - ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.package='org.matrix.android.sdk.account' matrix-sdk-android:connectedDebugAndroidTest - - name: Read Results [org.matrix.android.sdk.account] - if: always() - id: get-comment-body-account - run: python3 ./tools/ci/render_test_output.py account ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml - - name: Remove adb logcat - if: always() - run: pkill -9 adb - # package: org.matrix.android.sdk.internal - - name: Run integration tests for Matrix SDK [org.matrix.android.sdk.internal] API[${{ matrix.api-level }}] - if: always() - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86 - profile: Nexus 5X - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - emulator-build: 7425822 - script: | - adb root - adb logcat -c - touch emulator-internal.log - chmod 777 emulator-internal.log - adb logcat >> emulator-internal.log & - ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.package='org.matrix.android.sdk.internal' matrix-sdk-android:connectedDebugAndroidTest - - name: Read Results [org.matrix.android.sdk.internal] - if: always() - id: get-comment-body-internal - run: python3 ./tools/ci/render_test_output.py internal ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml - - name: Remove adb logcat - if: always() - run: pkill -9 adb - # package: org.matrix.android.sdk.ordering - - name: Run integration tests for Matrix SDK [org.matrix.android.sdk.ordering] API[${{ matrix.api-level }}] - if: always() - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86 - profile: Nexus 5X - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - emulator-build: 7425822 - script: | - adb root - adb logcat -c - touch emulator-ordering.log - chmod 777 emulator-ordering.log - adb logcat >> emulator-ordering.log & - ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.package='org.matrix.android.sdk.ordering' matrix-sdk-android:connectedDebugAndroidTest - - name: Read Results [org.matrix.android.sdk.ordering] - if: always() - id: get-comment-body-ordering - run: python3 ./tools/ci/render_test_output.py ordering ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml - - name: Remove adb logcat - if: always() - run: pkill -9 adb - # package: class PermalinkParserTest - - name: Run integration tests for Matrix SDK class [org.matrix.android.sdk.PermalinkParserTest] API[${{ matrix.api-level }}] - if: always() - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86 - profile: Nexus 5X - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - emulator-build: 7425822 - script: | - adb root - adb logcat -c - touch emulator-permalink.log - chmod 777 emulator-permalink.log - adb logcat >> emulator-permalink.log & - ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.class='org.matrix.android.sdk.PermalinkParserTest' matrix-sdk-android:connectedDebugAndroidTest - - name: Read Results [org.matrix.android.sdk.PermalinkParserTest] - if: always() - id: get-comment-body-permalink - run: python3 ./tools/ci/render_test_output.py permalink ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml - - name: Remove adb logcat - if: always() - run: pkill -9 adb - # package: class PermalinkParserTest - - name: Find Comment - if: always() && github.event_name == 'pull_request' - uses: peter-evans/find-comment@v2 - id: fc - with: - issue-number: ${{ github.event.pull_request.number }} - comment-author: 'github-actions[bot]' - body-includes: Integration Tests Results - - name: Publish results to PR - if: always() && github.event_name == 'pull_request' - uses: peter-evans/create-or-update-comment@v2 - with: - comment-id: ${{ steps.fc.outputs.comment-id }} - issue-number: ${{ github.event.pull_request.number }} - body: | - ### Matrix SDK - ## Integration Tests Results: - - `[org.matrix.android.sdk.session]`
${{ steps.get-comment-body-session.outputs.session }} - - `[org.matrix.android.sdk.account]`
${{ steps.get-comment-body-account.outputs.account }} - - `[org.matrix.android.sdk.internal]`
${{ steps.get-comment-body-internal.outputs.internal }} - - `[org.matrix.android.sdk.ordering]`
${{ steps.get-comment-body-ordering.outputs.ordering }} - - `[org.matrix.android.sdk.PermalinkParserTest]`
${{ steps.get-comment-body-permalink.outputs.permalink }} - edit-mode: replace - - name: Upload Test Report Log - uses: actions/upload-artifact@v3 - if: always() - with: - name: integrationtest-error-results - path: | - emulator-permalink.log - emulator-internal.log - emulator-ordering.log - emulator-account.log - emulator-session.log - ui-tests: name: UI Tests (Synapse) needs: should-i-run @@ -282,42 +88,13 @@ jobs: emulator.log failure_screenshots/ - codecov-units: - name: Unit tests with code coverage - needs: should-i-run - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: '11' - - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - run: ./gradlew allCodeCoverageReport $CI_GRADLE_ARG_PROPERTIES - - name: Upload Codecov data - uses: actions/upload-artifact@v3 - if: always() - with: - name: codecov-xml - path: | - build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml - # Notify the channel about delayed failures notify: name: Notify matrix runs-on: ubuntu-latest needs: - should-i-run - - integration-tests - ui-tests - - codecov-units if: always() && (needs.should-i-run.result == 'success' ) && ((needs.codecov-units.result != 'success' ) || (needs.ui-tests.result != 'success') || (needs.integration-tests.result != 'success')) # No concurrency required, runs every time on a schedule. steps: diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml deleted file mode 100644 index 6809751d91..0000000000 --- a/.github/workflows/sonarqube.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: Sonarqube nightly - -on: - schedule: - - cron: '0 20 * * *' - -# Enrich gradle.properties for CI/CD -env: - CI_GRADLE_ARG_PROPERTIES: > - -Porg.gradle.jvmargs=-Xmx4g - -Porg.gradle.parallel=false -jobs: - codecov-units: - name: Unit tests with code coverage - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: '11' - - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - run: ./gradlew allCodeCoverageReport $CI_GRADLE_ARG_PROPERTIES - - name: Upload Codecov data - uses: actions/upload-artifact@v3 - if: always() - with: - name: codecov-xml - path: | - build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml - - sonarqube: - name: Sonarqube upload - runs-on: ubuntu-latest - needs: - - codecov-units - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: '11' - - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - uses: actions/download-artifact@v3 - with: - name: codecov-xml # will restore to allCodeCoverageReport.xml by default; we restore to the same location in following tasks - - run: mkdir -p build/reports/jacoco/allCodeCoverageReport/ - - run: mv allCodeCoverageReport.xml build/reports/jacoco/allCodeCoverageReport/ - - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES - env: - ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }} - -# Notify the channel about sonarqube failures - notify: - name: Notify matrix - runs-on: ubuntu-latest - needs: - - sonarqube - - codecov-units - if: always() && (needs.sonarqube.result != 'success' || needs.codecov-units.result != 'success') - steps: - - uses: michaelkaye/matrix-hookshot-action@v1.0.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - hookshot_url: ${{ secrets.ELEMENT_ANDROID_HOOKSHOT_URL }} - text_template: "Sonarqube run (on ${{ github.ref }}): {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" - html_template: "Sonarqube run (on ${{ github.ref }}): {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3e8de8979c..5959fe9bb3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,73 +12,98 @@ env: -Porg.gradle.parallel=false jobs: - # Build Android Tests - build-android-tests: - name: Build Android Tests - runs-on: ubuntu-latest - concurrency: - group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('build-android-tests-{0}', github.ref) }} - cancel-in-progress: true - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: 11 - - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Build Android Tests - run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace - - unit-tests: - name: Run Unit Tests - runs-on: ubuntu-latest + tests: + name: Runs all tests + runs-on: macos-latest # for the emulator # Allow all jobs on main and develop. Just one per PR. concurrency: group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }} cancel-in-progress: true steps: - uses: actions/checkout@v3 - - uses: actions/cache@v3 with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Run unit tests - run: ./gradlew clean test $CI_GRADLE_ARG_PROPERTIES --stacktrace + fetch-depth: 0 + - uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: '11' + - uses: gradle/gradle-build-action@v2 + - uses: actions/setup-python@v3 + with: + python-version: 3.8 + - uses: michaelkaye/setup-matrix-synapse@v1.0.3 + with: + uploadLogs: true + httpPort: 8080 + disableRateLimiting: true + public_baseurl: "http://10.0.2.2:8080/" + - name: Run all the codecoverage tests at once + id: tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 28 + arch: x86 + profile: Nexus 5X + force-avd-creation: false + 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 --stacktrace $CI_GRADLE_ARG_PROPERTIES + - name: Run all the codecoverage tests at once (retry if emulator failed) + uses: reactivecircus/android-emulator-runner@v2 + if: always() && steps.tests.outcome == 'failure' # don't run if previous step succeeded. + with: + api-level: 28 + arch: x86 + profile: Nexus 5X + force-avd-creation: false + 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 --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: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }} + - name: Format unit test results if: always() run: python3 ./tools/ci/render_test_output.py unit ./**/build/test-results/**/*.xml - - name: Publish Unit Test Results - uses: EnricoMi/publish-unit-test-result-action@v1 - if: always() && - github.event.sender.login != 'dependabot[bot]' && - ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository ) - with: - files: ./**/build/test-results/**/*.xml -# Notify the channel about runs against develop or main that have failures, as PRs should have caught these first. - notify: - runs-on: ubuntu-latest - needs: - - unit-tests - - build-android-tests - if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' ) && failure() }} - steps: - - uses: michaelkaye/matrix-hookshot-action@v0.3.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }} - matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }} - text_template: "Build is broken for ${{ github.ref }}: {{#each job_statuses }}{{#with this }}{{#if completed }}{{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" - html_template: "Build is broken for ${{ github.ref }}: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion }} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" +# can't be run on macos due to containers. +# - name: Publish Unit Test Results +# uses: EnricoMi/publish-unit-test-result-action@v1 +# if: always() && +# github.event.sender.login != 'dependabot[bot]' && +# ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository ) +# with: +# files: ./**/build/test-results/**/*.xml + +# Unneeded as part of the test suite above, kept around in case we want to re-enable them. +# +# # Build Android Tests +# build-android-tests: +# name: Build Android Tests +# runs-on: ubuntu-latest +# concurrency: +# group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('build-android-tests-{0}', github.ref) }} +# cancel-in-progress: true +# steps: +# - uses: actions/checkout@v3 +# - uses: actions/setup-java@v3 +# with: +# distribution: 'adopt' +# java-version: 11 +# - uses: actions/cache@v3 +# with: +# path: | +# ~/.gradle/caches +# ~/.gradle/wrapper +# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} +# restore-keys: | +# ${{ runner.os }}-gradle- +# - name: Build Android Tests +# run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace diff --git a/CHANGES.md b/CHANGES.md index b0203c39e9..166453dfad 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +Changes in Element 1.4.19 (2022-06-07) +====================================== + +Bugfixes 🐛 +---------- + - Fix | performance regression on roomlist + proper display of space parents in explore rooms. ([#6233](https://github.com/vector-im/element-android/issues/6233)) + + Changes in Element v1.4.18 (2022-05-31) ======================================= diff --git a/README.md b/README.md index 54dfb7b288..7acb5aa638 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ [![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop) [![Weblate](https://translate.element.io/widgets/element-android/-/svg-badge.svg)](https://translate.element.io/engage/element-android/?utm_source=widget) [![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org) -[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=alert_status)](https://sonarcloud.io/dashboard?id=im.vector.app.android) -[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=im.vector.app.android) -[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=bugs)](https://sonarcloud.io/dashboard?id=im.vector.app.android) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-android&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vector-im_element-android) +[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-android&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=vector-im_element-android) +[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-android&metric=bugs)](https://sonarcloud.io/summary/new_code?id=vector-im_element-android) # Element Android diff --git a/build.gradle b/build.gradle index 635db83e14..03e175927c 100644 --- a/build.gradle +++ b/build.gradle @@ -180,8 +180,8 @@ apply plugin: 'org.sonarqube' sonarqube { properties { - property "sonar.projectName", "Element-Android" - property "sonar.projectKey", "im.vector.app.android" + property "sonar.projectName", "element-android" + property "sonar.projectKey", "vector-im_element-android" property "sonar.host.url", "https://sonarcloud.io" property "sonar.projectVersion", project(":vector").android.defaultConfig.versionName property "sonar.sourceEncoding", "UTF-8" @@ -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/allCodeCoverageReport/allCodeCoverageReport.xml" + property "sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/jacoco/theCodeCoverageReport/theCodeCoverageReport.xml" property "sonar.login", project.hasProperty("SONAR_LOGIN") ? SONAR_LOGIN : "invalid" } } diff --git a/changelog.d/5285.wip b/changelog.d/5285.wip new file mode 100644 index 0000000000..1dd68597be --- /dev/null +++ b/changelog.d/5285.wip @@ -0,0 +1 @@ +FTUE - Adds Sign Up tracking diff --git a/changelog.d/6017.misc b/changelog.d/6017.misc new file mode 100644 index 0000000000..2597f2d796 --- /dev/null +++ b/changelog.d/6017.misc @@ -0,0 +1 @@ +Adds support for parsing homeserver versions without a patch number diff --git a/changelog.d/6146.feature b/changelog.d/6146.feature new file mode 100644 index 0000000000..9d1e117ddb --- /dev/null +++ b/changelog.d/6146.feature @@ -0,0 +1 @@ +Allow .well-known configuration to override key sharing mode diff --git a/changelog.d/6169.sdk b/changelog.d/6169.sdk new file mode 100644 index 0000000000..dfee158c3f --- /dev/null +++ b/changelog.d/6169.sdk @@ -0,0 +1 @@ +Allows new passwords to be passed at the point of confirmation when resetting a password diff --git a/changelog.d/6222.bugfix b/changelog.d/6222.bugfix new file mode 100644 index 0000000000..ef430ee024 --- /dev/null +++ b/changelog.d/6222.bugfix @@ -0,0 +1 @@ +Fix StackOverflowError while recording voice message diff --git a/changelog.d/6232.bugfix b/changelog.d/6232.bugfix new file mode 100644 index 0000000000..df04655701 --- /dev/null +++ b/changelog.d/6232.bugfix @@ -0,0 +1 @@ +Text cropped: "Secure backup" diff --git a/coverage.gradle b/coverage.gradle index b62ce0b4a0..fb2352f47f 100644 --- a/coverage.gradle +++ b/coverage.gradle @@ -2,7 +2,10 @@ def excludes = [ ] def initializeReport(report, projects, classExcludes) { projects.each { project -> project.apply plugin: 'jacoco' } - report.executionData { fileTree(rootProject.rootDir.absolutePath).include("**/build/jacoco/*.exec") } + report.executionData { fileTree(rootProject.rootDir.absolutePath).include( + "**/build/outputs/unit_test_code_coverage/**/*.exec", + "**/build/outputs/code_coverage/**/coverage.ec" + ) } report.reports { xml.enabled true @@ -18,11 +21,13 @@ 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 @@ -43,13 +48,17 @@ def collectProjects(predicate) { return subprojects.findAll { it.buildFile.isFile() && predicate(it) } } -task allCodeCoverageReport(type: JacocoReport) { +task theCodeCoverageReport(type: JacocoReport) { outputs.upToDateWhen { false } rootProject.apply plugin: 'jacoco' - // to limit projects in a specific report, add - // def excludedProjects = [ ... ] - // def projects = collectProjects { !excludedProjects.contains(it.name) } - def projects = collectProjects { true } - dependsOn { projects*.test } + tasks.withType(Test) { + jacoco.includeNoLocationClasses = true + } + def projects = collectProjects { ['vector','matrix-sdk-android'].contains(it.name) } + dependsOn { + [':matrix-sdk-android:testDebugUnitTest'] + + [':vector:testGplayDebugUnitTest'] + + [':matrix-sdk-android:connectedDebugAndroidTest'] + } initializeReport(it, projects, excludes) } diff --git a/fastlane/metadata/android/en-US/changelogs/40104190.txt b/fastlane/metadata/android/en-US/changelogs/40104190.txt new file mode 100644 index 0000000000..61db61727a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104190.txt @@ -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 diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 3829063836..fbed8442a9 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -74,6 +74,7 @@ android { buildTypes { debug { + testCoverageEnabled true // 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 diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt index a763766821..f08f0a28ed 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt @@ -19,10 +19,14 @@ package org.matrix.android.sdk import android.content.Context import androidx.test.core.app.ApplicationProvider import org.junit.Rule +import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.test.shared.createTimberTestRule interface InstrumentedTest { + @Rule + fun retryTestRule() = RetryTestRule(3) + @Rule fun timberTestRule() = createTimberTestRule() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt index f5f585a1e0..17c7c28d81 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt @@ -22,6 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -40,6 +41,7 @@ import java.util.UUID @Suppress("SpellCheckingInspection") @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Ignore class AttachmentEncryptionTest { private fun checkDecryption(input: String, encryptedFileInfo: EncryptedFileInfo): String { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt index cd6c146f03..dbc6929e34 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt @@ -22,6 +22,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNull import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -37,6 +38,7 @@ import org.matrix.olm.OlmSession private const val DUMMY_DEVICE_KEY = "DeviceKey" @RunWith(AndroidJUnit4::class) +@Ignore class CryptoStoreTest : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt index 2f2d54b7e3..bddb31fc92 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt @@ -21,6 +21,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -30,6 +31,7 @@ import org.junit.runners.MethodSorters */ @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Ignore class ExportEncryptionTest { @Test diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt index 5fe7376184..e8a474a54a 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt @@ -21,6 +21,7 @@ import org.amshove.kluent.shouldBe import org.junit.Assert import org.junit.Before import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -59,6 +60,7 @@ import kotlin.coroutines.resume */ @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@Ignore class UnwedgingTest : InstrumentedTest { private lateinit var messagesReceivedByBob: List diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt index 05790bfb7d..8cb38ddc87 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt @@ -25,6 +25,7 @@ import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -47,6 +48,7 @@ import kotlin.coroutines.resume @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @LargeTest +@Ignore class XSigningTest : InstrumentedTest { @Test diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt index b16e4b82eb..994a055e5a 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt @@ -25,6 +25,7 @@ import org.amshove.kluent.internal.assertEquals import org.junit.Assert import org.junit.Assert.assertNull import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -50,6 +51,7 @@ import org.matrix.android.sdk.mustFail @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest +@Ignore class KeyShareTests : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt index 0aac4297e4..ae420a09b3 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt @@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import org.junit.Assert import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -46,6 +47,7 @@ import org.matrix.android.sdk.mustFail @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest +@Ignore class WithHeldTests : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt index c6e17e8c44..fb498e0de5 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt @@ -24,6 +24,7 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -55,6 +56,7 @@ import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest +@Ignore class KeysBackupTest : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt index bd0f9d1641..c2e74abc59 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt @@ -52,6 +52,7 @@ import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Ignore class SASTest : InstrumentedTest { @Test diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt index 265b7c8c4c..3f22906965 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.verification.qrcode import androidx.test.ext.junit.runners.AndroidJUnit4 import org.amshove.kluent.shouldBe import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -41,6 +42,7 @@ import kotlin.coroutines.resume @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@Ignore class VerificationTest : InstrumentedTest { data class ExpectedResult( diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt index 7ed0be927c..59b3b14532 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt @@ -20,6 +20,7 @@ import androidx.test.filters.LargeTest import kotlinx.coroutines.runBlocking import org.amshove.kluent.internal.assertEquals import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -38,6 +39,7 @@ import org.matrix.android.sdk.common.TestConstants @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest +@Ignore class TimelineSimpleBackPaginationTest : InstrumentedTest { @Test diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt index 0d8a9058a2..0cc0ef57c4 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -98,6 +99,7 @@ class SpaceCreationTest : InstrumentedTest { } @Test + @Ignore fun testJoinSimplePublicSpace() = runSessionTest(context()) { commonTestHelper -> val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true)) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt index f5670875c2..5b8d2328c7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt @@ -65,16 +65,14 @@ interface LoginWizard { * [resetPasswordMailConfirmed] is successfully called. * * @param email an email previously associated to the account the user wants the password to be reset. - * @param newPassword the desired new password */ - suspend fun resetPassword( - email: String, - newPassword: String - ) + suspend fun resetPassword(email: String) /** * 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 */ - suspend fun resetPasswordMailConfirmed() + suspend fun resetPasswordMailConfirmed(newPassword: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt index a376ede0d4..9f979098f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt @@ -27,3 +27,8 @@ fun CharSequence.ensurePrefix(prefix: CharSequence): CharSequence { * Append a new line and then the provided string. */ fun StringBuilder.appendNl(str: String) = append("\n").append(str) + +/** + * Returns null if the string is empty. + */ +fun String.ensureNotEmpty() = ifEmpty { null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 0a495f3552..5dfb8961e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -226,12 +226,19 @@ interface RoomService { ): LiveData> /** - * TODO Doc. + * Get's a live paged list from a filter that can be dynamically updated. + * + * @param queryParams The filter to use + * @param pagedListConfig The paged list configuration (page size, initial load, prefetch distance...) + * @param sortOrder defines how to sort the results + * @param getFlattenParents When true, the list of known parents and grand parents summaries will be resolved. + * This can have significant impact on performance, better be used only on manageable list (filtered by displayName, ..). */ fun getFilteredPagedRoomSummariesLive( queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config = defaultPagedListConfig, - sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY + sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY, + getFlattenParents: Boolean = false, ): UpdatableLivePageResult /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt index 0a189f86e6..20b056f1c7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt @@ -103,7 +103,7 @@ internal class DefaultLoginWizard( return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) } - override suspend fun resetPassword(email: String, newPassword: String) { + override suspend fun resetPassword(email: String) { val param = RegisterAddThreePidTask.Params( RegisterThreePid.Email(email), pendingSessionData.clientSecret, @@ -117,18 +117,16 @@ internal class DefaultLoginWizard( authAPI.resetPassword(AddThreePidRegistrationParams.from(param)) } - pendingSessionData = pendingSessionData.copy(resetPasswordData = ResetPasswordData(newPassword, result)) + pendingSessionData = pendingSessionData.copy(resetPasswordData = ResetPasswordData(result)) .also { pendingSessionStore.savePendingSessionData(it) } } - override suspend fun resetPasswordMailConfirmed() { - val safeResetPasswordData = pendingSessionData.resetPasswordData - ?: throw IllegalStateException("developer error, no reset password in progress") - + override suspend fun resetPasswordMailConfirmed(newPassword: String) { + val resetPasswordData = pendingSessionData.resetPasswordData ?: throw IllegalStateException("Developer error - Must call resetPassword first") val param = ResetPasswordMailConfirmed.create( pendingSessionData.clientSecret, - safeResetPasswordData.addThreePidRegistrationResponse.sid, - safeResetPasswordData.newPassword + resetPasswordData.addThreePidRegistrationResponse.sid, + newPassword ) executeRequest(null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt index a65ec38d6d..87a7b346dc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt @@ -24,6 +24,5 @@ import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistration */ @JsonClass(generateAdapter = true) internal data class ResetPasswordData( - val newPassword: String, val addThreePidRegistrationResponse: AddThreePidRegistrationResponse ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt index 9ab6f9d274..cd38b68a85 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.internal.auth.version +import org.matrix.android.sdk.api.extensions.ensureNotEmpty + /** * Values will take the form "rX.Y.Z". * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions @@ -38,14 +40,14 @@ internal data class HomeServerVersion( } companion object { - internal val pattern = Regex("""[r|v](\d+)\.(\d+)\.(\d+)""") + internal val pattern = Regex("""[r|v](\d+)\.(\d+)(?:\.(\d+))?""") internal fun parse(value: String): HomeServerVersion? { val result = pattern.matchEntire(value) ?: return null return HomeServerVersion( major = result.groupValues[1].toInt(), minor = result.groupValues[2].toInt(), - patch = result.groupValues[3].toInt() + patch = result.groupValues.getOrNull(index = 3)?.ensureNotEmpty()?.toInt() ?: 0 ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 7a7804c354..5e6d052443 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -139,9 +139,10 @@ internal class DefaultRoomService @Inject constructor( override fun getFilteredPagedRoomSummariesLive( queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, - sortOrder: RoomSortOrder + sortOrder: RoomSortOrder, + getFlattenParents: Boolean ): UpdatableLivePageResult { - return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder, getFlattenedParents = true) + return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder, getFlattenParents) } override fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersionTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersionTest.kt new file mode 100644 index 0000000000..413af9c434 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersionTest.kt @@ -0,0 +1,57 @@ +/* + * 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.internal.auth.version + +import org.amshove.kluent.internal.assertEquals +import org.junit.Test + +class HomeServerVersionTest { + + @Test + fun `given a semantic version, when parsing, then converts to home server version`() { + val supportedVersions = listOf( + case("1.5", expected = aVersion(1, 5, 0)), + case("0.5.1", expected = aVersion(0, 5, 1)), + case("1.0.0", expected = aVersion(1, 0, 0)), + case("1.10.3", expected = aVersion(1, 10, 3)), + ).withPrefixes("v", "r") + + val unsupportedVersions = listOf( + case("v-1.5.1", expected = null), + case("1.4.", expected = null), + case("1.5.1.", expected = null), + case("r1", expected = null), + case("a", expected = null), + case("1a.2b.3c", expected = null), + case("r", expected = null), + ) + + (supportedVersions + unsupportedVersions).forEach { (input, expected) -> + val result = HomeServerVersion.parse(input) + + assertEquals(expected, result, "Expected $input to be $expected but got $result") + } + } +} + +private fun aVersion(major: Int, minor: Int, patch: Int) = HomeServerVersion(major, minor, patch) +private fun case(input: String, expected: HomeServerVersion?) = Case(input, expected) + +private fun List.withPrefixes(vararg prefixes: String) = map { case -> + prefixes.map { prefix -> case.copy(input = "$prefix${case.input}") } +}.flatten() + +private data class Case(val input: String, val expected: HomeServerVersion?) diff --git a/vector/build.gradle b/vector/build.gradle index dea95b607a..751d15122b 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -244,6 +244,7 @@ android { buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false" signingConfig signingConfigs.debug + testCoverageEnabled true } release { diff --git a/vector/src/main/java/im/vector/app/features/analytics/extensions/SignUpExt.kt b/vector/src/main/java/im/vector/app/features/analytics/extensions/SignUpExt.kt new file mode 100644 index 0000000000..e63aafbfc4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/analytics/extensions/SignUpExt.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.analytics.extensions + +import im.vector.app.features.analytics.plan.Signup +import im.vector.app.features.onboarding.AuthenticationDescription + +fun AuthenticationDescription.AuthenticationType.toAnalyticsType() = when (this) { + AuthenticationDescription.AuthenticationType.Password -> Signup.AuthenticationType.Password + AuthenticationDescription.AuthenticationType.Apple -> Signup.AuthenticationType.Apple + AuthenticationDescription.AuthenticationType.Facebook -> Signup.AuthenticationType.Facebook + AuthenticationDescription.AuthenticationType.GitHub -> Signup.AuthenticationType.GitHub + AuthenticationDescription.AuthenticationType.GitLab -> Signup.AuthenticationType.GitLab + AuthenticationDescription.AuthenticationType.Google -> Signup.AuthenticationType.Google + AuthenticationDescription.AuthenticationType.SSO -> Signup.AuthenticationType.SSO + AuthenticationDescription.AuthenticationType.Other -> Signup.AuthenticationType.Other +} diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysrequest/OutboundSessionKeySharingStrategy.kt b/vector/src/main/java/im/vector/app/features/crypto/keysrequest/OutboundSessionKeySharingStrategy.kt index 2018a5b053..5396ce908e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysrequest/OutboundSessionKeySharingStrategy.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysrequest/OutboundSessionKeySharingStrategy.kt @@ -19,6 +19,7 @@ package im.vector.app.features.crypto.keysrequest enum class OutboundSessionKeySharingStrategy { /** * Keys will be sent for the first time when the first message is sent. + * This is handled by the Matrix SDK so there's no need to do it in Vector. */ WhenSendingEvent, diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index af3be0d3d5..244b77eefe 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -56,6 +56,7 @@ import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.navigation.Navigator import im.vector.app.features.notifications.NotificationDrawerManager +import im.vector.app.features.onboarding.AuthenticationDescription import im.vector.app.features.permalink.NavigationInterceptor import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.permalink.PermalinkHandler.Companion.MATRIX_TO_CUSTOM_SCHEME_URL_BASE @@ -91,7 +92,7 @@ import javax.inject.Inject @Parcelize data class HomeActivityArgs( val clearNotification: Boolean, - val accountCreation: Boolean, + val authenticationDescription: AuthenticationDescription? = null, val hasExistingSession: Boolean = false, val inviteNotificationRoomId: String? = null ) : Parcelable @@ -612,13 +613,13 @@ class HomeActivity : fun newIntent( context: Context, clearNotification: Boolean = false, - accountCreation: Boolean = false, + authenticationDescription: AuthenticationDescription? = null, existingSession: Boolean = false, inviteNotificationRoomId: String? = null ): Intent { val args = HomeActivityArgs( clearNotification = clearNotification, - accountCreation = accountCreation, + authenticationDescription = authenticationDescription, hasExistingSession = existingSession, inviteNotificationRoomId = inviteNotificationRoomId ) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 2144334790..581e11ac8a 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -28,17 +28,25 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.analytics.AnalyticsTracker +import im.vector.app.features.analytics.extensions.toAnalyticsType +import im.vector.app.features.analytics.plan.Signup import im.vector.app.features.analytics.store.AnalyticsStore import im.vector.app.features.login.ReAuthHelper +import im.vector.app.features.onboarding.AuthenticationDescription import im.vector.app.features.raw.wellknown.ElementWellKnown import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isSecureBackupRequired +import im.vector.app.features.raw.wellknown.withElementWellKnown import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor @@ -72,7 +80,8 @@ class HomeActivityViewModel @AssistedInject constructor( private val reAuthHelper: ReAuthHelper, private val analyticsStore: AnalyticsStore, private val lightweightSettingsStorage: LightweightSettingsStorage, - private val vectorPreferences: VectorPreferences + private val vectorPreferences: VectorPreferences, + private val analyticsTracker: AnalyticsTracker ) : VectorViewModel(initialState) { @AssistedFactory @@ -84,7 +93,7 @@ class HomeActivityViewModel @AssistedInject constructor( override fun initialState(viewModelContext: ViewModelContext): HomeActivityViewState? { val activity: HomeActivity = viewModelContext.activity() val args: HomeActivityArgs? = activity.intent.getParcelableExtra(Mavericks.KEY_ARG) - return args?.let { HomeActivityViewState(accountCreation = it.accountCreation) } + return args?.let { HomeActivityViewState(authenticationDescription = it.authenticationDescription) } ?: super.initialState(viewModelContext) } } @@ -113,9 +122,32 @@ class HomeActivityViewModel @AssistedInject constructor( } } .launchIn(viewModelScope) + + when (val recentAuthentication = initialState.authenticationDescription) { + is AuthenticationDescription.Register -> { + viewModelScope.launch { + analyticsStore.onUserGaveConsent { + analyticsTracker.capture(Signup(authenticationType = recentAuthentication.type.toAnalyticsType())) + } + } + } + AuthenticationDescription.Login -> { + // do nothing + } + null -> { + // do nothing + } + } } } + private suspend fun AnalyticsStore.onUserGaveConsent(action: () -> Unit) { + userConsentFlow + .takeWhile { !it } + .onCompletion { action() } + .collect() + } + private fun cleanupFiles() { // Mitigation: delete all cached decrypted files each time the application is started. activeSessionHolder.getSafeActiveSession()?.fileService()?.clearDecryptedCache() @@ -134,9 +166,8 @@ class HomeActivityViewModel @AssistedInject constructor( .onEach { info -> val isVerified = info.getOrNull()?.isTrusted() ?: false if (!isVerified && onceTrusted) { - viewModelScope.launch(Dispatchers.IO) { - val elementWellKnown = rawService.getElementWellknown(safeActiveSession.sessionParams) - sessionHasBeenUnverified(elementWellKnown) + rawService.withElementWellKnown(viewModelScope, safeActiveSession.sessionParams) { + sessionHasBeenUnverified(it) } } onceTrusted = isVerified @@ -285,7 +316,7 @@ class HomeActivityViewModel @AssistedInject constructor( val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false // In case of account creation, it is already done before - if (initialState.accountCreation) { + if (initialState.authenticationDescription is AuthenticationDescription.Register) { if (isSecureBackupRequired) { _viewEvents.post(HomeActivityViewEvents.StartRecoverySetupFlow) } else { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt index 45fe04fc61..95ab75549f 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt @@ -17,9 +17,10 @@ package im.vector.app.features.home import com.airbnb.mvrx.MavericksState +import im.vector.app.features.onboarding.AuthenticationDescription import org.matrix.android.sdk.api.session.initsync.SyncStatusService data class HomeActivityViewState( val syncStatusServiceStatus: SyncStatusService.Status = SyncStatusService.Status.Idle, - val accountCreation: Boolean = false + val authenticationDescription: AuthenticationDescription? = null ) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 1b630801cc..2a6357e088 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -29,7 +29,6 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.AppStateHandler -import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory @@ -56,6 +55,8 @@ import im.vector.app.features.home.room.typing.TypingHelper import im.vector.app.features.location.LocationSharingServiceConnection import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.powerlevel.PowerLevelsFlowFactory +import im.vector.app.features.raw.wellknown.getOutboundSessionKeySharingStrategyOrDefault +import im.vector.app.features.raw.wellknown.withElementWellKnown import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorDataStore import im.vector.app.features.settings.VectorPreferences @@ -76,6 +77,7 @@ import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.EventType @@ -118,6 +120,7 @@ class TimelineViewModel @AssistedInject constructor( private val vectorDataStore: VectorDataStore, private val stringProvider: StringProvider, private val session: Session, + private val rawService: RawService, private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider, private val stickerPickerActionHandler: StickerPickerActionHandler, private val typingHelper: TypingHelper, @@ -196,8 +199,13 @@ class TimelineViewModel @AssistedInject constructor( chatEffectManager.delegate = this // Ensure to share the outbound session keys with all members - if (OutboundSessionKeySharingStrategy.WhenEnteringRoom == BuildConfig.outboundSessionKeySharingStrategy && room.roomCryptoService().isEncrypted()) { - prepareForEncryption() + if (room.roomCryptoService().isEncrypted()) { + rawService.withElementWellKnown(viewModelScope, session.sessionParams) { + val strategy = it.getOutboundSessionKeySharingStrategyOrDefault() + if (strategy == OutboundSessionKeySharingStrategy.WhenEnteringRoom) { + prepareForEncryption() + } + } } // If the user had already accepted the invitation in the room list @@ -667,10 +675,13 @@ class TimelineViewModel @AssistedInject constructor( private fun handleComposerFocusChange(action: RoomDetailAction.ComposerFocusChange) { // Ensure outbound session keys - if (OutboundSessionKeySharingStrategy.WhenTyping == BuildConfig.outboundSessionKeySharingStrategy && room.roomCryptoService().isEncrypted()) { - if (action.focused) { - // Should we add some rate limit here, or do it only once per model lifecycle? - prepareForEncryption() + if (room.roomCryptoService().isEncrypted()) { + rawService.withElementWellKnown(viewModelScope, session.sessionParams) { + val strategy = it.getOutboundSessionKeySharingStrategyOrDefault() + if (strategy == OutboundSessionKeySharingStrategy.WhenTyping && action.focused) { + // Should we add some rate limit here, or do it only once per model lifecycle? + prepareForEncryption() + } } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt index 8f7ab46191..a8a30349c7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt @@ -71,7 +71,7 @@ class RoomListSectionBuilderGroup( }, { qpm -> val name = stringProvider.getString(R.string.bottom_action_rooms) - val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(qpm) + val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(qpm, getFlattenParents = true) onUpdatable(updatableFilterLivePageResult) val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow() diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index a39da8f473..171b05637a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -332,7 +332,7 @@ class RoomListSectionBuilderSpace( }, { queryParams -> val name = stringProvider.getString(R.string.bottom_action_rooms) - val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(queryParams) + val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(queryParams, getFlattenParents = true) onUpdatable(updatableFilterLivePageResult) val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow() diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index 0023a1249e..f50cec5149 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -207,7 +207,7 @@ class RoomSummaryItemFactory @Inject constructor( private fun getSearchResultSubtitle(roomSummary: RoomSummary): String { val userId = roomSummary.directUserId - val spaceName = roomSummary.spaceParents?.firstOrNull()?.roomSummary?.name + val spaceName = roomSummary.flattenParents.lastOrNull()?.name val canonicalAlias = roomSummary.canonicalAlias return (userId ?: spaceName ?: canonicalAlias).orEmpty() diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt index 15e5efe54c..722133f585 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt @@ -42,6 +42,7 @@ import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.HomeActivity import im.vector.app.features.login.terms.LoginTermsFragment import im.vector.app.features.login.terms.LoginTermsFragmentArgument +import im.vector.app.features.onboarding.AuthenticationDescription import im.vector.app.features.pin.UnlockedActivity import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.Stage @@ -218,10 +219,8 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA // change the screen name analyticsScreenName = MobileScreen.ScreenName.Register } - val intent = HomeActivity.newIntent( - this, - accountCreation = loginViewState.signMode == SignMode.SignUp - ) + val authDescription = inferAuthDescription(loginViewState) + val intent = HomeActivity.newIntent(this, authenticationDescription = authDescription) startActivity(intent) finish() return @@ -231,6 +230,13 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA views.loginLoading.isVisible = loginViewState.isLoading() } + private fun inferAuthDescription(loginViewState: LoginViewState) = when (loginViewState.signMode) { + SignMode.Unknown -> null + SignMode.SignUp -> AuthenticationDescription.Register(type = AuthenticationDescription.AuthenticationType.Other) + SignMode.SignIn -> AuthenticationDescription.Login + SignMode.SignInWithMatrixId -> AuthenticationDescription.Login + } + private fun onWebLoginError(onWebLoginError: LoginViewEvents.OnWebLoginError) { // Pop the backstack supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt index 1d25732ee4..9c598c400b 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.isInvalidPassword @@ -202,11 +203,11 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment?, mode: SocialLoginButtonsView.Mode, listener: (String?) -> Unit) { +fun SocialLoginButtonsView.render(ssoProviders: List?, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) { this.mode = mode this.ssoIdentityProviders = ssoProviders?.sorted() this.listener = SocialLoginButtonsView.InteractionListener { listener(it) } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt index f9917a4c31..a7c4b25344 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt @@ -35,6 +35,7 @@ import im.vector.app.features.login.SocialLoginButtonsView import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -96,11 +97,11 @@ class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragm views.loginSocialLoginContainer.isVisible = true views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted() views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { - override fun onProviderSelected(id: String?) { + override fun onProviderSelected(provider: SsoIdentityProvider?) { loginViewModel.getSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - providerId = id + providerId = provider?.id ) ?.let { openInCustomTab(it) } } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt index 064889876b..cc143b9255 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.isInvalidPassword @@ -123,11 +124,11 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2 { setState { copy( - resetPasswordEmail = null + resetPasswordEmail = null, + resetPasswordNewPassword = null ) } } @@ -443,7 +444,7 @@ class LoginViewModel2 @AssistedInject constructor( currentJob = viewModelScope.launch { try { - safeLoginWizard.resetPassword(action.email, action.newPassword) + safeLoginWizard.resetPassword(action.email) } catch (failure: Throwable) { _viewEvents.post(LoginViewEvents2.Failure(failure)) setState { copy(isLoading = false) } @@ -453,7 +454,8 @@ class LoginViewModel2 @AssistedInject constructor( setState { copy( isLoading = false, - resetPasswordEmail = action.email + resetPasswordEmail = action.email, + resetPasswordNewPassword = action.newPassword ) } @@ -472,7 +474,8 @@ class LoginViewModel2 @AssistedInject constructor( currentJob = viewModelScope.launch { try { - safeLoginWizard.resetPasswordMailConfirmed() + val state = awaitState() + safeLoginWizard.resetPasswordMailConfirmed(state.resetPasswordNewPassword!!) } catch (failure: Throwable) { _viewEvents.post(LoginViewEvents2.Failure(failure)) setState { copy(isLoading = false) } @@ -481,7 +484,8 @@ class LoginViewModel2 @AssistedInject constructor( setState { copy( isLoading = false, - resetPasswordEmail = null + resetPasswordEmail = null, + resetPasswordNewPassword = null ) } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt index 276d1111bb..8405381c4f 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt @@ -36,6 +36,8 @@ data class LoginViewState2( @PersistState val resetPasswordEmail: String? = null, @PersistState + val resetPasswordNewPassword: String? = null, + @PersistState val homeServerUrlFromUser: String? = null, // Can be modified after a Wellknown request diff --git a/vector/src/main/java/im/vector/app/features/onboarding/AuthenticationDescription.kt b/vector/src/main/java/im/vector/app/features/onboarding/AuthenticationDescription.kt new file mode 100644 index 0000000000..3672b8eef3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/AuthenticationDescription.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.onboarding + +import android.os.Parcelable +import im.vector.app.features.onboarding.AuthenticationDescription.AuthenticationType +import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider + +sealed interface AuthenticationDescription : Parcelable { + @Parcelize + object Login : AuthenticationDescription + + @Parcelize + data class Register(val type: AuthenticationType) : AuthenticationDescription + + enum class AuthenticationType { + Password, + Apple, + Facebook, + GitHub, + GitLab, + Google, + SSO, + Other + } +} + +fun SsoIdentityProvider?.toAuthenticationType() = when (this?.brand) { + SsoIdentityProvider.BRAND_GOOGLE -> AuthenticationType.Google + SsoIdentityProvider.BRAND_GITHUB -> AuthenticationType.GitHub + SsoIdentityProvider.BRAND_APPLE -> AuthenticationType.Apple + SsoIdentityProvider.BRAND_FACEBOOK -> AuthenticationType.Facebook + SsoIdentityProvider.BRAND_GITLAB -> AuthenticationType.GitLab + SsoIdentityProvider.BRAND_TWITTER -> AuthenticationType.SSO + null -> AuthenticationType.SSO + else -> AuthenticationType.SSO +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt index b6013b70b7..0d7c83e360 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt @@ -276,7 +276,7 @@ class Login2Variant( is LoginViewEvents2.OnLoginModeNotSupported -> onLoginModeNotSupported(event.supportedTypes) is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event) - is LoginViewEvents2.Finish -> terminate(true) + is LoginViewEvents2.Finish -> terminate() is LoginViewEvents2.CancelRegistration -> handleCancelRegistration() } } @@ -296,14 +296,13 @@ class Login2Variant( option = commonOption ) } else { - terminate(false) + terminate() } } - private fun terminate(newAccount: Boolean) { + private fun terminate() { val intent = HomeActivity.newIntent( - activity, - accountCreation = newAccount + activity ) activity.startActivity(intent) activity.finish() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 6e59ef03f5..61877a5f47 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -54,6 +54,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.RegistrationWizard @@ -127,7 +128,7 @@ class OnboardingViewModel @AssistedInject constructor( val isRegistrationStarted: Boolean get() = authenticationService.isRegistrationStarted() - private val loginWizard: LoginWizard? + private val loginWizard: LoginWizard get() = authenticationService.getLoginWizard() private var loginConfig: LoginConfig? = null @@ -245,21 +246,15 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleLoginWithToken(action: OnboardingAction.LoginWithToken) { val safeLoginWizard = loginWizard + setState { copy(isLoading = true) } - if (safeLoginWizard == null) { - setState { copy(isLoading = false) } - _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) - } else { - setState { copy(isLoading = true) } - - currentJob = viewModelScope.launch { - try { - val result = safeLoginWizard.loginWithToken(action.loginToken) - onSessionCreated(result, isAccountCreated = false) - } catch (failure: Throwable) { - setState { copy(isLoading = false) } - _viewEvents.post(OnboardingViewEvents.Failure(failure)) - } + currentJob = viewModelScope.launch { + try { + val result = safeLoginWizard.loginWithToken(action.loginToken) + onSessionCreated(result, authenticationDescription = AuthenticationDescription.Login) + } catch (failure: Throwable) { + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) } } } @@ -289,7 +284,11 @@ class OnboardingViewModel @AssistedInject constructor( // do nothing } else -> when (it) { - is RegistrationResult.Complete -> onSessionCreated(it.session, isAccountCreated = true) + is RegistrationResult.Complete -> onSessionCreated( + it.session, + authenticationDescription = awaitState().selectedAuthenticationState.description + ?: AuthenticationDescription.Register(AuthenticationDescription.AuthenticationType.Other) + ) is RegistrationResult.NextStep -> onFlowResponse(it.flowResult, onNextRegistrationStepAction) is RegistrationResult.SendEmailSuccess -> _viewEvents.post(OnboardingViewEvents.OnSendEmailSuccess(it.email)) is RegistrationResult.Error -> _viewEvents.post(OnboardingViewEvents.Failure(it.cause)) @@ -319,6 +318,10 @@ class OnboardingViewModel @AssistedInject constructor( private fun OnboardingViewState.hasSelectedMatrixOrg() = selectedHomeserver.userFacingUrl == matrixOrgUrl private fun handleRegisterWith(action: AuthenticateAction.Register) { + setState { + val authDescription = AuthenticationDescription.Register(AuthenticationDescription.AuthenticationType.Password) + copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription)) + } reAuthHelper.data = action.password handleRegisterAction( RegisterAction.CreateAccount( @@ -368,7 +371,7 @@ class OnboardingViewModel @AssistedInject constructor( setState { copy( isLoading = false, - resetPasswordEmail = null + resetState = ResetState() ) } } @@ -438,59 +441,52 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleResetPassword(action: OnboardingAction.ResetPassword) { val safeLoginWizard = loginWizard - - if (safeLoginWizard == null) { - setState { copy(isLoading = false) } - _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) - } else { - setState { copy(isLoading = true) } - - currentJob = viewModelScope.launch { - try { - safeLoginWizard.resetPassword(action.email, action.newPassword) - } catch (failure: Throwable) { - setState { copy(isLoading = false) } - _viewEvents.post(OnboardingViewEvents.Failure(failure)) - return@launch - } - - setState { - copy( - isLoading = false, - resetPasswordEmail = action.email - ) - } - - _viewEvents.post(OnboardingViewEvents.OnResetPasswordSendThreePidDone) - } + setState { copy(isLoading = true) } + currentJob = viewModelScope.launch { + runCatching { safeLoginWizard.resetPassword(action.email) }.fold( + onSuccess = { + setState { + copy( + isLoading = false, + resetState = ResetState(email = action.email, newPassword = action.newPassword) + ) + } + _viewEvents.post(OnboardingViewEvents.OnResetPasswordSendThreePidDone) + }, + onFailure = { + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(it)) + } + ) } } private fun handleResetPasswordMailConfirmed() { - val safeLoginWizard = loginWizard - - if (safeLoginWizard == null) { - setState { copy(isLoading = false) } - _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) - } else { - setState { copy(isLoading = false) } - - currentJob = viewModelScope.launch { - try { - safeLoginWizard.resetPasswordMailConfirmed() - } catch (failure: Throwable) { + setState { copy(isLoading = true) } + currentJob = viewModelScope.launch { + val resetState = awaitState().resetState + when (val newPassword = resetState.newPassword) { + null -> { setState { copy(isLoading = false) } - _viewEvents.post(OnboardingViewEvents.Failure(failure)) - return@launch + _viewEvents.post(OnboardingViewEvents.Failure(IllegalStateException("Developer error - No new password has been set"))) } - setState { - copy( - isLoading = false, - resetPasswordEmail = null + else -> { + runCatching { loginWizard.resetPasswordMailConfirmed(newPassword) }.fold( + onSuccess = { + setState { + copy( + isLoading = false, + resetState = ResetState() + ) + } + _viewEvents.post(OnboardingViewEvents.OnResetPasswordMailConfirmationSuccess) + }, + onFailure = { + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(it)) + } ) } - - _viewEvents.post(OnboardingViewEvents.OnResetPasswordMailConfirmationSuccess) } } } @@ -499,7 +495,7 @@ class OnboardingViewModel @AssistedInject constructor( setState { copy(isLoading = true) } currentJob = viewModelScope.launch { directLoginUseCase.execute(action, homeServerConnectionConfig).fold( - onSuccess = { onSessionCreated(it, isAccountCreated = false) }, + onSuccess = { onSessionCreated(it, authenticationDescription = AuthenticationDescription.Login) }, onFailure = { setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.Failure(it)) @@ -510,25 +506,19 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleLogin(action: AuthenticateAction.Login) { val safeLoginWizard = loginWizard - - if (safeLoginWizard == null) { - setState { copy(isLoading = false) } - _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) - } else { - setState { copy(isLoading = true) } - currentJob = viewModelScope.launch { - try { - val result = safeLoginWizard.login( - action.username, - action.password, - action.initialDeviceName - ) - reAuthHelper.data = action.password - onSessionCreated(result, isAccountCreated = false) - } catch (failure: Throwable) { - setState { copy(isLoading = false) } - _viewEvents.post(OnboardingViewEvents.Failure(failure)) - } + setState { copy(isLoading = true) } + currentJob = viewModelScope.launch { + try { + val result = safeLoginWizard.login( + action.username, + action.password, + action.initialDeviceName + ) + reAuthHelper.data = action.password + onSessionCreated(result, authenticationDescription = AuthenticationDescription.Login) + } catch (failure: Throwable) { + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) } } } @@ -553,7 +543,7 @@ class OnboardingViewModel @AssistedInject constructor( internalRegisterAction(RegisterAction.RegisterDummy, onNextRegistrationStepAction) } - private suspend fun onSessionCreated(session: Session, isAccountCreated: Boolean) { + private suspend fun onSessionCreated(session: Session, authenticationDescription: AuthenticationDescription) { val state = awaitState() state.useCase?.let { useCase -> session.vectorStore(applicationContext).setUseCase(useCase) @@ -564,15 +554,15 @@ class OnboardingViewModel @AssistedInject constructor( authenticationService.reset() session.configureAndStart(applicationContext) - when (isAccountCreated) { - true -> { + when (authenticationDescription) { + is AuthenticationDescription.Register -> { val personalizationState = createPersonalizationState(session, state) setState { copy(isLoading = false, personalizationState = personalizationState) } _viewEvents.post(OnboardingViewEvents.OnAccountCreated) } - false -> { + AuthenticationDescription.Login -> { setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn) } @@ -603,7 +593,7 @@ class OnboardingViewModel @AssistedInject constructor( currentJob = viewModelScope.launch { try { val result = authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials) - onSessionCreated(result, isAccountCreated = false) + onSessionCreated(result, authenticationDescription = AuthenticationDescription.Login) } catch (failure: Throwable) { setState { copy(isLoading = false) } } @@ -745,8 +735,12 @@ class OnboardingViewModel @AssistedInject constructor( return loginConfig?.homeServerUrl } - fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? { - return authenticationService.getSsoUrl(redirectUrl, deviceId, providerId) + fun fetchSsoUrl(redirectUrl: String, deviceId: String?, provider: SsoIdentityProvider?): String? { + setState { + val authDescription = AuthenticationDescription.Register(provider.toAuthenticationType()) + copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription)) + } + return authenticationService.getSsoUrl(redirectUrl, deviceId, provider?.id) } fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt index 442a0a7df1..268b1e7d49 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt @@ -39,7 +39,7 @@ data class OnboardingViewState( @PersistState val signMode: SignMode = SignMode.Unknown, @PersistState - val resetPasswordEmail: String? = null, + val resetState: ResetState = ResetState(), // For SSO session recovery @PersistState @@ -51,6 +51,9 @@ data class OnboardingViewState( @PersistState val selectedHomeserver: SelectedHomeserverState = SelectedHomeserverState(), + @PersistState + val selectedAuthenticationState: SelectedAuthenticationState = SelectedAuthenticationState(), + @PersistState val personalizationState: PersonalizationState = PersonalizationState() ) : MavericksState @@ -80,3 +83,14 @@ data class PersonalizationState( fun supportsPersonalization() = supportsChangingDisplayName || supportsChangingProfilePicture } + +@Parcelize +data class ResetState( + val email: String? = null, + val newPassword: String? = null, +) : Parcelable + +@Parcelize +data class SelectedAuthenticationState( + val description: AuthenticationDescription? = null, +) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt index cfd860c526..7766523de9 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt @@ -153,7 +153,7 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment // True when email is sent with success to the homeserver - isResetPasswordStarted = state.resetPasswordEmail.isNullOrBlank().not() + isResetPasswordStarted = state.resetState.email.isNullOrBlank().not() updateWithState(state) } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt index a032181e4d..1b764f4ce6 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt @@ -90,10 +90,10 @@ abstract class AbstractSSOFtueAuthFragment : AbstractFtueAuthF withState(viewModel) { state -> if (state.selectedHomeserver.preferredLoginMode.hasSso() && state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders().isNullOrEmpty()) { // in this case we can prefetch (not other cases for privacy concerns) - viewModel.getSsoUrl( + viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - providerId = null + provider = null ) ?.let { prefetchUrl(it) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt index 91a83b432a..10b9cf4683 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt @@ -131,10 +131,10 @@ class FtueAuthCombinedLoginFragment @Inject constructor( views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true views.ssoButtonsHeader.isVisible = views.ssoGroup.isVisible && views.loginEntryGroup.isVisible views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id -> - viewModel.getSsoUrl( + viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = deviceId, - providerId = id + provider = id )?.let { openInCustomTab(it) } } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt index ef68fd619e..e19f7837c3 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt @@ -164,11 +164,11 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu private fun renderSsoProviders(deviceId: String?, ssoProviders: List?) { views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true - views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id -> - viewModel.getSsoUrl( + views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider -> + viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = deviceId, - providerId = id + provider = provider )?.let { openInCustomTab(it) } } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt index c9383a2df7..9f551f9f25 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt @@ -45,6 +45,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidUsername import org.matrix.android.sdk.api.failure.isLoginEmailUnknown @@ -216,11 +217,11 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< views.loginSocialLoginContainer.isVisible = true views.loginSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders?.sorted() views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { - override fun onProviderSelected(id: String?) { - viewModel.getSsoUrl( + override fun onProviderSelected(provider: SsoIdentityProvider?) { + viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - providerId = id + provider = provider ) ?.let { openInCustomTab(it) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt index fd7f14b1cc..76fbae6f40 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt @@ -44,7 +44,7 @@ class FtueAuthResetPasswordMailConfirmationFragment @Inject constructor() : Abst } private fun setupUi(state: OnboardingViewState) { - views.resetPasswordMailConfirmationNotice.text = getString(R.string.login_reset_password_mail_confirmation_notice, state.resetPasswordEmail) + views.resetPasswordMailConfirmationNotice.text = getString(R.string.login_reset_password_mail_confirmation_notice, state.resetState.email) } private fun submit() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt index 902065ef86..6723e48bcc 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt @@ -34,6 +34,7 @@ import im.vector.app.features.login.SocialLoginButtonsView import im.vector.app.features.login.ssoIdentityProviders import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState +import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import javax.inject.Inject /** @@ -81,11 +82,11 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF views.loginSignupSigninSignInSocialLoginContainer.isVisible = true views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders()?.sorted() views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { - override fun onProviderSelected(id: String?) { - viewModel.getSsoUrl( + override fun onProviderSelected(provider: SsoIdentityProvider?) { + viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - providerId = id + provider = provider ) ?.let { openInCustomTab(it) } } @@ -123,10 +124,10 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF private fun submit() = withState(viewModel) { state -> if (state.selectedHomeserver.preferredLoginMode is LoginMode.Sso) { - viewModel.getSsoUrl( + viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - providerId = null + provider = null ) ?.let { openInCustomTab(it) } } else { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index e8c478b7ff..f8ad700b40 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -216,7 +216,7 @@ class FtueAuthVariant( is OnboardingViewEvents.OnAccountCreated -> onAccountCreated() OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn() OnboardingViewEvents.OnChooseDisplayName -> onChooseDisplayName() - OnboardingViewEvents.OnTakeMeHome -> navigateToHome(createdAccount = true) + OnboardingViewEvents.OnTakeMeHome -> navigateToHome() OnboardingViewEvents.OnChooseProfilePicture -> onChooseProfilePicture() OnboardingViewEvents.OnPersonalizationComplete -> onPersonalizationComplete() OnboardingViewEvents.OnBack -> activity.popBackstack() @@ -467,7 +467,7 @@ class FtueAuthVariant( } private fun onAccountSignedIn() { - navigateToHome(createdAccount = false) + navigateToHome() } private fun onAccountCreated() { @@ -479,10 +479,12 @@ class FtueAuthVariant( ) } - private fun navigateToHome(createdAccount: Boolean) { - val intent = HomeActivity.newIntent(activity, accountCreation = createdAccount) - activity.startActivity(intent) - activity.finish() + private fun navigateToHome() { + withState(onboardingViewModel) { + val intent = HomeActivity.newIntent(activity, authenticationDescription = it.selectedAuthenticationState.description) + activity.startActivity(intent) + activity.finish() + } } private fun onChooseDisplayName() { diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt index 3c4d514e47..0df5f0e9cf 100644 --- a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt +++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt @@ -65,7 +65,14 @@ data class E2EWellKnownConfig( * clients should fallback to the default value of: ["key", "passphrase"]. */ @Json(name = "secure_backup_setup_methods") - val secureBackupSetupMethods: List? = null + val secureBackupSetupMethods: List? = null, + + /** + * Configuration for sharing keys strategy which should be used instead of [im.vector.app.BuildConfig.outboundSessionKeySharingStrategy]. + * One of on_room_opening, on_typing or disabled. + */ + @Json(name = "outbound_keys_pre_sharing_mode") + val outboundsKeyPreSharingMode: String? = null, ) @JsonClass(generateAdapter = true) diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt index fce91b8f15..73662613f7 100644 --- a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt +++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt @@ -16,6 +16,11 @@ package im.vector.app.features.raw.wellknown +import im.vector.app.BuildConfig +import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.extensions.tryOrNull @@ -30,6 +35,25 @@ suspend fun RawService.getElementWellknown(sessionParams: SessionParams): Elemen fun ElementWellKnown.isE2EByDefault() = elementE2E?.e2eDefault ?: riotE2E?.e2eDefault ?: true +fun ElementWellKnown?.getOutboundSessionKeySharingStrategyOrDefault(): OutboundSessionKeySharingStrategy { + return when (this?.elementE2E?.outboundsKeyPreSharingMode) { + "on_room_opening" -> OutboundSessionKeySharingStrategy.WhenEnteringRoom + "on_typing" -> OutboundSessionKeySharingStrategy.WhenTyping + "disabled" -> OutboundSessionKeySharingStrategy.WhenSendingEvent + else -> BuildConfig.outboundSessionKeySharingStrategy + } +} + +fun RawService.withElementWellKnown( + coroutineScope: CoroutineScope, + sessionParams: SessionParams, + block: ((ElementWellKnown?) -> Unit) +) = with(coroutineScope) { + launch(Dispatchers.IO) { + block(getElementWellknown(sessionParams)) + } +} + fun ElementWellKnown.isSecureBackupRequired() = elementE2E?.secureBackupRequired ?: riotE2E?.secureBackupRequired ?: false diff --git a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt index 9fcf1e6321..0af0488090 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt @@ -151,12 +151,14 @@ class AudioWaveformView @JvmOverloads constructor( private fun handleNewFftList(fftList: List) { val maxVisibleBarCount = getMaxVisibleBarCount() + fftList.forEach { fft -> rawFftList.add(fft) val barHeight = max(fft.value / MAX_FFT * (height - verticalPadding * 2), barMinHeight) visibleBarHeights.add(FFT(barHeight, fft.color)) + if (visibleBarHeights.size > maxVisibleBarCount) { - visibleBarHeights = visibleBarHeights.subList(visibleBarHeights.size - maxVisibleBarCount, visibleBarHeights.size) + visibleBarHeights = visibleBarHeights.takeLast(maxVisibleBarCount).toMutableList() } } } diff --git a/vector/src/main/res/layout/bottom_sheet_bootstrap.xml b/vector/src/main/res/layout/bottom_sheet_bootstrap.xml index 3818e50566..be209c2dd4 100644 --- a/vector/src/main/res/layout/bottom_sheet_bootstrap.xml +++ b/vector/src/main/res/layout/bottom_sheet_bootstrap.xml @@ -37,6 +37,7 @@ android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" + android:layout_marginTop="16dp" android:ellipsize="end" android:textColor="?vctr_content_primary" android:textStyle="bold" diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 1abfa7e9a8..77539da232 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -31,6 +31,7 @@ import im.vector.app.test.fakes.FakeContext import im.vector.app.test.fakes.FakeDirectLoginUseCase import im.vector.app.test.fakes.FakeHomeServerConnectionConfigFactory import im.vector.app.test.fakes.FakeHomeServerHistoryService +import im.vector.app.test.fakes.FakeLoginWizard import im.vector.app.test.fakes.FakeRegisterActionHandler import im.vector.app.test.fakes.FakeRegistrationWizard import im.vector.app.test.fakes.FakeSession @@ -67,6 +68,8 @@ private val A_DIRECT_LOGIN = OnboardingAction.AuthenticateAction.LoginDirect("@a private const val A_HOMESERVER_URL = "https://edited-homeserver.org" private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance) private val SELECTED_HOMESERVER_STATE = SelectedHomeserverState(preferredLoginMode = LoginMode.Password) +private const val AN_EMAIL = "hello@example.com" +private const val A_PASSWORD = "a-password" class OnboardingViewModelTest { @@ -85,6 +88,7 @@ class OnboardingViewModelTest { private val fakeHomeServerConnectionConfigFactory = FakeHomeServerConnectionConfigFactory() private val fakeStartAuthenticationFlowUseCase = FakeStartAuthenticationFlowUseCase() private val fakeHomeServerHistoryService = FakeHomeServerHistoryService() + private val fakeLoginWizard = FakeLoginWizard() private var initialState = OnboardingViewState() private lateinit var viewModel: OnboardingViewModel @@ -466,6 +470,43 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `given can successfully reset password, when resetting password, then emits reset done event`() = runTest { + val test = viewModel.test() + fakeLoginWizard.givenResetPasswordSuccess(AN_EMAIL) + fakeAuthenticationService.givenLoginWizard(fakeLoginWizard) + + viewModel.handle(OnboardingAction.ResetPassword(email = AN_EMAIL, newPassword = A_PASSWORD)) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false, resetState = ResetState(AN_EMAIL, A_PASSWORD)) } + ) + .assertEvents(OnboardingViewEvents.OnResetPasswordSendThreePidDone) + .finish() + } + + @Test + fun `given can successfully confirm reset password, when confirm reset password, then emits reset success`() = runTest { + viewModelWith(initialState.copy(resetState = ResetState(AN_EMAIL, A_PASSWORD))) + val test = viewModel.test() + fakeLoginWizard.givenConfirmResetPasswordSuccess(A_PASSWORD) + fakeAuthenticationService.givenLoginWizard(fakeLoginWizard) + + viewModel.handle(OnboardingAction.ResetPasswordMailConfirmed) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false, resetState = ResetState()) } + ) + .assertEvents(OnboardingViewEvents.OnResetPasswordMailConfirmationSuccess) + .finish() + } + private fun viewModelWith(state: OnboardingViewState) { OnboardingViewModel( state, diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt index 0456bbd474..cc606497f5 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt @@ -23,6 +23,7 @@ import io.mockk.mockk import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.LoginFlowResult +import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.wellknown.WellknownResult @@ -36,6 +37,10 @@ class FakeAuthenticationService : AuthenticationService by mockk() { every { isRegistrationStarted() } returns started } + fun givenLoginWizard(loginWizard: LoginWizard) { + every { getLoginWizard() } returns loginWizard + } + fun givenLoginFlow(config: HomeServerConnectionConfig, result: LoginFlowResult) { coEvery { getLoginFlow(config) } returns result } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeLoginWizard.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeLoginWizard.kt new file mode 100644 index 0000000000..38bb75087c --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeLoginWizard.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import io.mockk.coJustRun +import io.mockk.mockk +import org.matrix.android.sdk.api.auth.login.LoginWizard + +class FakeLoginWizard : LoginWizard by mockk() { + + fun givenResetPasswordSuccess(email: String) { + coJustRun { resetPassword(email) } + } + + fun givenConfirmResetPasswordSuccess(password: String) { + coJustRun { resetPasswordMailConfirmed(password) } + } +}