Merge tag 'v1.3.0' into sc

Change-Id: Ib681fa5493f078b15d6110262ba622b9d0384d68

Conflicts:
	gradle.properties
	vector/build.gradle
	vector/src/main/java/im/vector/app/AppStateHandler.kt
	vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
	vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt
	vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
	vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt
	vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
	vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt
	vector/src/main/java/im/vector/app/features/spaces/SpaceSettingsMenuBottomSheet.kt
	vector/src/main/res/layout/reaction_button.xml
This commit is contained in:
SpiritCroc 2021-09-29 09:41:41 +02:00
commit 4f93eb041c
247 changed files with 7002 additions and 1556 deletions

View file

@ -36,6 +36,7 @@ body:
- [ ] Push `main` and the new tag `v1.1.10` to origin - [ ] Push `main` and the new tag `v1.1.10` to origin
- [ ] Checkout `develop` - [ ] Checkout `develop`
- [ ] Increase version in `./vector/build.gradle` - [ ] Increase version in `./vector/build.gradle`
- [ ] Change the value of SDK_VERSION in the file `./matrix-sdk-android/build.gradle`
- [ ] Commit and push `develop` - [ ] Commit and push `develop`
- [ ] Wait for [Buildkite](https://buildkite.com/matrix-dot-org/element-android/builds?branch=main) to build the `main` branch. - [ ] Wait for [Buildkite](https://buildkite.com/matrix-dot-org/element-android/builds?branch=main) to build the `main` branch.
- [ ] Run the script `~/scripts/releaseElement.sh`. It will download the APKs from Buildkite check them and sign them. - [ ] Run the script `~/scripts/releaseElement.sh`. It will download the APKs from Buildkite check them and sign them.
@ -72,9 +73,25 @@ body:
- [ ] Create a release with GitFlow - [ ] Create a release with GitFlow
- [ ] Update the files `./build.gradle` and `./gradle/gradle-wrapper.properties` manually, to use the latest version for the dependency. You can get inspired by the same files on Element Android project. - [ ] Update the files `./build.gradle` and `./gradle/gradle-wrapper.properties` manually, to use the latest version for the dependency. You can get inspired by the same files on Element Android project.
- [ ] Run the script `./tools/import_from_element.sh` - [ ] Run the script `./tools/import_from_element.sh`
- [ ] Update the version in `./matrix-sdk-android/build.gradle` and let the script finish to build the library - [ ] Update the version in `./matrix-sdk-android/build.gradle`
- [ ] Check the diff on this file and restore what may have been erased (in particular the line `apply plugin: "com.vanniktech.maven.publish"`)
- [ ] Let the script finish to build the library
- [ ] Update the file `CHANGES.md` - [ ] Update the file `CHANGES.md`
- [ ] Update the value of VERSION_NAME in the file gradle.properties
- [ ] Finish the release using GitFlow - [ ] Finish the release using GitFlow
##### Release on MavenCentral
- [ ] Run the command `./gradlew publish --no-daemon --no-parallel`. You'll need some non-public element to do so
- [ ] Connect to https://s01.oss.sonatype.org
- [ ] Click on Staging Repositories and check the the files have been uploaded
- [ ] Click on close
- [ ] Wait (check Activity tab until step "Repository closed" is displayed)
- [ ] Click on release. The staging repository will disappear
- [ ] Check that the release is available in https://repo1.maven.org/maven2/org/matrix/android/matrix-android-sdk2/ (it can take a few minutes)
##### Release on GitHub
- [ ] Create the release on GitHub from [the tag](https://github.com/matrix-org/matrix-android-sdk2/tags) - [ ] Create the release on GitHub from [the tag](https://github.com/matrix-org/matrix-android-sdk2/tags)
- [ ] Upload the AAR on the GitHub release - [ ] Upload the AAR on the GitHub release
@ -82,7 +99,7 @@ body:
https://github.com/matrix-org/matrix-android-sdk2-sample https://github.com/matrix-org/matrix-android-sdk2-sample
- [ ] Update the dependency to the new version of the SDK2. Jitpack will have to build the AAR, it can take a few minutes. You can check status on https://jitpack.io/#matrix-org/matrix-android-sdk2 - [ ] Update the dependency to the new version of the SDK2. It can take some time for MavenCentral to make the librarie available. You can check status on https://repo1.maven.org/maven2/org/matrix/android/matrix-android-sdk2/
- [ ] Build and run the sample, you may have to fix some API break - [ ] Build and run the sample, you may have to fix some API break
- [ ] Commit and push directly on `main` - [ ] Commit and push directly on `main`
validations: validations:

View file

@ -5,6 +5,12 @@ on:
push: push:
branches: [ main, develop ] branches: [ main, develop ]
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx2g
-Porg.gradle.parallel=false
jobs: jobs:
debug: debug:
name: Build debug APKs (${{ matrix.target }}) name: Build debug APKs (${{ matrix.target }})
@ -25,7 +31,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- name: Assemble ${{ matrix.target }} debug apk - name: Assemble ${{ matrix.target }} debug apk
run: ./gradlew assemble${{ matrix.target }}Debug --stacktrace run: ./gradlew assemble${{ matrix.target }}Debug $CI_GRADLE_ARG_PROPERTIES --stacktrace
- name: Upload ${{ matrix.target }} debug APKs - name: Upload ${{ matrix.target }} debug APKs
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
@ -48,7 +54,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- name: Assemble GPlay unsigned apk - name: Assemble GPlay unsigned apk
run: ./gradlew clean assembleGplayRelease --stacktrace run: ./gradlew clean assembleGplayRelease $CI_GRADLE_ARG_PROPERTIES --stacktrace
- name: Upload Gplay unsigned APKs - name: Upload Gplay unsigned APKs
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:

View file

@ -5,6 +5,12 @@ on:
push: push:
branches: [ main, develop ] branches: [ main, develop ]
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx2g
-Porg.gradle.parallel=false
jobs: jobs:
# Temporary add build of Android tests, which cannot be run on the CI right now, but they need to at least compile # Temporary add build of Android tests, which cannot be run on the CI right now, but they need to at least compile
# So it will be mandatory for this action to be successful on every PRs # So it will be mandatory for this action to be successful on every PRs
@ -22,7 +28,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- name: Compile Android tests - name: Compile Android tests
run: ./gradlew clean assembleAndroidTest --stacktrace -PallWarningsAsErrors=false run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace -PallWarningsAsErrors=false
integration-tests: integration-tests:
name: Integration Tests (Synapse) name: Integration Tests (Synapse)
@ -30,9 +36,14 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
api-level: [21, 28, 30] api-level: [28]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 11
- name: Set up Python 3.8 - name: Set up Python 3.8
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
@ -64,5 +75,12 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
#arch: x86_64
#disable-animations: true
# script: ./gradlew -PallWarningsAsErrors=false vector:connectedAndroidTest matrix-sdk-android:connectedAndroidTest # script: ./gradlew -PallWarningsAsErrors=false vector:connectedAndroidTest matrix-sdk-android:connectedAndroidTest
script: ./gradlew -PallWarningsAsErrors=false connectedCheck 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: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace

View file

@ -5,6 +5,12 @@ on:
push: push:
branches: [ main, develop ] branches: [ main, develop ]
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx2g
-Porg.gradle.parallel=false
jobs: jobs:
integration-tests: integration-tests:
name: Sanity Tests (Synapse) name: Sanity Tests (Synapse)
@ -46,5 +52,5 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
script: ./gradlew -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest

View file

@ -5,6 +5,12 @@ on:
push: push:
branches: [main, develop] branches: [main, develop]
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx2g
-Porg.gradle.parallel=false
jobs: jobs:
unit-tests: unit-tests:
name: Run Unit Tests name: Run Unit Tests
@ -20,4 +26,11 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- name: Run unit tests - name: Run unit tests
run: ./gradlew clean test --stacktrace -PallWarningsAsErrors=false run: ./gradlew clean test $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false --stacktrace
- 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

1
.gitignore vendored
View file

@ -8,6 +8,7 @@
.idea/*.xml .idea/*.xml
.DS_Store .DS_Store
/build /build
/benchmark-out
/captures /captures
.externalNativeBuild .externalNativeBuild

View file

@ -1,3 +1,53 @@
Changes in Element v1.3.0 (2021-09-27)
======================================
Features ✨
----------
- Spaces!
- Adds email notification registration to Settings ([#2243](https://github.com/vector-im/element-android/issues/2243))
- Spaces | M3.23 Invite by email in create private space flow ([#3678](https://github.com/vector-im/element-android/issues/3678))
- Improve space invite bottom sheet ([#4057](https://github.com/vector-im/element-android/issues/4057))
- Allow to also leave rooms when leaving a space ([#3692](https://github.com/vector-im/element-android/issues/3692))
- Better expose adding spaces as Subspaces ([#3752](https://github.com/vector-im/element-android/issues/3752))
- Push and syncs: add debug info on room list and on room detail screen and improves the log format. ([#4046](https://github.com/vector-im/element-android/issues/4046))
Bugfixes 🐛
----------
- Remove the "Teammate spaces aren't quite ready" bottom sheet ([#3945](https://github.com/vector-im/element-android/issues/3945))
- Restricted Room previews aren't working ([#3946](https://github.com/vector-im/element-android/issues/3946))
- A removed room from a space can't be re-added as it won't be shown in add-room ([#3947](https://github.com/vector-im/element-android/issues/3947))
- "Non-Admin" user able to invite others to Private Space (by default) ([#3951](https://github.com/vector-im/element-android/issues/3951))
- Kick user dialog for spaces talks about rooms ([#3956](https://github.com/vector-im/element-android/issues/3956))
- Messages are displayed as unable to decrypt then decrypted a few seconds later ([#4011](https://github.com/vector-im/element-android/issues/4011))
- Fix DTMF not working ([#4015](https://github.com/vector-im/element-android/issues/4015))
- Fix sticky end call notification ([#4019](https://github.com/vector-im/element-android/issues/4019))
- Fix call screen stuck with some hanging up scenarios ([#4026](https://github.com/vector-im/element-android/issues/4026))
- Fix other call not always refreshed when ended ([#4028](https://github.com/vector-im/element-android/issues/4028))
- Private space invite bottomsheet only offering inviting by username not by email ([#4042](https://github.com/vector-im/element-android/issues/4042))
- Spaces invitation system notifications don't take me to the join space toast ([#4043](https://github.com/vector-im/element-android/issues/4043))
- Space Invites are not lighting up the drawer menu ([#4059](https://github.com/vector-im/element-android/issues/4059))
- MessageActionsBottomSheet not being shown on local echos ([#4068](https://github.com/vector-im/element-android/issues/4068))
SDK API changes ⚠️
------------------
- InitialSyncProgressService has been renamed to SyncStatusService and its function getInitialSyncProgressStatus() has been renamed to getSyncStatusLive() ([#4046](https://github.com/vector-im/element-android/issues/4046))
Other changes
-------------
- Better support for Sdk2 version. Also slight change in the default user agent: `MatrixAndroidSDK_X` is replaced by `MatrixAndroidSdk2` ([#3994](https://github.com/vector-im/element-android/issues/3994))
- Introduces ConferenceEvent to abstract usage of Jitsi BroadcastEvent class. ([#4014](https://github.com/vector-im/element-android/issues/4014))
- Improve performances on RoomDetail screen ([#4065](https://github.com/vector-im/element-android/issues/4065))
Changes in Element v1.2.2 (2021-09-13)
======================================
Bugfixes 🐛
----------
- Fix a security issue with message key sharing. See https://matrix.org/blog/2021/09/13/vulnerability-disclosure-key-sharing for details.
Changes in Element v1.2.1 (2021-09-08) Changes in Element v1.2.1 (2021-09-08)
====================================== ======================================

View file

@ -18,13 +18,12 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
android { android {
compileSdkVersion 30
compileSdk versions.compileSdk
defaultConfig { defaultConfig {
minSdkVersion 21 minSdk versions.minSdk
targetSdkVersion 30 targetSdk versions.targetSdk
versionCode 1
versionName "1.0"
} }
buildTypes { buildTypes {
@ -34,8 +33,8 @@ android {
} }
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_11 sourceCompatibility versions.sourceCompat
targetCompatibility JavaVersion.VERSION_11 targetCompatibility versions.targetCompat
} }
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
@ -51,13 +50,13 @@ dependencies {
implementation 'com.github.chrisbanes:PhotoView:2.3.0' implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' implementation libs.rx.rxKotlin
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation libs.rx.rxAndroid
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation libs.jetbrains.kotlinStdlib
implementation 'androidx.core:core-ktx:1.6.0' implementation libs.androidx.core
implementation 'androidx.appcompat:appcompat:1.3.1' implementation libs.androidx.appCompat
implementation "androidx.recyclerview:recyclerview:1.2.1" implementation libs.androidx.recyclerview
implementation 'com.google.android.material:material:1.4.0' implementation libs.google.material
} }

View file

@ -1,9 +1,9 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
// Ref: https://kotlinlang.org/releases.html
ext.kotlin_version = '1.5.21' apply from: 'dependencies.gradle'
ext.kotlin_coroutines_version = "1.5.0"
repositories { repositories {
google() google()
jcenter() jcenter()
@ -11,12 +11,13 @@ buildscript {
url "https://plugins.gradle.org/m2/" url "https://plugins.gradle.org/m2/"
} }
} }
dependencies { dependencies {
// Release notes of Android Gradle Plugin (AGP): // Release notes of Android Gradle Plugin (AGP):
// https://developer.android.com/studio/releases/gradle-plugin // https://developer.android.com/studio/releases/gradle-plugin
classpath 'com.android.tools.build:gradle:7.0.2' classpath libs.gradle.gradlePlugin
classpath libs.gradle.kotlinPlugin
classpath 'com.google.gms:google-services:4.3.10' classpath 'com.google.gms:google-services:4.3.10'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4'
classpath "com.likethesalad.android:string-reference:1.2.2" classpath "com.likethesalad.android:string-reference:1.2.2"

132
dependencies.gradle Normal file
View file

@ -0,0 +1,132 @@
ext.versions = [
'minSdk' : 21,
'compileSdk' : 30,
'targetSdk' : 30,
'sourceCompat' : JavaVersion.VERSION_11,
'targetCompat' : JavaVersion.VERSION_11,
]
def gradle = "7.0.2"
// Ref: https://kotlinlang.org/releases.html
def kotlin = "1.5.30"
def kotlinCoroutines = "1.5.1"
def dagger = "2.38.1"
def retrofit = "2.9.0"
def arrow = "0.8.2"
def markwon = "4.6.2"
def moshi = "1.12.0"
def lifecycle = "2.2.0"
def rxBinding = "3.1.0"
def epoxy = "4.6.2"
def glide = "4.12.0"
def bigImageViewer = "1.8.1"
def jjwt = "0.11.2"
// Testing
def mockk = "1.12.0"
def espresso = "3.4.0"
def androidxTest = "1.4.0"
ext.libs = [
gradle : [
'gradlePlugin' : "com.android.tools.build:gradle:$gradle",
'kotlinPlugin' : "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin"
],
jetbrains : [
'kotlinStdlibJdk7' : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin",
'kotlinStdlib' : "org.jetbrains.kotlin:kotlin-stdlib:$kotlin",
'kotlinReflect' : "org.jetbrains.kotlin:kotlin-reflect:$kotlin",
'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines",
'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines",
'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines"
],
androidx : [
'appCompat' : "androidx.appcompat:appcompat:1.3.1",
'core' : "androidx.core:core-ktx:1.6.0",
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3",
'fragmentKtx' : "androidx.fragment:fragment-ktx:1.3.6",
'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.0",
'work' : "androidx.work:work-runtime-ktx:2.5.0",
'autoFill' : "androidx.autofill:autofill:1.1.0",
'preferenceKtx' : "androidx.preference:preference-ktx:1.1.1",
'junit' : "androidx.test.ext:junit:1.1.3",
'lifecycleExtensions' : "androidx.lifecycle:lifecycle-extensions:$lifecycle",
'lifecycleJava8' : "androidx.lifecycle:lifecycle-common-java8:$lifecycle",
'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1",
'datastore' : "androidx.datastore:datastore:1.0.0",
'datastorepreferences' : "androidx.datastore:datastore-preferences:1.0.0",
'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2",
'coreTesting' : "androidx.arch.core:core-testing:2.1.0",
'testCore' : "androidx.test:core:$androidxTest",
'orchestrator' : "androidx.test:orchestrator:$androidxTest",
'testRunner' : "androidx.test:runner:$androidxTest",
'testRules' : "androidx.test:rules:$androidxTest",
'espressoCore' : "androidx.test.espresso:espresso-core:$espresso",
'espressoContrib' : "androidx.test.espresso:espresso-contrib:$espresso",
'espressoIntents' : "androidx.test.espresso:espresso-intents:$espresso"
],
google : [
'material' : "com.google.android.material:material:1.4.0"
],
dagger : [
'dagger' : "com.google.dagger:dagger:$dagger",
'daggerCompiler' : "com.google.dagger:dagger-compiler:$dagger"
],
squareup : [
'moshi' : "com.squareup.moshi:moshi-adapters:$moshi",
'moshiKt' : "com.squareup.moshi:moshi-kotlin:$moshi",
'moshiKotlin' : "com.squareup.moshi:moshi-kotlin-codegen:$moshi",
'retrofit' : "com.squareup.retrofit2:retrofit:$retrofit",
'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit"
],
rx : [
'rxKotlin' : "io.reactivex.rxjava2:rxkotlin:2.4.0",
'rxAndroid' : "io.reactivex.rxjava2:rxandroid:2.1.1"
],
arrow : [
'core' : "io.arrow-kt:arrow-core:$arrow",
'instances' : "io.arrow-kt:arrow-instances-core:$arrow"
],
markwon : [
'core' : "io.noties.markwon:core:$markwon",
'html' : "io.noties.markwon:html:$markwon"
],
airbnb : [
'epoxy' : "com.airbnb.android:epoxy:$epoxy",
'epoxyGlide' : "com.airbnb.android:epoxy-glide-preloading:$epoxy",
'epoxyProcessor' : "com.airbnb.android:epoxy-processor:$epoxy",
'epoxyPaging' : "com.airbnb.android:epoxy-paging:$epoxy",
'mvrx' : "com.airbnb.android:mvrx:1.5.1"
],
mockk : [
'mockk' : "io.mockk:mockk:$mockk",
'mockkAndroid' : "io.mockk:mockk-android:$mockk"
],
github : [
'glide' : "com.github.bumptech.glide:glide:$glide",
'glideCompiler' : "com.github.bumptech.glide:compiler:$glide",
'bigImageViewer' : "com.github.piasy:BigImageViewer:$bigImageViewer",
'glideImageLoader' : "com.github.piasy:GlideImageLoader:$bigImageViewer",
'progressPieIndicator' : "com.github.piasy:ProgressPieIndicator:$bigImageViewer",
'glideImageViewFactory' : "com.github.piasy:GlideImageViewFactory:$bigImageViewer"
],
jakewharton : [
'timber' : "com.jakewharton.timber:timber:5.0.1",
'rxbinding' : "com.jakewharton.rxbinding3:rxbinding:$rxBinding",
'rxbindingAppcompat' : "com.jakewharton.rxbinding3:rxbinding-appcompat:$rxBinding",
'rxbindingMaterial' : "com.jakewharton.rxbinding3:rxbinding-material:$rxBinding"
],
jsonwebtoken: [
'jjwtApi' : "io.jsonwebtoken:jjwt-api:$jjwt",
'jjwtImpl' : "io.jsonwebtoken:jjwt-impl:$jjwt",
'jjwtOrgjson' : "io.jsonwebtoken:jjwt-orgjson:$jjwt"
],
tests : [
'kluent' : "org.amshove.kluent:kluent-android:1.68",
'timberJunitRule' : "net.lachlanmckee:timber-junit-rule:1.0.1",
'junit' : "junit:junit:4.13.2"
]
]

View file

@ -0,0 +1,2 @@
Hauptänderungen: Sprachnachrichten standardmäßig aktiviert.
Ganze Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.1.16

View file

@ -0,0 +1,2 @@
Main changes in this version: Organize your rooms using Spaces!
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.0

View file

@ -0,0 +1,2 @@
Principaux changements pour cette version : messages vocaux activés par défault.
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.16

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: Memperbaiki kesalahan saat mengirim pesan terenkripsi jika seseorang yang ada di ruangan keluar.
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.16

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: Pesan Suara diaktifkan secara default
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.16

View file

@ -8,10 +8,10 @@ Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas ya
- Obrolan video dengan VoIP dan berbagi layar - Obrolan video dengan VoIP dan berbagi layar
- Integrasi yang mudah dengan alat kolaborasi online favorit Anda, alat manajemen proyek, layanan VoIP dan aplikasi perpesanan tim lainnya - Integrasi yang mudah dengan alat kolaborasi online favorit Anda, alat manajemen proyek, layanan VoIP dan aplikasi perpesanan tim lainnya
Element benar-benar berbeda dari aplikasi perpesanan dan kolaborasi lainnya. Ini beroperasi pada Matrix, jaringan terbuka untuk pengiriman pesan yang aman dan komunikasi terdesentralisasi. Ini memungkinkan hosting sendiri untuk memberi pengguna kepemilikan maksimum dan kontrol data dan pesan mereka. Element benar-benar berbeda dari aplikasi perpesanan dan kolaborasi lainnya. Element beroperasi pada Matrix, jaringan terbuka untuk pengiriman pesan yang aman dan komunikasi terdesentralisasi. Matrix memungkinkan hosting sendiri untuk memberi pengguna kepemilikan maksimum dan kontrol data dan pesan mereka.
<b>Pesan privasi dan terenkripsi</b> <b>Pesan privasi dan terenkripsi</b>
Element melindungi Anda dari iklan yang tidak diinginkan, data penambangan dan taman berdinding. Ini juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu melalui enkripsi ujung-ke-ujung dan verifikasi perangkat yang di-cross-signed. Element melindungi Anda dari iklan yang tidak diinginkan, data penambangan dan taman berdinding. Element juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu melalui enkripsi ujung-ke-ujung dan verifikasi perangkat yang ditanda tangani silang.
Element memberi Anda kendali atas privasi Anda sambil memungkinkan Anda untuk berkomunikasi dengan aman dengan siapa pun di jaringan Matrix, atau alat kolaborasi bisnis lainnya dengan mengintegrasikan dengan aplikasi seperti Slack. Element memberi Anda kendali atas privasi Anda sambil memungkinkan Anda untuk berkomunikasi dengan aman dengan siapa pun di jaringan Matrix, atau alat kolaborasi bisnis lainnya dengan mengintegrasikan dengan aplikasi seperti Slack.
@ -30,7 +30,7 @@ Element menempatkan Anda dalam kendali dengan cara yang berbeda:
Anda dapat mengobrol dengan siapa saja di jaringan Matrix, apakah mereka menggunakan Element, aplikasi Matrix lain atau bahkan jika mereka menggunakan aplikasi perpesanan yang berbeda. Anda dapat mengobrol dengan siapa saja di jaringan Matrix, apakah mereka menggunakan Element, aplikasi Matrix lain atau bahkan jika mereka menggunakan aplikasi perpesanan yang berbeda.
<b>Sangat aman</b> <b>Sangat aman</b>
Enkripsi ujung-ke-ujung beneran (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan verifikasi perangkat yang di-cross-signed. Enkripsi ujung-ke-ujung beneran (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan verifikasi perangkat yang ditanda tangani silang.
<b>Komunikasi dan integrasi lengkap</b> <b>Komunikasi dan integrasi lengkap</b>
Perpesanan, panggilan suara dan video, berbagi file, berbagi layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal. Perpesanan, panggilan suara dan video, berbagi file, berbagi layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal.

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: реализация голосовых сообщений в настройках лабораторий.
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.15

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: Исправление ошибки при отправке зашифрованного сообщения, если кто-то в комнате выходит.
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.16

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: Голосовое сообщение включено по умолчанию.
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.16

View file

@ -6,20 +6,20 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html # http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx4096m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# Enable file system watch (https://docs.gradle.org/6.7/release-notes.html) # Build Time Optimizations
org.gradle.jvmargs=-Xmx3g -Xms512M -XX:MaxPermSize=2048m -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC
org.gradle.configureondemand=true
org.gradle.parallel=true
org.gradle.vfs.watch=true org.gradle.vfs.watch=true
# Android Settings
android.enableJetifier=true
android.useAndroidX=true
#Project Settings
# Change debugPrivateData to true for debugging
vector.debugPrivateData=false vector.debugPrivateData=false
# httpLogLevel values: NONE, BASIC, HEADERS, BODY
vector.httpLogLevel=BASIC vector.httpLogLevel=BASIC
# Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above
#vector.debugPrivateData=true
#vector.httpLogLevel=BODY

View file

@ -20,14 +20,11 @@ plugins {
} }
android { android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
compileSdk versions.compileSdk
defaultConfig { defaultConfig {
minSdkVersion 21 minSdk versions.minSdk
targetSdkVersion 30 targetSdk versions.targetSdk
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro" consumerProguardFiles "consumer-rules.pro"
@ -41,8 +38,8 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_11 sourceCompatibility versions.sourceCompat
targetCompatibility JavaVersion.VERSION_11 targetCompatibility versions.targetCompat
} }
kotlinOptions { kotlinOptions {
@ -55,10 +52,10 @@ android {
} }
dependencies { dependencies {
implementation 'androidx.appcompat:appcompat:1.3.1' implementation libs.androidx.appCompat
implementation 'com.google.android.material:material:1.4.0' implementation libs.google.material
// Pref theme // Pref theme
implementation 'androidx.preference:preference-ktx:1.1.1' implementation libs.androidx.preferenceKtx
// PFLockScreen attrs // PFLockScreen attrs
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12' implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
// dialpad dimen // dialpad dimen

View file

@ -8,6 +8,7 @@
<attr name="actionDescription" format="string" /> <attr name="actionDescription" format="string" />
<attr name="leftIcon" format="reference" /> <attr name="leftIcon" format="reference" />
<attr name="rightIcon" format="reference" /> <attr name="rightIcon" format="reference" />
<attr name="betaAction" format="boolean" />
<attr name="forceStartPadding" format="boolean" /> <attr name="forceStartPadding" format="boolean" />
</declare-styleable> </declare-styleable>

View file

@ -3,13 +3,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
android { android {
compileSdkVersion 30 compileSdk versions.compileSdk
defaultConfig { defaultConfig {
minSdkVersion 21 minSdk versions.minSdk
targetSdkVersion 30 targetSdk versions.targetSdk
versionCode 1
versionName "1.0"
// Multidex is useful for tests // Multidex is useful for tests
multiDexEnabled true multiDexEnabled true
@ -24,8 +22,8 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_11 sourceCompatibility versions.sourceCompat
targetCompatibility JavaVersion.VERSION_11 targetCompatibility versions.targetCompat
} }
kotlinOptions { kotlinOptions {
@ -34,15 +32,16 @@ android {
} }
dependencies { dependencies {
implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android")
implementation 'androidx.appcompat:appcompat:1.3.1' implementation libs.androidx.appCompat
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' implementation libs.rx.rxKotlin
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation libs.rx.rxAndroid
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlin_coroutines_version" implementation libs.jetbrains.coroutinesRx2
// Paging // Paging
implementation "androidx.paging:paging-runtime-ktx:2.1.2" implementation libs.androidx.pagingRuntimeKtx
// Logging // Logging
implementation 'com.jakewharton.timber:timber:5.0.1' implementation libs.jakewharton.timber
} }

View file

@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_S
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams
import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.identity.FoundThreePid
import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.pushers.Pusher import org.matrix.android.sdk.api.session.pushers.Pusher
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
@ -239,6 +240,10 @@ class RxSession(private val session: Session) {
) )
.distinctUntilChanged() .distinctUntilChanged()
} }
fun lookupThreePid(threePid: ThreePid): Single<Optional<FoundThreePid>> = rxSingle {
session.identityService().lookUp(listOf(threePid)).firstOrNull().toOptional()
}
} }
fun Session.rx(): RxSession { fun Session.rx(): RxSession {

View file

@ -14,14 +14,14 @@ buildscript {
} }
android { android {
compileSdkVersion 30
testOptions.unitTests.includeAndroidResources = true testOptions.unitTests.includeAndroidResources = true
compileSdk versions.compileSdk
defaultConfig { defaultConfig {
minSdkVersion 21 minSdk versions.minSdk
targetSdkVersion 30 targetSdk versions.targetSdk
versionCode 1
versionName "0.0.1"
// Multidex is useful for tests // Multidex is useful for tests
multiDexEnabled true multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -31,9 +31,7 @@ android {
// that the app's state is completely cleared between tests. // that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true' testInstrumentationRunnerArguments clearPackageData: 'true'
// Seems that the build tools 4.1.0 does not generate BuildConfig.VERSION_NAME anymore. buildConfigField "String", "SDK_VERSION", "\"1.2.2\""
// Add it manually here. We may remove this trick in the future
buildConfigField "String", "VERSION_NAME", "\"0.0.1\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
resValue "string", "git_sdk_revision", "\"${gitRevision()}\"" resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
@ -68,8 +66,8 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_11 sourceCompatibility versions.sourceCompat
targetCompatibility JavaVersion.VERSION_11 targetCompatibility versions.targetCompat
} }
kotlinOptions { kotlinOptions {
@ -103,92 +101,83 @@ static def gitRevisionDate() {
dependencies { dependencies {
def arrow_version = "0.8.2" implementation libs.jetbrains.kotlinStdlibJdk7
def moshi_version = '1.12.0' implementation libs.jetbrains.coroutinesCore
def lifecycle_version = '2.2.0' implementation libs.jetbrains.coroutinesAndroid
def arch_version = '2.1.0'
def markwon_version = '3.1.0'
def daggerVersion = '2.38.1'
def work_version = '2.5.0'
def retrofit_version = '2.9.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation libs.androidx.appCompat
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation libs.androidx.core
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.appcompat:appcompat:1.3.1" implementation libs.androidx.lifecycleExtensions
implementation "androidx.core:core-ktx:1.6.0" implementation libs.androidx.lifecycleJava8
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// Network // Network
implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation libs.squareup.retrofit
implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version" implementation libs.squareup.retrofitMoshi
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.1")) implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.1"))
implementation 'com.squareup.okhttp3:okhttp' implementation 'com.squareup.okhttp3:okhttp'
implementation 'com.squareup.okhttp3:logging-interceptor' implementation 'com.squareup.okhttp3:logging-interceptor'
implementation 'com.squareup.okhttp3:okhttp-urlconnection' implementation 'com.squareup.okhttp3:okhttp-urlconnection'
implementation "com.squareup.moshi:moshi-adapters:$moshi_version" implementation libs.squareup.moshi
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" kapt libs.squareup.moshiKotlin
implementation "ru.noties.markwon:core:$markwon_version" implementation libs.markwon.core
// Image // Image
implementation 'androidx.exifinterface:exifinterface:1.3.3' implementation libs.androidx.exifinterface
// Database // Database
implementation 'com.github.Zhuinden:realm-monarchy:0.7.1' implementation 'com.github.Zhuinden:realm-monarchy:0.7.1'
kapt 'dk.ilios:realmfieldnameshelper:2.0.0' kapt 'dk.ilios:realmfieldnameshelper:2.0.0'
// Work // Work
implementation "androidx.work:work-runtime-ktx:$work_version" implementation libs.androidx.work
// FP // FP
implementation "io.arrow-kt:arrow-core:$arrow_version" implementation libs.arrow.core
implementation "io.arrow-kt:arrow-instances-core:$arrow_version" implementation libs.arrow.instances
// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm // olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
implementation 'org.matrix.gitlab.matrix-org:olm:3.2.4' implementation 'org.matrix.gitlab.matrix-org:olm:3.2.4'
// DI // DI
implementation "com.google.dagger:dagger:$daggerVersion" implementation libs.dagger.dagger
kapt "com.google.dagger:dagger-compiler:$daggerVersion" kapt libs.dagger.daggerCompiler
// Logging // Logging
implementation 'com.jakewharton.timber:timber:5.0.1' implementation libs.jakewharton.timber
implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0' implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
// Video compression // Video compression
implementation 'com.otaliastudios:transcoder:0.10.3' implementation 'com.otaliastudios:transcoder:0.10.4'
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.31' implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.33'
testImplementation 'junit:junit:4.13.2' testImplementation libs.tests.junit
testImplementation 'org.robolectric:robolectric:4.5.1' testImplementation 'org.robolectric:robolectric:4.6.1'
//testImplementation 'org.robolectric:shadows-support-v4:3.0' //testImplementation 'org.robolectric:shadows-support-v4:3.0'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation 'io.mockk:mockk:1.12.0' testImplementation libs.mockk.mockk
testImplementation 'org.amshove.kluent:kluent-android:1.68' testImplementation libs.tests.kluent
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" implementation libs.jetbrains.coroutinesAndroid
// Plant Timber tree for test // Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion" kaptAndroidTest libs.dagger.daggerCompiler
androidTestImplementation 'androidx.test:core:1.4.0' androidTestImplementation libs.androidx.testCore
androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation libs.androidx.testRunner
androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation libs.androidx.testRules
androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation libs.androidx.junit
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation libs.androidx.espressoCore
androidTestImplementation 'org.amshove.kluent:kluent-android:1.68' androidTestImplementation libs.tests.kluent
androidTestImplementation 'io.mockk:mockk-android:1.12.0' androidTestImplementation libs.mockk.mockkAndroid
androidTestImplementation "androidx.arch.core:core-testing:$arch_version" androidTestImplementation libs.androidx.coreTesting
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" androidTestImplementation libs.jetbrains.coroutinesAndroid
// Plant Timber tree for test // Plant Timber tree for test
androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' androidTestImplementation libs.tests.timberJunitRule
androidTestUtil 'androidx.test:orchestrator:1.4.0' androidTestUtil libs.androidx.orchestrator
} }

View file

@ -117,7 +117,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
} }
fun getSdkVersion(): String { fun getSdkVersion(): String {
return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")" return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")"
} }
} }
} }

View file

@ -19,6 +19,8 @@ package org.matrix.android.sdk.common
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -59,13 +61,15 @@ class CommonTestHelper(context: Context) {
fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestNetworkModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestNetworkModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
init { init {
Matrix.initialize( UiThreadStatement.runOnUiThread {
context, Matrix.initialize(
MatrixConfiguration( context,
applicationFlavor = "TestFlavor", MatrixConfiguration(
roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider() applicationFlavor = "TestFlavor",
) roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider()
) )
)
}
matrix = Matrix.getInstance(context) matrix = Matrix.getInstance(context)
} }

View file

@ -32,10 +32,19 @@ import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.api.session.room.powerlevels.Role
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.SessionTestParams
@ -386,6 +395,8 @@ class SpaceHierarchyTest : InstrumentedTest {
// The room should have disapear from flat children // The room should have disapear from flat children
GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) } GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
} }
commonTestHelper.signOutAndClose(session)
} }
data class TestSpaceCreationResult( data class TestSpaceCreationResult(
@ -434,6 +445,57 @@ class SpaceHierarchyTest : InstrumentedTest {
return TestSpaceCreationResult(spaceId, roomIds) return TestSpaceCreationResult(spaceId, roomIds)
} }
@Suppress("EXPERIMENTAL_API_USAGE")
private fun createPrivateSpace(session: Session,
spaceName: String,
childInfo: List<Triple<String, Boolean, Boolean?>>
/** Name, auto-join, canonical*/
): TestSpaceCreationResult {
var spaceId = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false)
it.countDown()
}
}
val syncedSpace = session.spaceService().getSpace(spaceId)
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
val roomIds =
childInfo.map { entry ->
var roomId = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
val homeServerCapabilities = session
.getHomeServerCapabilities()
roomId = session.createRoom(CreateRoomParams().apply {
name = entry.first
this.featurePreset = RestrictedRoomPreset(
homeServerCapabilities,
listOf(
RoomJoinRulesAllowEntry.restrictedToRoom(spaceId)
)
)
})
it.countDown()
}
}
roomId
}
roomIds.forEachIndexed { index, roomId ->
runBlocking {
syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
val canonical = childInfo[index].third
if (canonical != null) {
session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
}
}
}
return TestSpaceCreationResult(spaceId, roomIds)
}
@Test @Test
fun testRootSpaces() { fun testRootSpaces() {
val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val session = commonTestHelper.createAccount("John", SessionTestParams(true))
@ -473,5 +535,111 @@ class SpaceHierarchyTest : InstrumentedTest {
val rootSpaces = session.spaceService().getRootSpaceSummaries() val rootSpaces = session.spaceService().getRootSpaceSummaries()
assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size) assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size)
commonTestHelper.signOutAndClose(session)
}
@Test
fun testParentRelation() {
val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true))
val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true))
val spaceAInfo = createPrivateSpace(aliceSession, "Private Space A", listOf(
Triple("General", true /*suggested*/, true/*canonical*/),
Triple("Random", true, true)
))
commonTestHelper.runBlockingTest {
aliceSession.getRoom(spaceAInfo.spaceId)!!.invite(bobSession.myUserId, null)
}
commonTestHelper.runBlockingTest {
bobSession.joinRoom(spaceAInfo.spaceId, null, emptyList())
}
var bobRoomId = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
bobRoomId = bobSession.createRoom(CreateRoomParams().apply { name = "A Bob Room" })
bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId)
it.countDown()
}
}
commonTestHelper.runBlockingTest {
aliceSession.joinRoom(bobRoomId)
}
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession.getRoomSummary(bobRoomId)?.membership?.isActive() == true
}
}
commonTestHelper.waitWithLatch {
GlobalScope.launch {
bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
it.countDown()
}
}
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
val stateEvent = aliceSession.getRoom(bobRoomId)!!.getStateEvent(EventType.STATE_SPACE_PARENT, QueryStringValue.Equals(spaceAInfo.spaceId))
stateEvent != null
}
}
// This should be an invalid space parent relation, because no opposite child and bob is not admin of the space
commonTestHelper.runBlockingTest {
// we can see the state event
// but it is not valid and room is not in hierarchy
assertTrue("Bob Room should not be listed as a child of the space", aliceSession.getRoomSummary(bobRoomId)?.flattenParentIds?.isEmpty() == true)
}
// Let's now try to make alice admin of the room
commonTestHelper.waitWithLatch {
GlobalScope.launch {
val room = bobSession.getRoom(bobRoomId)!!
val currentPLContent = room
.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
?.let { it.content.toModel<PowerLevelsContent>() }
val newPowerLevelsContent = currentPLContent
?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
?.toContent()
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent!!)
it.countDown()
}
}
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
val powerLevelsHelper = aliceSession.getRoom(bobRoomId)!!
.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
?.content
?.toModel<PowerLevelsContent>()
?.let { PowerLevelsHelper(it) }
powerLevelsHelper!!.isUserAllowedToSend(aliceSession.myUserId, true, EventType.STATE_SPACE_PARENT)
}
}
commonTestHelper.waitWithLatch {
GlobalScope.launch {
aliceSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
it.countDown()
}
}
commonTestHelper.waitWithLatch { latch ->
commonTestHelper.retryPeriodicallyWithLatch(latch) {
bobSession.getRoomSummary(bobRoomId)?.flattenParentIds?.contains(spaceAInfo.spaceId) == true
}
}
commonTestHelper.signOutAndClose(aliceSession)
commonTestHelper.signOutAndClose(bobSession)
} }
} }

View file

@ -111,7 +111,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
} }
fun getSdkVersion(): String { fun getSdkVersion(): String {
return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")" return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")"
} }
} }
} }

View file

@ -24,6 +24,7 @@ package org.matrix.android.sdk.api.logger
*/ */
open class LoggerTag(_value: String, parentTag: LoggerTag? = null) { open class LoggerTag(_value: String, parentTag: LoggerTag? = null) {
object SYNC : LoggerTag("SYNC")
object VOIP : LoggerTag("VOIP") object VOIP : LoggerTag("VOIP")
val value: String = if (parentTag == null) { val value: String = if (parentTag == null) {

View file

@ -36,7 +36,7 @@ import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.group.GroupService import org.matrix.android.sdk.api.session.group.GroupService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.identity.IdentityService import org.matrix.android.sdk.api.session.identity.IdentityService
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.media.MediaService
import org.matrix.android.sdk.api.session.openid.OpenIdService import org.matrix.android.sdk.api.session.openid.OpenIdService
@ -75,7 +75,7 @@ interface Session :
ProfileService, ProfileService,
PushRuleService, PushRuleService,
PushersService, PushersService,
InitialSyncProgressService, SyncStatusService,
HomeServerCapabilitiesService, HomeServerCapabilitiesService,
SecureStorageService, SecureStorageService,
AccountService { AccountService {

View file

@ -239,7 +239,7 @@ data class Event(
fun Event.isTextMessage(): Boolean { fun Event.isTextMessage(): Boolean {
return getClearType() == EventType.MESSAGE return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) { && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
MessageType.MSGTYPE_TEXT, MessageType.MSGTYPE_TEXT,
MessageType.MSGTYPE_EMOTE, MessageType.MSGTYPE_EMOTE,
MessageType.MSGTYPE_NOTICE -> true MessageType.MSGTYPE_NOTICE -> true
@ -249,7 +249,7 @@ fun Event.isTextMessage(): Boolean {
fun Event.isImageMessage(): Boolean { fun Event.isImageMessage(): Boolean {
return getClearType() == EventType.MESSAGE return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) { && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
MessageType.MSGTYPE_IMAGE -> true MessageType.MSGTYPE_IMAGE -> true
else -> false else -> false
} }
@ -257,7 +257,7 @@ fun Event.isImageMessage(): Boolean {
fun Event.isVideoMessage(): Boolean { fun Event.isVideoMessage(): Boolean {
return getClearType() == EventType.MESSAGE return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) { && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
MessageType.MSGTYPE_VIDEO -> true MessageType.MSGTYPE_VIDEO -> true
else -> false else -> false
} }
@ -265,7 +265,7 @@ fun Event.isVideoMessage(): Boolean {
fun Event.isAudioMessage(): Boolean { fun Event.isAudioMessage(): Boolean {
return getClearType() == EventType.MESSAGE return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) { && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
MessageType.MSGTYPE_AUDIO -> true MessageType.MSGTYPE_AUDIO -> true
else -> false else -> false
} }
@ -273,7 +273,7 @@ fun Event.isAudioMessage(): Boolean {
fun Event.isFileMessage(): Boolean { fun Event.isFileMessage(): Boolean {
return getClearType() == EventType.MESSAGE return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) { && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
MessageType.MSGTYPE_FILE -> true MessageType.MSGTYPE_FILE -> true
else -> false else -> false
} }
@ -281,7 +281,7 @@ fun Event.isFileMessage(): Boolean {
fun Event.isAttachmentMessage(): Boolean { fun Event.isAttachmentMessage(): Boolean {
return getClearType() == EventType.MESSAGE return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) { && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_AUDIO, MessageType.MSGTYPE_AUDIO,
MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_VIDEO,

View file

@ -0,0 +1,49 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.initsync
import androidx.lifecycle.LiveData
interface SyncStatusService {
fun getSyncStatusLive(): LiveData<Status>
sealed class Status {
/**
* For initial sync
*/
abstract class InitialSyncStatus: Status()
object Idle : InitialSyncStatus()
data class Progressing(
val initSyncStep: InitSyncStep,
val percentProgress: Int = 0
) : InitialSyncStatus()
/**
* For incremental sync
*/
abstract class IncrementalSyncStatus: Status()
object IncrementalSyncIdle : IncrementalSyncStatus()
data class IncrementalSyncParsing(
val rooms: Int,
val toDevice: Int
) : IncrementalSyncStatus()
object IncrementalSyncError : IncrementalSyncStatus()
object IncrementalSyncDone : IncrementalSyncStatus()
}
}

View file

@ -26,7 +26,14 @@ data class Pusher(
val data: PusherData, val data: PusherData,
val state: PusherState val state: PusherState
) ) {
companion object {
const val KIND_EMAIL = "email"
const val KIND_HTTP = "http"
const val APP_ID_EMAIL = "m.email"
}
}
enum class PusherState { enum class PusherState {
UNREGISTERED, UNREGISTERED,

View file

@ -27,14 +27,12 @@ interface PushersService {
/** /**
* Add a new HTTP pusher. * Add a new HTTP pusher.
* Note that only `http` kind is supported by the SDK for now.
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
* *
* @param pushkey This is a unique identifier for this pusher. The value you should use for * @param pushkey This is a unique identifier for this pusher. The value you should use for
* this is the routing or destination address information for the notification, * this is the routing or destination address information for the notification,
* for example, the APNS token for APNS or the Registration ID for GCM. If your * for example, the APNS token for APNS or the Registration ID for GCM. If your
* notification client has no such concept, use any unique identifier. Max length, 512 chars. * notification client has no such concept, use any unique identifier. Max length, 512 chars.
* If the kind is "email", this is the email address to send notifications to.
* @param appId the application id * @param appId the application id
* This is a reverse-DNS style identifier for the application. It is recommended * This is a reverse-DNS style identifier for the application. It is recommended
* that this end with the platform, such that different platform versions get * that this end with the platform, such that different platform versions get
@ -64,6 +62,30 @@ interface PushersService {
append: Boolean, append: Boolean,
withEventIdOnly: Boolean): UUID withEventIdOnly: Boolean): UUID
/**
* Add a new Email pusher.
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
*
* @param email The email address to send notifications to.
* @param lang The preferred language for receiving notifications (e.g. "en" or "en-US").
* @param emailBranding The branding placeholder to include in the email communications.
* @param appDisplayName A human readable string that will allow the user to identify what application owns this pusher.
* @param deviceDisplayName A human readable string that will allow the user to identify what device owns this pusher.
* @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition
* to any others with different user IDs. Otherwise, the homeserver must remove any other pushers
* with the same App ID and pushkey for different users. Typically We always want to append for
* email pushers since we don't want to stop other accounts notifying to the same email address.
* @return A work request uuid. Can be used to listen to the status
* (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>))
* @throws [InvalidParameterException] if a parameter is not correct
*/
fun addEmailPusher(email: String,
lang: String,
emailBranding: String,
appDisplayName: String,
deviceDisplayName: String,
append: Boolean = true): UUID
/** /**
* Directly ask the push gateway to send a push to this device * Directly ask the push gateway to send a push to this device
* If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId. * If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
@ -80,10 +102,23 @@ interface PushersService {
eventId: String) eventId: String)
/** /**
* Remove the http pusher * Remove a registered pusher
* @param pusher the pusher to remove, can be http or email
*/
suspend fun removePusher(pusher: Pusher)
/**
* Remove a Http pusher by its pushkey and appId
* @see addHttpPusher
*/ */
suspend fun removeHttpPusher(pushkey: String, appId: String) suspend fun removeHttpPusher(pushkey: String, appId: String)
/**
* Remove an Email pusher
* @see addEmailPusher
*/
suspend fun removeEmailPusher(email: String)
/** /**
* Get the current pushers, as a LiveData * Get the current pushers, as a LiveData
*/ */

View file

@ -0,0 +1,111 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.room.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* These are the same fields as those returned by /publicRooms, with a few additions: room_type, membership and is_encrypted.
*/
@JsonClass(generateAdapter = true)
data class RoomStrippedState(
/**
* Aliases of the room. May be empty.
*/
@Json(name = "aliases")
val aliases: List<String>? = null,
/**
* The canonical alias of the room, if any.
*/
@Json(name = "canonical_alias")
val canonicalAlias: String? = null,
/**
* The name of the room, if any.
*/
@Json(name = "name")
val name: String? = null,
/**
* Required. The number of members joined to the room.
*/
@Json(name = "num_joined_members")
val numJoinedMembers: Int = 0,
/**
* Required. The ID of the room.
*/
@Json(name = "room_id")
val roomId: String,
/**
* The topic of the room, if any.
*/
@Json(name = "topic")
val topic: String? = null,
/**
* Required. Whether the room may be viewed by guest users without joining.
*/
@Json(name = "world_readable")
val worldReadable: Boolean = false,
/**
* Required. Whether guest users may join the room and participate in it. If they can,
* they will be subject to ordinary power level rules like any other user.
*/
@Json(name = "guest_can_join")
val guestCanJoin: Boolean = false,
/**
* The URL for the room's avatar, if one is set.
*/
@Json(name = "avatar_url")
val avatarUrl: String? = null,
/**
* Undocumented item
*/
@Json(name = "m.federate")
val isFederated: Boolean = false,
/**
* Optional. If the room is encrypted. This is already accessible as stripped state.
*/
@Json(name = "is_encrypted")
val isEncrypted: Boolean?,
/**
* Optional. Type of the room, if any, i.e. m.space
*/
@Json(name = "room_type")
val roomType: String?,
/**
* The current membership of this user in the room. Usually leave if the room is fetched over federation.
*/
@Json(name = "membership")
val membership: String?
) {
/**
* Return the canonical alias, or the first alias from the list of aliases, or null
*/
fun getPrimaryAlias(): String? {
return canonicalAlias ?: aliases?.firstOrNull()
}
}

View file

@ -28,7 +28,7 @@ data class MessageAudioContent(
/** /**
* Required. Must be 'm.audio'. * Required. Must be 'm.audio'.
*/ */
@Json(name = "msgtype") override val msgType: String, @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
/** /**
* Required. A description of the audio e.g. 'Bee Gees - Stayin' Alive', or some kind of content description for accessibility e.g. 'audio attachment'. * Required. A description of the audio e.g. 'Bee Gees - Stayin' Alive', or some kind of content description for accessibility e.g. 'audio attachment'.

View file

@ -20,6 +20,11 @@ import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
interface MessageContent { interface MessageContent {
companion object {
const val MSG_TYPE_JSON_KEY = "msgtype"
}
val msgType: String val msgType: String
val body: String val body: String
val relatesTo: RelationDefaultContent? val relatesTo: RelationDefaultContent?

View file

@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageDefaultContent( data class MessageDefaultContent(
@Json(name = "msgtype") override val msgType: String, @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null @Json(name = "m.new_content") override val newContent: Content? = null

View file

@ -26,7 +26,7 @@ data class MessageEmoteContent(
/** /**
* Required. Must be 'm.emote'. * Required. Must be 'm.emote'.
*/ */
@Json(name = "msgtype") override val msgType: String, @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
/** /**
* Required. The emote action to perform. * Required. The emote action to perform.

View file

@ -28,7 +28,7 @@ data class MessageFileContent(
/** /**
* Required. Must be 'm.file'. * Required. Must be 'm.file'.
*/ */
@Json(name = "msgtype") override val msgType: String, @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
/** /**
* Required. A human-readable description of the file. This is recommended to be the filename of the original upload. * Required. A human-readable description of the file. This is recommended to be the filename of the original upload.

View file

@ -27,7 +27,7 @@ data class MessageImageContent(
/** /**
* Required. Must be 'm.image'. * Required. Must be 'm.image'.
*/ */
@Json(name = "msgtype") override val msgType: String, @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
/** /**
* Required. A textual representation of the image. This could be the alt text of the image, the filename of the image, * Required. A textual representation of the image. This could be the alt text of the image, the filename of the image,

View file

@ -26,7 +26,7 @@ data class MessageLocationContent(
/** /**
* Required. Must be 'm.location'. * Required. Must be 'm.location'.
*/ */
@Json(name = "msgtype") override val msgType: String, @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
/** /**
* Required. A description of the location e.g. 'Big Ben, London, UK', or some kind * Required. A description of the location e.g. 'Big Ben, London, UK', or some kind

View file

@ -26,7 +26,7 @@ data class MessageNoticeContent(
/** /**
* Required. Must be 'm.notice'. * Required. Must be 'm.notice'.
*/ */
@Json(name = "msgtype") override val msgType: String, @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
/** /**
* Required. The notice text to send. * Required. The notice text to send.

View file

@ -30,7 +30,7 @@ const val OPTION_TYPE_BUTTONS = "org.matrix.buttons"
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageOptionsContent( data class MessageOptionsContent(
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_OPTIONS, @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_OPTIONS,
@Json(name = "type") val optionType: String? = null, @Json(name = "type") val optionType: String? = null,
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
@Json(name = "label") val label: String?, @Json(name = "label") val label: String?,

View file

@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessagePollResponseContent( data class MessagePollResponseContent(
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_RESPONSE, @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_RESPONSE,
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null @Json(name = "m.new_content") override val newContent: Content? = null

View file

@ -26,7 +26,7 @@ data class MessageTextContent(
/** /**
* Required. Must be 'm.text'. * Required. Must be 'm.text'.
*/ */
@Json(name = "msgtype") override val msgType: String, @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
/** /**
* Required. The body of the message. * Required. The body of the message.

View file

@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReque
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageVerificationRequestContent( data class MessageVerificationRequestContent(
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST, @Json(name = MessageContent.MSG_TYPE_JSON_KEY)override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String,
@Json(name = "from_device") override val fromDevice: String?, @Json(name = "from_device") override val fromDevice: String?,
@Json(name = "methods") override val methods: List<String>, @Json(name = "methods") override val methods: List<String>,

View file

@ -27,7 +27,7 @@ data class MessageVideoContent(
/** /**
* Required. Must be 'm.video'. * Required. Must be 'm.video'.
*/ */
@Json(name = "msgtype") override val msgType: String, @Json(name = MessageContent.MSG_TYPE_JSON_KEY)override val msgType: String,
/** /**
* Required. A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'. * Required. A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'.

View file

@ -94,5 +94,7 @@ interface SpaceService {
*/ */
suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List<String>) suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List<String>)
suspend fun removeSpaceParent(childRoomId: String, parentSpaceId: String)
fun getRootSpaceSummaries(): List<RoomSummary> fun getRootSpaceSummaries(): List<RoomSummary>
} }

View file

@ -71,18 +71,24 @@ internal class InboundGroupSessionStore @Inject constructor(
} }
@Synchronized @Synchronized
fun storeInBoundGroupSession(wrapper: OlmInboundGroupSessionWrapper2) { fun storeInBoundGroupSession(wrapper: OlmInboundGroupSessionWrapper2, sessionId: String, senderKey: String) {
Timber.v("## Inbound: getInboundGroupSession mark as dirty ${wrapper.roomId}-${wrapper.senderKey}") Timber.v("## Inbound: getInboundGroupSession mark as dirty ${wrapper.roomId}-${wrapper.senderKey}")
// We want to batch this a bit for performances // We want to batch this a bit for performances
dirtySession.add(wrapper) dirtySession.add(wrapper)
if (sessionCache[CacheKey(sessionId, senderKey)] == null) {
// first time seen, put it in memory cache while waiting for batch insert
// If it's already known, no need to update cache it's already there
sessionCache.put(CacheKey(sessionId, senderKey), wrapper)
}
timerTask?.cancel() timerTask?.cancel()
timerTask = object : TimerTask() { timerTask = object : TimerTask() {
override fun run() { override fun run() {
batchSave() batchSave()
} }
} }
timer.schedule(timerTask!!, 2_000) timer.schedule(timerTask!!, 300)
} }
@Synchronized @Synchronized

View file

@ -577,7 +577,8 @@ internal class MXOlmDevice @Inject constructor(
session.keysClaimed = keysClaimed session.keysClaimed = keysClaimed
session.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain session.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain
store.storeInboundGroupSessions(listOf(session)) inboundGroupSessionStore.storeInBoundGroupSession(session, sessionId, senderKey)
// store.storeInboundGroupSessions(listOf(session))
return true return true
} }
@ -703,7 +704,7 @@ internal class MXOlmDevice @Inject constructor(
timelineSet.add(messageIndexKey) timelineSet.add(messageIndexKey)
} }
inboundGroupSessionStore.storeInBoundGroupSession(session) inboundGroupSessionStore.storeInBoundGroupSession(session, sessionId, senderKey)
val payload = try { val payload = try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE) val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage) val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)

View file

@ -36,7 +36,7 @@ internal class UserAgentHolder @Inject constructor(private val context: Context,
/** /**
* Create an user agent with the application version. * Create an user agent with the application version.
* Ex: Element/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSDK_X 1.0) * Ex: Element/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSdk2 1.0)
* *
* @param flavorDescription the flavor description * @param flavorDescription the flavor description
*/ */
@ -74,13 +74,13 @@ internal class UserAgentHolder @Inject constructor(private val context: Context,
// if there is no user agent or cannot parse it // if there is no user agent or cannot parse it
if (null == systemUserAgent || systemUserAgent.lastIndexOf(")") == -1 || !systemUserAgent.contains("(")) { if (null == systemUserAgent || systemUserAgent.lastIndexOf(")") == -1 || !systemUserAgent.contains("(")) {
userAgent = (appName + "/" + appVersion + " ( Flavour " + flavorDescription userAgent = (appName + "/" + appVersion + " ( Flavour " + flavorDescription
+ "; MatrixAndroidSDK_X " + BuildConfig.VERSION_NAME + ")") + "; MatrixAndroidSdk2 " + BuildConfig.SDK_VERSION + ")")
} else { } else {
// update // update
userAgent = appName + "/" + appVersion + " " + userAgent = appName + "/" + appVersion + " " +
systemUserAgent.substring(systemUserAgent.indexOf("("), systemUserAgent.lastIndexOf(")") - 1) + systemUserAgent.substring(systemUserAgent.indexOf("("), systemUserAgent.lastIndexOf(")") - 1) +
"; Flavour " + flavorDescription + "; Flavour " + flavorDescription +
"; MatrixAndroidSDK_X " + BuildConfig.VERSION_NAME + ")" "; MatrixAndroidSdk2 " + BuildConfig.SDK_VERSION + ")"
} }
} }
} }

View file

@ -40,7 +40,7 @@ import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.group.GroupService import org.matrix.android.sdk.api.session.group.GroupService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.media.MediaService
import org.matrix.android.sdk.api.session.openid.OpenIdService import org.matrix.android.sdk.api.session.openid.OpenIdService
@ -115,7 +115,7 @@ internal class DefaultSession @Inject constructor(
private val contentUploadProgressTracker: ContentUploadStateTracker, private val contentUploadProgressTracker: ContentUploadStateTracker,
private val typingUsersTracker: TypingUsersTracker, private val typingUsersTracker: TypingUsersTracker,
private val contentDownloadStateTracker: ContentDownloadStateTracker, private val contentDownloadStateTracker: ContentDownloadStateTracker,
private val initialSyncProgressService: Lazy<InitialSyncProgressService>, private val syncStatusService: Lazy<SyncStatusService>,
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>, private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
private val accountDataService: Lazy<SessionAccountDataService>, private val accountDataService: Lazy<SessionAccountDataService>,
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>, private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
@ -141,7 +141,7 @@ internal class DefaultSession @Inject constructor(
PushersService by pushersService.get(), PushersService by pushersService.get(),
EventService by eventService.get(), EventService by eventService.get(),
TermsService by termsService.get(), TermsService by termsService.get(),
InitialSyncProgressService by initialSyncProgressService.get(), SyncStatusService by syncStatusService.get(),
SecureStorageService by secureStorageService.get(), SecureStorageService by secureStorageService.get(),
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(), HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
ProfileService by profileService.get(), ProfileService by profileService.get(),

View file

@ -43,7 +43,7 @@ import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationMan
import org.matrix.android.sdk.internal.session.media.MediaModule import org.matrix.android.sdk.internal.session.media.MediaModule
import org.matrix.android.sdk.internal.session.openid.OpenIdModule import org.matrix.android.sdk.internal.session.openid.OpenIdModule
import org.matrix.android.sdk.internal.session.profile.ProfileModule import org.matrix.android.sdk.internal.session.profile.ProfileModule
import org.matrix.android.sdk.internal.session.pushers.AddHttpPusherWorker import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
import org.matrix.android.sdk.internal.session.pushers.PushersModule import org.matrix.android.sdk.internal.session.pushers.PushersModule
import org.matrix.android.sdk.internal.session.room.RoomModule import org.matrix.android.sdk.internal.session.room.RoomModule
import org.matrix.android.sdk.internal.session.room.relation.SendRelationWorker import org.matrix.android.sdk.internal.session.room.relation.SendRelationWorker
@ -127,7 +127,7 @@ internal interface SessionComponent {
fun inject(worker: SyncWorker) fun inject(worker: SyncWorker)
fun inject(worker: AddHttpPusherWorker) fun inject(worker: AddPusherWorker)
fun inject(worker: SendVerificationMessageWorker) fun inject(worker: SendVerificationMessageWorker)

View file

@ -37,7 +37,7 @@ import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.events.EventService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.openid.OpenIdService import org.matrix.android.sdk.api.session.openid.OpenIdService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
@ -81,7 +81,7 @@ import org.matrix.android.sdk.internal.session.download.DownloadProgressIntercep
import org.matrix.android.sdk.internal.session.events.DefaultEventService import org.matrix.android.sdk.internal.session.events.DefaultEventService
import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService
import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService import org.matrix.android.sdk.internal.session.initsync.DefaultSyncStatusService
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService
import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
@ -355,7 +355,7 @@ internal abstract class SessionModule {
abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver
@Binds @Binds
abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService abstract fun bindSyncStatusService(service: DefaultSyncStatusService): SyncStatusService
@Binds @Binds
abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService

View file

@ -20,10 +20,16 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.LifecycleRegistry
import dagger.Lazy import dagger.Lazy
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.identity.FoundThreePid import org.matrix.android.sdk.api.session.identity.FoundThreePid
@ -36,23 +42,17 @@ import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
import org.matrix.android.sdk.internal.extensions.observeNotNull import org.matrix.android.sdk.internal.extensions.observeNotNull
import org.matrix.android.sdk.internal.network.RetrofitFactory import org.matrix.android.sdk.internal.network.RetrofitFactory
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.identity.data.IdentityStore import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask
import org.matrix.android.sdk.internal.session.profile.BindThreePidsTask import org.matrix.android.sdk.internal.session.profile.BindThreePidsTask
import org.matrix.android.sdk.internal.session.profile.UnbindThreePidsTask import org.matrix.android.sdk.internal.session.profile.UnbindThreePidsTask
import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentityServerContent import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentityServerContent
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.ensureProtocol import org.matrix.android.sdk.internal.util.ensureProtocol
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
@ -202,6 +202,8 @@ internal class DefaultIdentityService @Inject constructor(
identityStore.setUrl(urlCandidate) identityStore.setUrl(urlCandidate)
identityStore.setToken(token) identityStore.setToken(token)
// could we remember if it was previously given?
identityStore.setUserConsent(false)
updateIdentityAPI(urlCandidate) updateIdentityAPI(urlCandidate)
updateAccountData(urlCandidate) updateAccountData(urlCandidate)
@ -230,6 +232,8 @@ internal class DefaultIdentityService @Inject constructor(
} }
override suspend fun lookUp(threePids: List<ThreePid>): List<FoundThreePid> { override suspend fun lookUp(threePids: List<ThreePid>): List<FoundThreePid> {
if (getCurrentIdentityServerUrl() == null) throw IdentityServiceError.NoIdentityServerConfigured
if (!getUserConsent()) { if (!getUserConsent()) {
throw IdentityServiceError.UserConsentNotProvided throw IdentityServiceError.UserConsentNotProvided
} }

View file

@ -18,23 +18,28 @@ package org.matrix.android.sdk.internal.session.initsync
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import org.matrix.android.sdk.api.session.initsync.InitSyncStep import org.matrix.android.sdk.api.session.initsync.InitSyncStep
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import javax.inject.Inject import javax.inject.Inject
@SessionScope @SessionScope
internal class DefaultInitialSyncProgressService @Inject constructor() internal class DefaultSyncStatusService @Inject constructor()
: InitialSyncProgressService, : SyncStatusService,
ProgressReporter { ProgressReporter {
private val status = MutableLiveData<InitialSyncProgressService.Status>() private val status = MutableLiveData<SyncStatusService.Status>()
private var rootTask: TaskInfo? = null private var rootTask: TaskInfo? = null
override fun getInitialSyncProgressStatus(): LiveData<InitialSyncProgressService.Status> { override fun getSyncStatusLive(): LiveData<SyncStatusService.Status> {
return status return status
} }
// Only to be used for incremental sync
fun setStatus(newStatus: SyncStatusService.Status.IncrementalSyncStatus) {
status.postValue(newStatus)
}
/** /**
* Create a rootTask * Create a rootTask
*/ */
@ -67,7 +72,7 @@ internal class DefaultInitialSyncProgressService @Inject constructor()
// Update the progress of the leaf and all its parents // Update the progress of the leaf and all its parents
leaf.setProgress(progress) leaf.setProgress(progress)
// Then update the live data using leaf wording and root progress // Then update the live data using leaf wording and root progress
status.postValue(InitialSyncProgressService.Status.Progressing(leaf.initSyncStep, root.currentProgress.toInt())) status.postValue(SyncStatusService.Status.Progressing(leaf.initSyncStep, root.currentProgress.toInt()))
} }
} }
} }
@ -82,13 +87,13 @@ internal class DefaultInitialSyncProgressService @Inject constructor()
// And close it // And close it
endedTask.parent.child = null endedTask.parent.child = null
} else { } else {
status.postValue(InitialSyncProgressService.Status.Idle) status.postValue(SyncStatusService.Status.Idle)
} }
} }
} }
fun endAll() { fun endAll() {
rootTask = null rootTask = null
status.postValue(InitialSyncProgressService.Status.Idle) status.postValue(SyncStatusService.Status.Idle)
} }
} }

View file

@ -33,8 +33,8 @@ import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import javax.inject.Inject import javax.inject.Inject
internal class AddHttpPusherWorker(context: Context, params: WorkerParameters) internal class AddPusherWorker(context: Context, params: WorkerParameters)
: SessionSafeCoroutineWorker<AddHttpPusherWorker.Params>(context, params, Params::class.java) { : SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( internal data class Params(

View file

@ -66,27 +66,45 @@ internal class DefaultPushersService @Inject constructor(
deviceDisplayName: String, deviceDisplayName: String,
url: String, url: String,
append: Boolean, append: Boolean,
withEventIdOnly: Boolean) withEventIdOnly: Boolean
: UUID { ) = addPusher(
// Do some parameter checks. It's ok to throw Exception, to inform developer of the problem JsonPusher(
if (pushkey.length > 512) throw InvalidParameterException("pushkey should not exceed 512 chars") pushKey = pushkey,
if (appId.length > 64) throw InvalidParameterException("appId should not exceed 64 chars") kind = Pusher.KIND_HTTP,
if ("/_matrix/push/v1/notify" !in url) throw InvalidParameterException("url should contain '/_matrix/push/v1/notify'") appId = appId,
profileTag = profileTag,
lang = lang,
appDisplayName = appDisplayName,
deviceDisplayName = deviceDisplayName,
data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }),
append = append
)
)
val pusher = JsonPusher( override fun addEmailPusher(email: String,
pushKey = pushkey, lang: String,
kind = "http", emailBranding: String,
appId = appId, appDisplayName: String,
appDisplayName = appDisplayName, deviceDisplayName: String,
deviceDisplayName = deviceDisplayName, append: Boolean
profileTag = profileTag, ) = addPusher(
lang = lang, JsonPusher(
data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }), pushKey = email,
append = append) kind = Pusher.KIND_EMAIL,
appId = Pusher.APP_ID_EMAIL,
profileTag = "",
lang = lang,
appDisplayName = appDisplayName,
deviceDisplayName = deviceDisplayName,
data = JsonPusherData(brand = emailBranding),
append = append
)
)
val params = AddHttpPusherWorker.Params(sessionId, pusher) private fun addPusher(pusher: JsonPusher): UUID {
pusher.validateParameters()
val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddHttpPusherWorker>() val params = AddPusherWorker.Params(sessionId, pusher)
val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddPusherWorker>()
.setConstraints(WorkManagerProvider.workConstraints) .setConstraints(WorkManagerProvider.workConstraints)
.setInputData(WorkerParamsFactory.toData(params)) .setInputData(WorkerParamsFactory.toData(params))
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
@ -95,8 +113,27 @@ internal class DefaultPushersService @Inject constructor(
return request.id return request.id
} }
private fun JsonPusher.validateParameters() {
// Do some parameter checks. It's ok to throw Exception, to inform developer of the problem
if (pushKey.length > 512) throw InvalidParameterException("pushkey should not exceed 512 chars")
if (appId.length > 64) throw InvalidParameterException("appId should not exceed 64 chars")
data?.url?.let { url -> if ("/_matrix/push/v1/notify" !in url) throw InvalidParameterException("url should contain '/_matrix/push/v1/notify'") }
}
override suspend fun removePusher(pusher: Pusher) {
removePusher(pusher.pushKey, pusher.appId)
}
override suspend fun removeHttpPusher(pushkey: String, appId: String) { override suspend fun removeHttpPusher(pushkey: String, appId: String) {
val params = RemovePusherTask.Params(pushkey, appId) removePusher(pushkey, appId)
}
override suspend fun removeEmailPusher(email: String) {
removePusher(pushKey = email, Pusher.APP_ID_EMAIL)
}
private suspend fun removePusher(pushKey: String, pushAppId: String) {
val params = RemovePusherTask.Params(pushKey, pushAppId)
removePusherTask.execute(params) removePusherTask.execute(params)
} }

View file

@ -32,5 +32,8 @@ internal data class JsonPusherData(
* Currently the only format available is 'event_id_only'. * Currently the only format available is 'event_id_only'.
*/ */
@Json(name = "format") @Json(name = "format")
val format: String? = null val format: String? = null,
@Json(name = "brand")
val brand: String? = null
) )

View file

@ -0,0 +1,42 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.room
import org.matrix.android.sdk.api.session.room.model.RoomStrippedState
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface GetRoomSummaryTask : Task<GetRoomSummaryTask.Params, RoomStrippedState> {
data class Params(
val roomId: String,
val viaServers: List<String>?
)
}
internal class DefaultGetRoomSummaryTask @Inject constructor(
private val roomAPI: RoomAPI,
private val globalErrorReceiver: GlobalErrorReceiver
) : GetRoomSummaryTask {
override suspend fun execute(params: GetRoomSummaryTask.Params): RoomStrippedState {
return executeRequest(globalErrorReceiver) {
roomAPI.getRoomSummary(params.roomId, params.viaServers)
}
}
}

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomStrippedState
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
@ -254,7 +255,7 @@ internal interface RoomAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}")
suspend fun join(@Path("roomIdOrAlias") roomIdOrAlias: String, suspend fun join(@Path("roomIdOrAlias") roomIdOrAlias: String,
@Query("server_name") viaServers: List<String>, @Query("server_name") viaServers: List<String>,
@Body params: JsonDict): JoinRoomResponse @Body params: JsonDict): JoinRoomResponse
/** /**
* Leave the given room. * Leave the given room.
@ -381,4 +382,14 @@ internal interface RoomAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade")
suspend fun upgradeRoom(@Path("roomId") roomId: String, suspend fun upgradeRoom(@Path("roomId") roomId: String,
@Body body: RoomUpgradeBody): RoomUpgradeResponse @Body body: RoomUpgradeBody): RoomUpgradeResponse
/**
* The API returns the summary of the specified room, if the room could be found and the client should be able to view
* its contents according to the join_rules, history visibility, space membership and similar rules outlined in MSC3173
* as well as if the user is already a member of that room.
* https://github.com/deepbluev7/matrix-doc/blob/room-summaries/proposals/3266-room-summary.md
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "im.nheko.summary/rooms/{roomIdOrAlias}/summary")
suspend fun getRoomSummary(@Path("roomIdOrAlias") roomidOrAlias: String,
@Query("via") viaServers: List<String>?): RoomStrippedState
} }

View file

@ -258,4 +258,7 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindSign3pidInvitationTask(task: DefaultSign3pidInvitationTask): Sign3pidInvitationTask abstract fun bindSign3pidInvitationTask(task: DefaultSign3pidInvitationTask): Sign3pidInvitationTask
@Binds
abstract fun bindGetRoomSummaryTask(task: DefaultGetRoomSummaryTask): GetRoomSummaryTask
} }

View file

@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFi
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.room.peeking.PeekResult
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.internal.session.room.GetRoomSummaryTask
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
@ -49,6 +50,7 @@ internal class DefaultPeekRoomTask @Inject constructor(
private val getRoomIdByAliasTask: GetRoomIdByAliasTask, private val getRoomIdByAliasTask: GetRoomIdByAliasTask,
private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask, private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask,
private val getPublicRoomTask: GetPublicRoomTask, private val getPublicRoomTask: GetPublicRoomTask,
private val getRoomSummaryTask: GetRoomSummaryTask,
private val resolveRoomStateTask: ResolveRoomStateTask private val resolveRoomStateTask: ResolveRoomStateTask
) : PeekRoomTask { ) : PeekRoomTask {
@ -70,6 +72,25 @@ internal class DefaultPeekRoomTask @Inject constructor(
serverList = emptyList() serverList = emptyList()
} }
// If the room summary API is available on the Home Server we should try it first
val strippedState = tryOrNull("Failed to get room stripped state roomId:$roomId") {
getRoomSummaryTask.execute(GetRoomSummaryTask.Params(roomId, serverList))
}
if (strippedState != null) {
return PeekResult.Success(
roomId = strippedState.roomId,
alias = strippedState.getPrimaryAlias() ?: params.roomIdOrAlias.takeIf { isAlias },
avatarUrl = strippedState.avatarUrl,
name = strippedState.name,
topic = strippedState.topic,
numJoinedMembers = strippedState.numJoinedMembers,
viaServers = serverList,
roomType = strippedState.roomType,
someMembers = null,
isPublic = strippedState.worldReadable
)
}
// Is it a public room? // Is it a public room?
val visibilityRes = tryOrNull("## PEEK: failed to get visibility") { val visibilityRes = tryOrNull("## PEEK: failed to get visibility") {
getRoomDirectoryVisibilityTask.execute(GetRoomDirectoryVisibilityTask.Params(roomId)) getRoomDirectoryVisibilityTask.execute(GetRoomDirectoryVisibilityTask.Params(roomId))

View file

@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
@ -31,6 +32,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.model.VersioningState import org.matrix.android.sdk.api.session.room.model.VersioningState
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.EventDecryptor import org.matrix.android.sdk.internal.crypto.EventDecryptor
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
@ -226,63 +228,102 @@ internal class RoomSummaryUpdater @Inject constructor(
} }
.toMap() .toMap()
lookupMap.keys.forEach { lookedUp -> // First handle child relations
if (lookedUp.roomType == RoomType.SPACE) { lookupMap.keys.asSequence()
// get childrens .filter { it.roomType == RoomType.SPACE }
.forEach { lookedUp ->
// get childrens
lookedUp.children.clearWith { it.deleteFromRealm() } lookedUp.children.clearWith { it.deleteFromRealm() }
RoomChildRelationInfo(realm, lookedUp.roomId).getDirectChildrenDescriptions().forEach { child -> RoomChildRelationInfo(realm, lookedUp.roomId).getDirectChildrenDescriptions().forEach { child ->
lookedUp.children.add( lookedUp.children.add(
realm.createObject<SpaceChildSummaryEntity>().apply { realm.createObject<SpaceChildSummaryEntity>().apply {
this.childRoomId = child.roomId this.childRoomId = child.roomId
this.childSummaryEntity = RoomSummaryEntity.where(realm, child.roomId).findFirst() this.childSummaryEntity = RoomSummaryEntity.where(realm, child.roomId).findFirst()
this.order = child.order this.order = child.order
// this.autoJoin = child.autoJoin // this.autoJoin = child.autoJoin
this.viaServers.addAll(child.viaServers) this.viaServers.addAll(child.viaServers)
} }
) )
RoomSummaryEntity.where(realm, child.roomId) RoomSummaryEntity.where(realm, child.roomId)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships()) .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
.findFirst() .findFirst()
?.let { childSum -> ?.let { childSum ->
lookupMap.entries.firstOrNull { it.key.roomId == lookedUp.roomId }?.let { entry -> lookupMap.entries.firstOrNull { it.key.roomId == lookedUp.roomId }?.let { entry ->
if (entry.value.indexOfFirst { it.roomId == childSum.roomId } == -1) { if (entry.value.indexOfFirst { it.roomId == childSum.roomId } == -1) {
// add looked up as a parent // add looked up as a parent
entry.value.add(childSum) entry.value.add(childSum)
}
} }
} }
}
}
// Now let's check parent relations
lookupMap.keys
.forEach { lookedUp ->
lookedUp.parents.clearWith { it.deleteFromRealm() }
// can we check parent relations here??
/**
* rooms can claim parents via the m.space.parent state event.
* canonical determines whether this is the main parent for the space.
*
* To avoid abuse where a room admin falsely claims that a room is part of a space that it should not be,
* clients could ignore such m.space.parent events unless either
* (a) there is a corresponding m.space.child event in the claimed parent, or
* (b) the sender of the m.space.child event has a sufficient power-level to send such an m.space.child event in the parent.
* (It is not necessarily required that that user currently be a member of the parent room -
* only the m.room.power_levels event is inspected.)
* [Checking the power-level rather than requiring an actual m.space.child event in the parent allows for "secret" rooms (see below).]
*/
RoomChildRelationInfo(realm, lookedUp.roomId).getParentDescriptions()
.map { parentInfo ->
// Is it a valid parent relation?
// Check if it's a child of the parent?
val isValidRelation: Boolean
val parent = lookupMap.firstNotNullOfOrNull { if (it.key.roomId == parentInfo.roomId) it.value else null }
if (parent?.firstOrNull { it.roomId == lookedUp.roomId } != null) {
// there is a corresponding m.space.child event in the claimed parent
isValidRelation = true
} else {
// check if sender can post child relation in parent?
val senderId = parentInfo.stateEventSender
val parentRoomId = parentInfo.roomId
val powerLevelsHelper = CurrentStateEventEntity
.getOrNull(realm, parentRoomId, "", EventType.STATE_ROOM_POWER_LEVELS)
?.root
?.let { ContentMapper.map(it.content).toModel<PowerLevelsContent>() }
?.let { PowerLevelsHelper(it) }
isValidRelation = powerLevelsHelper?.isUserAllowedToSend(senderId, true, EventType.STATE_SPACE_CHILD) ?: false
}
if (isValidRelation) {
lookedUp.parents.add(
realm.createObject<SpaceParentSummaryEntity>().apply {
this.parentRoomId = parentInfo.roomId
this.parentSummaryEntity = RoomSummaryEntity.where(realm, parentInfo.roomId).findFirst()
this.canonical = parentInfo.canonical
this.viaServers.addAll(parentInfo.viaServers)
}
)
RoomSummaryEntity.where(realm, parentInfo.roomId)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
.findFirst()
?.let { parentSum ->
if (lookupMap[parentSum]?.indexOfFirst { it.roomId == lookedUp.roomId } == -1) {
// add lookedup as a parent
lookupMap[parentSum]?.add(lookedUp)
}
}
}
} }
} }
} else {
lookedUp.parents.clearWith { it.deleteFromRealm() }
// can we check parent relations here??
RoomChildRelationInfo(realm, lookedUp.roomId).getParentDescriptions()
.map { parentInfo ->
lookedUp.parents.add(
realm.createObject<SpaceParentSummaryEntity>().apply {
this.parentRoomId = parentInfo.roomId
this.parentSummaryEntity = RoomSummaryEntity.where(realm, parentInfo.roomId).findFirst()
this.canonical = parentInfo.canonical
this.viaServers.addAll(parentInfo.viaServers)
}
)
RoomSummaryEntity.where(realm, parentInfo.roomId)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
.findFirst()
?.let { parentSum ->
if (lookupMap[parentSum]?.indexOfFirst { it.roomId == lookedUp.roomId } == -1) {
// add lookedup as a parent
lookupMap[parentSum]?.add(lookedUp)
}
}
}
}
}
// Simple algorithm to break cycles // Simple algorithm to break cycles
// Need more work to decide how to break, probably need to be as consistent as possible // Need more work to decide how to break, probably need to be as consistent as possible

View file

@ -89,7 +89,6 @@ internal class DefaultSpace(
body = SpaceChildContent( body = SpaceChildContent(
order = null, order = null,
via = null, via = null,
// autoJoin = null,
suggested = null suggested = null
).toContent() ).toContent()
) )

View file

@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.api.session.room.powerlevels.Role
import org.matrix.android.sdk.api.session.space.CreateSpaceParams import org.matrix.android.sdk.api.session.space.CreateSpaceParams
import org.matrix.android.sdk.api.session.space.JoinSpaceResult import org.matrix.android.sdk.api.session.space.JoinSpaceResult
import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.session.space.Space
@ -77,7 +78,7 @@ internal class DefaultSpaceService @Inject constructor(
if (isPublic) { if (isPublic) {
this.roomAliasName = roomAliasLocalPart this.roomAliasName = roomAliasLocalPart
this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy( this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy(
invite = 0 invite = if (isPublic) Role.Default.value else Role.Moderator.value
) )
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE
@ -221,4 +222,23 @@ internal class DefaultSpaceService @Inject constructor(
).toContent() ).toContent()
) )
} }
override suspend fun removeSpaceParent(childRoomId: String, parentSpaceId: String) {
val room = roomGetter.getRoom(childRoomId)
?: throw IllegalArgumentException("Unknown Room $childRoomId")
val existingEvent = room.getStateEvent(EventType.STATE_SPACE_PARENT, QueryStringValue.Equals(parentSpaceId))
if (existingEvent != null) {
// Should i check if it was sent by me?
// we don't check power level, it will throw if you cannot do that
room.sendStateEvent(
eventType = EventType.STATE_SPACE_PARENT,
stateKey = parentSpaceId,
body = SpaceParentContent(
via = null,
canonical = null
).toContent()
)
}
}
} }

View file

@ -17,7 +17,9 @@
package org.matrix.android.sdk.internal.session.sync package org.matrix.android.sdk.internal.session.sync
import okhttp3.ResponseBody import okhttp3.ResponseBody
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.initsync.InitSyncStep import org.matrix.android.sdk.api.session.initsync.InitSyncStep
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.internal.di.SessionFilesDirectory import org.matrix.android.sdk.internal.di.SessionFilesDirectory
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
@ -26,7 +28,7 @@ import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.network.toFailure import org.matrix.android.sdk.internal.network.toFailure
import org.matrix.android.sdk.internal.session.filter.FilterRepository import org.matrix.android.sdk.internal.session.filter.FilterRepository
import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilitiesTask import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilitiesTask
import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService import org.matrix.android.sdk.internal.session.initsync.DefaultSyncStatusService
import org.matrix.android.sdk.internal.session.initsync.reportSubtask import org.matrix.android.sdk.internal.session.initsync.reportSubtask
import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral
import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser
@ -40,6 +42,8 @@ import java.io.File
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import javax.inject.Inject import javax.inject.Inject
private val loggerTag = LoggerTag("SyncTask", LoggerTag.SYNC)
internal interface SyncTask : Task<SyncTask.Params, Unit> { internal interface SyncTask : Task<SyncTask.Params, Unit> {
data class Params( data class Params(
@ -53,7 +57,7 @@ internal class DefaultSyncTask @Inject constructor(
@UserId private val userId: String, @UserId private val userId: String,
private val filterRepository: FilterRepository, private val filterRepository: FilterRepository,
private val syncResponseHandler: SyncResponseHandler, private val syncResponseHandler: SyncResponseHandler,
private val initialSyncProgressService: DefaultInitialSyncProgressService, private val defaultSyncStatusService: DefaultSyncStatusService,
private val syncTokenStore: SyncTokenStore, private val syncTokenStore: SyncTokenStore,
private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask,
private val userStore: UserStore, private val userStore: UserStore,
@ -75,7 +79,7 @@ internal class DefaultSyncTask @Inject constructor(
} }
private suspend fun doSync(params: SyncTask.Params) { private suspend fun doSync(params: SyncTask.Params) {
Timber.v("Sync task started on Thread: ${Thread.currentThread().name}") Timber.tag(loggerTag.value).d("Sync task started on Thread: ${Thread.currentThread().name}")
val requestParams = HashMap<String, String>() val requestParams = HashMap<String, String>()
var timeout = 0L var timeout = 0L
@ -92,7 +96,7 @@ internal class DefaultSyncTask @Inject constructor(
if (isInitialSync) { if (isInitialSync) {
// We might want to get the user information in parallel too // We might want to get the user information in parallel too
userStore.createOrUpdate(userId) userStore.createOrUpdate(userId)
initialSyncProgressService.startRoot(InitSyncStep.ImportingAccount, 100) defaultSyncStatusService.startRoot(InitSyncStep.ImportingAccount, 100)
} }
// Maybe refresh the homeserver capabilities data we know // Maybe refresh the homeserver capabilities data we know
getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false)) getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false))
@ -100,20 +104,20 @@ internal class DefaultSyncTask @Inject constructor(
val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT) val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT)
if (isInitialSync) { if (isInitialSync) {
Timber.d("INIT_SYNC with filter: ${requestParams["filter"]}") Timber.tag(loggerTag.value).d("INIT_SYNC with filter: ${requestParams["filter"]}")
val initSyncStrategy = initialSyncStrategy val initSyncStrategy = initialSyncStrategy
logDuration("INIT_SYNC strategy: $initSyncStrategy") { logDuration("INIT_SYNC strategy: $initSyncStrategy", loggerTag) {
if (initSyncStrategy is InitialSyncStrategy.Optimized) { if (initSyncStrategy is InitialSyncStrategy.Optimized) {
roomSyncEphemeralTemporaryStore.reset() roomSyncEphemeralTemporaryStore.reset()
workingDir.mkdirs() workingDir.mkdirs()
val file = downloadInitSyncResponse(requestParams) val file = downloadInitSyncResponse(requestParams)
reportSubtask(initialSyncProgressService, InitSyncStep.ImportingAccount, 1, 0.7F) { reportSubtask(defaultSyncStatusService, InitSyncStep.ImportingAccount, 1, 0.7F) {
handleSyncFile(file, initSyncStrategy) handleSyncFile(file, initSyncStrategy)
} }
// Delete all files // Delete all files
workingDir.deleteRecursively() workingDir.deleteRecursively()
} else { } else {
val syncResponse = logDuration("INIT_SYNC Request") { val syncResponse = logDuration("INIT_SYNC Request", loggerTag) {
executeRequest(globalErrorReceiver) { executeRequest(globalErrorReceiver) {
syncAPI.sync( syncAPI.sync(
params = requestParams, params = requestParams,
@ -122,43 +126,60 @@ internal class DefaultSyncTask @Inject constructor(
} }
} }
logDuration("INIT_SYNC Database insertion") { logDuration("INIT_SYNC Database insertion", loggerTag) {
syncResponseHandler.handleResponse(syncResponse, token, initialSyncProgressService) syncResponseHandler.handleResponse(syncResponse, token, defaultSyncStatusService)
} }
} }
} }
initialSyncProgressService.endAll() defaultSyncStatusService.endAll()
} else { } else {
val syncResponse = executeRequest(globalErrorReceiver) { Timber.tag(loggerTag.value).d("Start incremental sync request")
syncAPI.sync( defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncIdle)
params = requestParams, val syncResponse = try {
readTimeOut = readTimeOut executeRequest(globalErrorReceiver) {
) syncAPI.sync(
params = requestParams,
readTimeOut = readTimeOut
)
}
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "Incremental sync request error")
defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncError)
throw throwable
} }
val nbRooms = syncResponse.rooms?.invite.orEmpty().size + syncResponse.rooms?.join.orEmpty().size + syncResponse.rooms?.leave.orEmpty().size
val nbToDevice = syncResponse.toDevice?.events.orEmpty().size
Timber.tag(loggerTag.value).d("Incremental sync request parsing, $nbRooms room(s) $nbToDevice toDevice(s)")
defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncParsing(
rooms = nbRooms,
toDevice = nbToDevice
))
syncResponseHandler.handleResponse(syncResponse, token, null) syncResponseHandler.handleResponse(syncResponse, token, null)
Timber.tag(loggerTag.value).d("Incremental sync done")
defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncDone)
} }
Timber.v("Sync task finished on Thread: ${Thread.currentThread().name}") Timber.tag(loggerTag.value).d("Sync task finished on Thread: ${Thread.currentThread().name}")
} }
private suspend fun downloadInitSyncResponse(requestParams: Map<String, String>): File { private suspend fun downloadInitSyncResponse(requestParams: Map<String, String>): File {
val workingFile = File(workingDir, "initSync.json") val workingFile = File(workingDir, "initSync.json")
val status = initialSyncStatusRepository.getStep() val status = initialSyncStatusRepository.getStep()
if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) { if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) {
Timber.d("INIT_SYNC file is already here") Timber.tag(loggerTag.value).d("INIT_SYNC file is already here")
reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.3f) { reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.3f) {
// Empty task // Empty task
} }
} else { } else {
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADING) initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADING)
val syncResponse = logDuration("INIT_SYNC Perform server request") { val syncResponse = logDuration("INIT_SYNC Perform server request", loggerTag) {
reportSubtask(initialSyncProgressService, InitSyncStep.ServerComputing, 1, 0.2f) { reportSubtask(defaultSyncStatusService, InitSyncStep.ServerComputing, 1, 0.2f) {
getSyncResponse(requestParams, MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT) getSyncResponse(requestParams, MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT)
} }
} }
if (syncResponse.isSuccessful) { if (syncResponse.isSuccessful) {
logDuration("INIT_SYNC Download and save to file") { logDuration("INIT_SYNC Download and save to file", loggerTag) {
reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.1f) { reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.1f) {
syncResponse.body()?.byteStream()?.use { inputStream -> syncResponse.body()?.byteStream()?.use { inputStream ->
workingFile.outputStream().use { outputStream -> workingFile.outputStream().use { outputStream ->
inputStream.copyTo(outputStream) inputStream.copyTo(outputStream)
@ -168,7 +189,7 @@ internal class DefaultSyncTask @Inject constructor(
} }
} else { } else {
throw syncResponse.toFailure(globalErrorReceiver) throw syncResponse.toFailure(globalErrorReceiver)
.also { Timber.w("INIT_SYNC request failure: $this") } .also { Timber.tag(loggerTag.value).w("INIT_SYNC request failure: $this") }
} }
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADED) initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADED)
} }
@ -185,9 +206,9 @@ internal class DefaultSyncTask @Inject constructor(
).awaitResponse() ).awaitResponse()
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
if (throwable is SocketTimeoutException && retry > 0) { if (throwable is SocketTimeoutException && retry > 0) {
Timber.w("INIT_SYNC timeout retry left: $retry") Timber.tag(loggerTag.value).w("INIT_SYNC timeout retry left: $retry")
} else { } else {
Timber.e(throwable, "INIT_SYNC timeout, no retry left, or other error") Timber.tag(loggerTag.value).e(throwable, "INIT_SYNC timeout, no retry left, or other error")
throw throwable throw throwable
} }
} }
@ -195,18 +216,18 @@ internal class DefaultSyncTask @Inject constructor(
} }
private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized) { private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized) {
logDuration("INIT_SYNC handleSyncFile()") { logDuration("INIT_SYNC handleSyncFile()", loggerTag) {
val syncResponse = logDuration("INIT_SYNC Read file and parse") { val syncResponse = logDuration("INIT_SYNC Read file and parse", loggerTag) {
syncResponseParser.parse(initSyncStrategy, workingFile) syncResponseParser.parse(initSyncStrategy, workingFile)
} }
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_PARSED) initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_PARSED)
// Log some stats // Log some stats
val nbOfJoinedRooms = syncResponse.rooms?.join?.size ?: 0 val nbOfJoinedRooms = syncResponse.rooms?.join?.size ?: 0
val nbOfJoinedRoomsInFile = syncResponse.rooms?.join?.values?.count { it.ephemeral is LazyRoomSyncEphemeral.Stored } val nbOfJoinedRoomsInFile = syncResponse.rooms?.join?.values?.count { it.ephemeral is LazyRoomSyncEphemeral.Stored }
Timber.d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files") Timber.tag(loggerTag.value).d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files")
logDuration("INIT_SYNC Database insertion") { logDuration("INIT_SYNC Database insertion", loggerTag) {
syncResponseHandler.handleResponse(syncResponse, null, initialSyncProgressService) syncResponseHandler.handleResponse(syncResponse, null, defaultSyncStatusService)
} }
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS) initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS)
} }

View file

@ -36,6 +36,7 @@ import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.internal.session.call.ActiveCallHandler import org.matrix.android.sdk.internal.session.call.ActiveCallHandler
import org.matrix.android.sdk.internal.session.sync.SyncPresence import org.matrix.android.sdk.internal.session.sync.SyncPresence
@ -49,6 +50,8 @@ import kotlin.concurrent.schedule
private const val RETRY_WAIT_TIME_MS = 10_000L private const val RETRY_WAIT_TIME_MS = 10_000L
private const val DEFAULT_LONG_POOL_TIMEOUT = 30_000L private const val DEFAULT_LONG_POOL_TIMEOUT = 30_000L
private val loggerTag = LoggerTag("SyncThread", LoggerTag.SYNC)
internal class SyncThread @Inject constructor(private val syncTask: SyncTask, internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
private val networkConnectivityChecker: NetworkConnectivityChecker, private val networkConnectivityChecker: NetworkConnectivityChecker,
private val backgroundDetectionObserver: BackgroundDetectionObserver, private val backgroundDetectionObserver: BackgroundDetectionObserver,
@ -83,7 +86,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
fun restart() = synchronized(lock) { fun restart() = synchronized(lock) {
if (!isStarted) { if (!isStarted) {
Timber.v("Resume sync...") Timber.tag(loggerTag.value).d("Resume sync...")
isStarted = true isStarted = true
// Check again server availability and the token validity // Check again server availability and the token validity
canReachServer = true canReachServer = true
@ -94,7 +97,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
fun pause() = synchronized(lock) { fun pause() = synchronized(lock) {
if (isStarted) { if (isStarted) {
Timber.v("Pause sync...") Timber.tag(loggerTag.value).d("Pause sync...")
isStarted = false isStarted = false
retryNoNetworkTask?.cancel() retryNoNetworkTask?.cancel()
syncScope.coroutineContext.cancelChildren() syncScope.coroutineContext.cancelChildren()
@ -102,7 +105,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
} }
fun kill() = synchronized(lock) { fun kill() = synchronized(lock) {
Timber.v("Kill sync...") Timber.tag(loggerTag.value).d("Kill sync...")
updateStateTo(SyncState.Killing) updateStateTo(SyncState.Killing)
retryNoNetworkTask?.cancel() retryNoNetworkTask?.cancel()
syncScope.coroutineContext.cancelChildren() syncScope.coroutineContext.cancelChildren()
@ -124,21 +127,21 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
} }
override fun run() { override fun run() {
Timber.v("Start syncing...") Timber.tag(loggerTag.value).d("Start syncing...")
isStarted = true isStarted = true
networkConnectivityChecker.register(this) networkConnectivityChecker.register(this)
backgroundDetectionObserver.register(this) backgroundDetectionObserver.register(this)
registerActiveCallsObserver() registerActiveCallsObserver()
while (state != SyncState.Killing) { while (state != SyncState.Killing) {
Timber.v("Entering loop, state: $state") Timber.tag(loggerTag.value).d("Entering loop, state: $state")
if (!isStarted) { if (!isStarted) {
Timber.v("Sync is Paused. Waiting...") Timber.tag(loggerTag.value).d("Sync is Paused. Waiting...")
updateStateTo(SyncState.Paused) updateStateTo(SyncState.Paused)
synchronized(lock) { lock.wait() } synchronized(lock) { lock.wait() }
Timber.v("...unlocked") Timber.tag(loggerTag.value).d("...unlocked")
} else if (!canReachServer) { } else if (!canReachServer) {
Timber.v("No network. Waiting...") Timber.tag(loggerTag.value).d("No network. Waiting...")
updateStateTo(SyncState.NoNetwork) updateStateTo(SyncState.NoNetwork)
// We force retrying in RETRY_WAIT_TIME_MS maximum. Otherwise it will be unlocked by onConnectivityChanged() or restart() // We force retrying in RETRY_WAIT_TIME_MS maximum. Otherwise it will be unlocked by onConnectivityChanged() or restart()
retryNoNetworkTask = Timer(SyncState.NoNetwork.toString(), false).schedule(RETRY_WAIT_TIME_MS) { retryNoNetworkTask = Timer(SyncState.NoNetwork.toString(), false).schedule(RETRY_WAIT_TIME_MS) {
@ -148,19 +151,19 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
} }
} }
synchronized(lock) { lock.wait() } synchronized(lock) { lock.wait() }
Timber.v("...retry") Timber.tag(loggerTag.value).d("...retry")
} else if (!isTokenValid) { } else if (!isTokenValid) {
Timber.v("Token is invalid. Waiting...") Timber.tag(loggerTag.value).d("Token is invalid. Waiting...")
updateStateTo(SyncState.InvalidToken) updateStateTo(SyncState.InvalidToken)
synchronized(lock) { lock.wait() } synchronized(lock) { lock.wait() }
Timber.v("...unlocked") Timber.tag(loggerTag.value).d("...unlocked")
} else { } else {
if (state !is SyncState.Running) { if (state !is SyncState.Running) {
updateStateTo(SyncState.Running(afterPause = true)) updateStateTo(SyncState.Running(afterPause = true))
} }
// No timeout after a pause // No timeout after a pause
val timeout = state.let { if (it is SyncState.Running && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT } val timeout = state.let { if (it is SyncState.Running && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT }
Timber.v("Execute sync request with timeout $timeout") Timber.tag(loggerTag.value).d("Execute sync request with timeout $timeout")
val params = SyncTask.Params(timeout, SyncPresence.Online) val params = SyncTask.Params(timeout, SyncPresence.Online)
val sync = syncScope.launch { val sync = syncScope.launch {
doSync(params) doSync(params)
@ -168,10 +171,10 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
runBlocking { runBlocking {
sync.join() sync.join()
} }
Timber.v("...Continue") Timber.tag(loggerTag.value).d("...Continue")
} }
} }
Timber.v("Sync killed") Timber.tag(loggerTag.value).d("Sync killed")
updateStateTo(SyncState.Killed) updateStateTo(SyncState.Killed)
backgroundDetectionObserver.unregister(this) backgroundDetectionObserver.unregister(this)
networkConnectivityChecker.unregister(this) networkConnectivityChecker.unregister(this)
@ -199,19 +202,19 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
} }
if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) { if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) {
// Timeout are not critical // Timeout are not critical
Timber.v("Timeout") Timber.tag(loggerTag.value).d("Timeout")
} else if (failure is CancellationException) { } else if (failure is CancellationException) {
Timber.v("Cancelled") Timber.tag(loggerTag.value).d("Cancelled")
} else if (failure.isTokenError()) { } else if (failure.isTokenError()) {
// No token or invalid token, stop the thread // No token or invalid token, stop the thread
Timber.w(failure, "Token error") Timber.tag(loggerTag.value).w(failure, "Token error")
isStarted = false isStarted = false
isTokenValid = false isTokenValid = false
} else { } else {
Timber.e(failure) Timber.tag(loggerTag.value).e(failure)
if (failure !is Failure.NetworkConnection || failure.cause is JsonEncodingException) { if (failure !is Failure.NetworkConnection || failure.cause is JsonEncodingException) {
// Wait 10s before retrying // Wait 10s before retrying
Timber.v("Wait 10s") Timber.tag(loggerTag.value).d("Wait 10s")
delay(RETRY_WAIT_TIME_MS) delay(RETRY_WAIT_TIME_MS)
} }
} }
@ -225,7 +228,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
} }
private fun updateStateTo(newState: SyncState) { private fun updateStateTo(newState: SyncState) {
Timber.v("Update state from $state to $newState") Timber.tag(loggerTag.value).d("Update state from $state to $newState")
if (newState == state) { if (newState == state) {
return return
} }

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.util package org.matrix.android.sdk.internal.util
import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.logger.LoggerTag
import timber.log.Timber import timber.log.Timber
internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String { internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
@ -32,14 +33,15 @@ internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
} }
internal suspend fun <T> logDuration(message: String, internal suspend fun <T> logDuration(message: String,
loggerTag: LoggerTag,
block: suspend () -> T): T { block: suspend () -> T): T {
Timber.d("$message -- BEGIN") Timber.tag(loggerTag.value).d("$message -- BEGIN")
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
val result = logRamUsage(message) { val result = logRamUsage(message) {
block() block()
} }
val duration = System.currentTimeMillis() - start val duration = System.currentTimeMillis() - start
Timber.d("$message -- END duration: $duration ms") Timber.tag(loggerTag.value).d("$message -- END duration: $duration ms")
return result return result
} }

View file

@ -19,14 +19,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-parcelize'
android { android {
compileSdkVersion 30 compileSdk versions.compileSdk
defaultConfig { defaultConfig {
minSdkVersion 19 minSdk versions.minSdk
targetSdkVersion 30 targetSdk versions.targetSdk
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro' consumerProguardFiles 'consumer-rules.pro'
} }
@ -41,11 +38,11 @@ android {
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation libs.jetbrains.kotlinStdlibJdk7
implementation 'androidx.appcompat:appcompat:1.3.1' implementation libs.androidx.appCompat
implementation "androidx.fragment:fragment-ktx:1.3.6" implementation libs.androidx.fragmentKtx
implementation 'androidx.exifinterface:exifinterface:1.3.3' implementation libs.androidx.exifinterface
// Log // Log
implementation 'com.jakewharton.timber:timber:5.0.1' implementation libs.jakewharton.timber
} }

View file

@ -0,0 +1,39 @@
#
# Copyright 2021 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.
#
clean_assemble {
tasks = ["clean", ":vector:assembleGPlayDebug"]
}
clean_assemble_build_cache {
tasks = ["clean", ":vector:assembleGPlayDebug"]
gradle-args = ["--build-cache"]
}
clean_assemble_without_cache {
tasks = ["clean", ":vector:assembleGPlayDebug"]
gradle-args = ["--no-build-cache"]
}
incremental_assemble_sdk_abi {
tasks = [":vector:assembleGPlayDebug"]
apply-abi-change-to = "matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt"
}
incremental_assemble_sdk_no_abi {
tasks = [":vector:assembleGPlayDebug"]
apply-non-abi-change-to = "matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt"
}

View file

@ -0,0 +1,33 @@
#!/usr/bin/env bash
#
# Copyright 2021 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.
#
if ! command -v gradle-profiler &> /dev/null
then
echo "gradle-profiler could not be found https://github.com/gradle/gradle-profiler"
exit
fi
gradle-profiler \
--benchmark \
--project-dir . \
--scenario-file tools/benchmark/benchmark.profile \
--output-dir benchmark-out/output \
--gradle-user-home benchmark-out/gradle-home \
--warmups 3 \
--iterations 3 \
$1

View file

@ -162,7 +162,7 @@ Formatter\.formatShortFileSize===1
# android\.text\.TextUtils # android\.text\.TextUtils
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
enum class===105 enum class===106
### Do not import temporary legacy classes ### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3 import org.matrix.android.sdk.internal.legacy.riot===3

View file

@ -13,8 +13,8 @@ kapt {
// Note: 2 digits max for each value // Note: 2 digits max for each value
ext.versionMajor = 1 ext.versionMajor = 1
ext.versionMinor = 2 ext.versionMinor = 3
ext.versionPatch = 2 ext.versionPatch = 0
ext.scVersion = 44 ext.scVersion = 44
@ -104,17 +104,20 @@ ext.abiVersionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2, "x86": 3, "x86_64": 4].
def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0 def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0
android { android {
compileSdkVersion 30
// Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use // Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use
// Ref: https://issuetracker.google.com/issues/144111441 // Ref: https://issuetracker.google.com/issues/144111441
ndkVersion "21.3.6528147" ndkVersion "21.3.6528147"
compileSdk versions.compileSdk
defaultConfig { defaultConfig {
applicationId "de.spiritcroc.riotx" applicationId "de.spiritcroc.riotx"
// Set to API 21: see #405 // Set to API 21: see #405
minSdkVersion 21 minSdk versions.minSdk
targetSdkVersion 30 targetSdk versions.targetSdk
multiDexEnabled true multiDexEnabled true
renderscriptTargetApi 24 renderscriptTargetApi 24
@ -294,8 +297,8 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_11 sourceCompatibility versions.sourceCompat
targetCompatibility JavaVersion.VERSION_11 targetCompatibility versions.targetCompat
} }
kotlinOptions { kotlinOptions {
@ -322,26 +325,6 @@ android {
dependencies { dependencies {
def epoxy_version = '4.6.2'
def fragment_version = '1.3.6'
def arrow_version = "0.8.2"
def markwon_version = '4.1.2'
def big_image_viewer_version = '1.8.1'
def glide_version = '4.12.0'
def moshi_version = '1.12.0'
def daggerVersion = '2.38.1'
def autofill_version = "1.1.0"
def work_version = '2.5.0'
def arch_version = '2.1.0'
def lifecycle_version = '2.2.0'
def rxbinding_version = '3.1.0'
def jjwt_version = '0.11.2'
// Tests
def kluent_version = '1.68'
def androidxTest_version = '1.4.0'
def espresso_version = '3.4.0'
implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android")
implementation project(":matrix-sdk-android-rx") implementation project(":matrix-sdk-android-rx")
implementation project(":diff-match-patch") implementation project(":diff-match-patch")
@ -350,75 +333,79 @@ dependencies {
implementation project(":library:ui-styles") implementation project(":library:ui-styles")
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation libs.jetbrains.kotlinStdlibJdk7
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation libs.jetbrains.kotlinReflect
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation libs.jetbrains.coroutinesCore
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" implementation libs.jetbrains.coroutinesAndroid
implementation "androidx.recyclerview:recyclerview:1.2.1" implementation libs.androidx.recyclerview
implementation 'androidx.appcompat:appcompat:1.3.1' implementation libs.androidx.appCompat
implementation "androidx.fragment:fragment-ktx:$fragment_version" implementation libs.androidx.fragmentKtx
implementation 'androidx.constraintlayout:constraintlayout:2.1.0' implementation libs.androidx.constraintLayout
implementation "androidx.sharetarget:sharetarget:1.1.0" implementation "androidx.sharetarget:sharetarget:1.1.0"
implementation 'androidx.core:core-ktx:1.6.0' implementation libs.androidx.core
implementation "androidx.media:media:1.4.1" implementation "androidx.media:media:1.4.2"
implementation "androidx.transition:transition:1.4.1" implementation "androidx.transition:transition:1.4.1"
implementation "org.threeten:threetenbp:1.4.0:no-tzdb" implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0" implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0"
implementation "com.squareup.moshi:moshi-adapters:$moshi_version" implementation libs.squareup.moshi
implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" implementation libs.squareup.moshiKt
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" kapt libs.squareup.moshiKotlin
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" implementation libs.androidx.lifecycleExtensions
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" implementation libs.androidx.lifecycleLivedata
implementation libs.androidx.datastore
implementation libs.androidx.datastorepreferences
// Log // Log
implementation 'com.jakewharton.timber:timber:5.0.1' implementation libs.jakewharton.timber
// Debug // Debug
implementation 'com.facebook.stetho:stetho:1.6.0' implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.31' implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.33'
// rx // rx
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' implementation libs.rx.rxKotlin
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation libs.rx.rxAndroid
implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.1' implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.1'
// RXBinding // RXBinding
implementation "com.jakewharton.rxbinding3:rxbinding:$rxbinding_version" implementation libs.jakewharton.rxbinding
implementation "com.jakewharton.rxbinding3:rxbinding-appcompat:$rxbinding_version" implementation libs.jakewharton.rxbindingAppcompat
implementation "com.jakewharton.rxbinding3:rxbinding-material:$rxbinding_version" implementation libs.jakewharton.rxbindingMaterial
implementation("com.airbnb.android:epoxy:$epoxy_version") implementation libs.airbnb.epoxy
implementation "com.airbnb.android:epoxy-glide-preloading:$epoxy_version" implementation libs.airbnb.epoxyGlide
kapt "com.airbnb.android:epoxy-processor:$epoxy_version" kapt libs.airbnb.epoxyProcessor
implementation "com.airbnb.android:epoxy-paging:$epoxy_version" implementation libs.airbnb.epoxyPaging
implementation 'com.airbnb.android:mvrx:1.5.1' implementation libs.airbnb.mvrx
// Work // Work
implementation "androidx.work:work-runtime-ktx:$work_version" implementation libs.androidx.work
// Paging // Paging
implementation "androidx.paging:paging-runtime-ktx:2.1.2" implementation libs.androidx.pagingRuntimeKtx
// Functional Programming // Functional Programming
implementation "io.arrow-kt:arrow-core:$arrow_version" implementation libs.arrow.core
// Pref // Pref
implementation 'androidx.preference:preference-ktx:1.1.1' implementation libs.androidx.preferenceKtx
// UI // UI
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'com.google.android.material:material:1.4.0' implementation libs.google.material
implementation 'me.gujun.android:span:1.7' implementation 'me.gujun.android:span:1.7'
implementation "io.noties.markwon:core:$markwon_version" implementation libs.markwon.core
implementation "io.noties.markwon:html:$markwon_version" implementation libs.markwon.html
implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.5.2' implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.5.2'
implementation 'me.saket:better-link-movement-method:2.2.0' implementation 'me.saket:better-link-movement-method:2.2.0'
implementation 'com.google.android:flexbox:2.0.1' implementation 'com.google.android:flexbox:2.0.1'
implementation "androidx.autofill:autofill:$autofill_version" implementation libs.androidx.autoFill
implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'jp.wasabeef:glide-transformations:4.3.0'
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12' implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
implementation 'com.github.hyuwah:DraggableView:1.0.0' implementation 'com.github.hyuwah:DraggableView:1.0.0'
@ -433,7 +420,7 @@ dependencies {
// To convert voice message on old platforms // To convert voice message on old platforms
implementation 'com.arthenica:ffmpeg-kit-audio:4.4.LTS' implementation 'com.arthenica:ffmpeg-kit-audio:4.4.LTS'
//Alerter // Alerter
implementation 'com.tapadoo.android:alerter:7.0.1' implementation 'com.tapadoo.android:alerter:7.0.1'
implementation 'com.otaliastudios:autocomplete:1.1.0' implementation 'com.otaliastudios:autocomplete:1.1.0'
@ -442,16 +429,16 @@ dependencies {
implementation 'com.squareup:seismic:1.0.2' implementation 'com.squareup:seismic:1.0.2'
// Image Loading // Image Loading
implementation "com.github.piasy:BigImageViewer:$big_image_viewer_version" implementation libs.github.bigImageViewer
implementation "com.github.piasy:GlideImageLoader:$big_image_viewer_version" implementation libs.github.glideImageLoader
implementation "com.github.piasy:ProgressPieIndicator:$big_image_viewer_version" implementation libs.github.progressPieIndicator
implementation "com.github.piasy:GlideImageViewFactory:$big_image_viewer_version" implementation libs.github.glideImageViewFactory
// implementation 'com.github.MikeOrtiz:TouchImageView:3.0.2' // implementation 'com.github.MikeOrtiz:TouchImageView:3.0.2'
implementation 'com.github.chrisbanes:PhotoView:2.3.0' implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation "com.github.bumptech.glide:glide:$glide_version" implementation libs.github.glide
kapt "com.github.bumptech.glide:compiler:$glide_version" kapt libs.github.glideCompiler
implementation 'com.danikula:videocache:2.7.1' implementation 'com.danikula:videocache:2.7.1'
implementation 'com.github.yalantis:ucrop:2.2.7' implementation 'com.github.yalantis:ucrop:2.2.7'
@ -462,8 +449,8 @@ dependencies {
implementation 'nl.dionsegijn:konfetti:1.3.2' implementation 'nl.dionsegijn:konfetti:1.3.2'
implementation 'com.github.jetradarmobile:android-snowfall:1.2.1' implementation 'com.github.jetradarmobile:android-snowfall:1.2.1'
// DI // DI
implementation "com.google.dagger:dagger:$daggerVersion" implementation libs.dagger.dagger
kapt "com.google.dagger:dagger-compiler:$daggerVersion" kapt libs.dagger.daggerCompiler
// UnifiedPush // UnifiedPush
implementation 'com.github.UnifiedPush:android-connector:1.2.0' implementation 'com.github.UnifiedPush:android-connector:1.2.0'
@ -505,36 +492,37 @@ dependencies {
implementation 'im.dlg:android-dialer:1.2.5' implementation 'im.dlg:android-dialer:1.2.5'
// JWT // JWT
api "io.jsonwebtoken:jjwt-api:$jjwt_version" api libs.jsonwebtoken.jjwtApi
runtimeOnly "io.jsonwebtoken:jjwt-impl:$jjwt_version" runtimeOnly libs.jsonwebtoken.jjwtImpl
runtimeOnly("io.jsonwebtoken:jjwt-orgjson:$jjwt_version") { runtimeOnly(libs.jsonwebtoken.jjwtOrgjson) {
exclude group: 'org.json', module: 'json' //provided by Android natively exclude group: 'org.json', module: 'json' //provided by Android natively
} }
implementation 'commons-codec:commons-codec:1.15' implementation 'commons-codec:commons-codec:1.15'
// TESTS // TESTS
testImplementation 'junit:junit:4.13.2' testImplementation libs.tests.junit
testImplementation "org.amshove.kluent:kluent-android:$kluent_version" testImplementation libs.tests.kluent
// Plant Timber tree for test // Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' testImplementation libs.tests.timberJunitRule
// Activate when you want to check for leaks, from time to time. // Activate when you want to check for leaks, from time to time.
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3' //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
androidTestImplementation "androidx.test:core:$androidxTest_version" androidTestImplementation libs.androidx.testCore
androidTestImplementation "androidx.test:runner:$androidxTest_version" androidTestImplementation libs.androidx.testRunner
androidTestImplementation "androidx.test:rules:$androidxTest_version" androidTestImplementation libs.androidx.testRules
androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation libs.androidx.junit
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version" androidTestImplementation libs.androidx.espressoCore
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version" androidTestImplementation libs.androidx.espressoContrib
androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version" androidTestImplementation libs.androidx.espressoIntents
androidTestImplementation "org.amshove.kluent:kluent-android:$kluent_version" androidTestImplementation libs.tests.kluent
androidTestImplementation "androidx.arch.core:core-testing:$arch_version" androidTestImplementation libs.androidx.coreTesting
// Plant Timber tree for test // Plant Timber tree for test
androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' androidTestImplementation libs.tests.timberJunitRule
// "The one who serves a great Espresso" // "The one who serves a great Espresso"
androidTestImplementation('com.adevinta.android:barista:4.1.0') { androidTestImplementation('com.adevinta.android:barista:4.2.0') {
exclude group: 'org.jetbrains.kotlin' exclude group: 'org.jetbrains.kotlin'
} }
androidTestUtil libs.androidx.orchestrator
} }

View file

@ -47,6 +47,7 @@
<issue id="Recycle" severity="error" /> <issue id="Recycle" severity="error" />
<issue id="KotlinPropertyAccess" severity="error" /> <issue id="KotlinPropertyAccess" severity="error" />
<issue id="DefaultLocale" severity="error" /> <issue id="DefaultLocale" severity="error" />
<issue id="CheckResult" severity="error" />
<issue id="InvalidPackage"> <issue id="InvalidPackage">
<!-- Ignore error from HtmlCompressor lib --> <!-- Ignore error from HtmlCompressor lib -->

View file

@ -17,6 +17,10 @@
package im.vector.app.features.reactions.data package im.vector.app.features.reactions.data
import im.vector.app.InstrumentedTest import im.vector.app.InstrumentedTest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
@ -30,64 +34,80 @@ import kotlin.system.measureTimeMillis
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
class EmojiDataSourceTest : InstrumentedTest { class EmojiDataSourceTest : InstrumentedTest {
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
@Test @Test
fun checkParsingTime() { fun checkParsingTime() {
val time = measureTimeMillis { val time = measureTimeMillis {
EmojiDataSource(context().resources) createEmojiDataSource()
} }
assertTrue("Too long to parse", time < 100) assertTrue("Too long to parse", time < 100)
} }
@Test @Test
fun checkNumberOfResult() { fun checkNumberOfResult() {
val emojiDataSource = EmojiDataSource(context().resources) val emojiDataSource = createEmojiDataSource()
assertTrue("Wrong number of emojis", emojiDataSource.rawData.emojis.size >= 500) val rawData = runBlocking {
assertTrue("Wrong number of categories", emojiDataSource.rawData.categories.size >= 8) emojiDataSource.rawData.await()
}
assertTrue("Wrong number of emojis", rawData.emojis.size >= 500)
assertTrue("Wrong number of categories", rawData.categories.size >= 8)
} }
@Test @Test
fun searchTestEmptySearch() { fun searchTestEmptySearch() {
val emojiDataSource = EmojiDataSource(context().resources) val emojiDataSource = createEmojiDataSource()
val result = runBlocking {
assertTrue("Empty search should return at least 500 results", emojiDataSource.filterWith("").size >= 500) emojiDataSource.filterWith("")
}
assertTrue("Empty search should return at least 500 results", result.size >= 500)
} }
@Test @Test
fun searchTestNoResult() { fun searchTestNoResult() {
val emojiDataSource = EmojiDataSource(context().resources) val emojiDataSource = createEmojiDataSource()
val result = runBlocking {
assertTrue("Should not have result", emojiDataSource.filterWith("noresult").isEmpty()) emojiDataSource.filterWith("noresult")
}
assertTrue("Should not have result", result.isEmpty())
} }
@Test @Test
fun searchTestOneResult() { fun searchTestOneResult() {
val emojiDataSource = EmojiDataSource(context().resources) val emojiDataSource = createEmojiDataSource()
val result = runBlocking {
assertEquals("Should have 1 result", 1, emojiDataSource.filterWith("france").size) emojiDataSource.filterWith("france")
}
assertEquals("Should have 1 result", 1, result.size)
} }
@Test @Test
fun searchTestManyResult() { fun searchTestManyResult() {
val emojiDataSource = EmojiDataSource(context().resources) val emojiDataSource = createEmojiDataSource()
val result = runBlocking {
assertTrue("Should have many result", emojiDataSource.filterWith("fra").size > 1) emojiDataSource.filterWith("fra")
}
assertTrue("Should have many result", result.size > 1)
} }
@Test @Test
fun testTada() { fun testTada() {
val emojiDataSource = EmojiDataSource(context().resources) val emojiDataSource = createEmojiDataSource()
val result = runBlocking {
val result = emojiDataSource.filterWith("tada") emojiDataSource.filterWith("tada")
}
assertEquals("Should find tada emoji", 1, result.size) assertEquals("Should find tada emoji", 1, result.size)
assertEquals("Should find tada emoji", "🎉", result[0].emoji) assertEquals("Should find tada emoji", "🎉", result[0].emoji)
} }
@Test @Test
fun testQuickReactions() { fun testQuickReactions() {
val emojiDataSource = EmojiDataSource(context().resources) val emojiDataSource = createEmojiDataSource()
val result = runBlocking {
assertEquals("Should have 8 quick reactions", 8, emojiDataSource.getQuickReactions().size) emojiDataSource.getQuickReactions()
}
assertEquals("Should have 8 quick reactions", 8, result.size)
} }
private fun createEmojiDataSource() = EmojiDataSource(coroutineScope, context().resources)
} }

View file

@ -225,6 +225,8 @@ class UiAllScreensSanityTest {
clickOn(R.string.message_add_reaction) clickOn(R.string.message_add_reaction)
// Filter // Filter
// TODO clickMenu(R.id.search) // TODO clickMenu(R.id.search)
// Wait for emoji to load, it's async now
sleep(1_000)
clickListItem(R.id.emojiRecyclerView, 4) clickListItem(R.id.emojiRecyclerView, 4)
// Test Edit mode // Test Edit mode
@ -283,6 +285,7 @@ class UiAllScreensSanityTest {
clickListItem(R.id.matrixProfileRecyclerView, 9) clickListItem(R.id.matrixProfileRecyclerView, 9)
// File tab // File tab
clickOn(R.string.uploads_files_title) clickOn(R.string.uploads_files_title)
sleep(1000)
pressBack() pressBack()
assertDisplayed(R.id.roomProfileAvatarView) assertDisplayed(R.id.roomProfileAvatarView)
@ -334,6 +337,7 @@ class UiAllScreensSanityTest {
private fun navigateToRoomPeople() { private fun navigateToRoomPeople() {
// Open first user // Open first user
clickListItem(R.id.roomSettingsRecyclerView, 1) clickListItem(R.id.roomSettingsRecyclerView, 1)
sleep(1000)
assertDisplayed(R.id.memberProfilePowerLevelView) assertDisplayed(R.id.memberProfilePowerLevelView)
// Verification // Verification
@ -342,8 +346,9 @@ class UiAllScreensSanityTest {
// Role // Role
clickListItem(R.id.matrixProfileRecyclerView, 3) clickListItem(R.id.matrixProfileRecyclerView, 3)
sleep(1000)
clickDialogNegativeButton() clickDialogNegativeButton()
sleep(1000)
clickBack() clickBack()
} }

View file

@ -330,6 +330,7 @@
<activity android:name=".features.spaces.SpaceCreationActivity" /> <activity android:name=".features.spaces.SpaceCreationActivity" />
<activity android:name=".features.spaces.manage.SpaceManageActivity" /> <activity android:name=".features.spaces.manage.SpaceManageActivity" />
<activity android:name=".features.spaces.people.SpacePeopleActivity" /> <activity android:name=".features.spaces.people.SpacePeopleActivity" />
<activity android:name=".features.spaces.leave.SpaceLeaveAdvancedActivity" />
<!-- Services --> <!-- Services -->
<service <service

View file

@ -61,7 +61,20 @@ class AppStateHandler @Inject constructor(
var onSwitchSpaceListener: OnSwitchSpaceListener? = null var onSwitchSpaceListener: OnSwitchSpaceListener? = null
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? = selectedSpaceDataSource.currentValue?.orNull() fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? {
// XXX we should somehow make it live :/ just a work around
// For example just after creating a space and switching to it the
// name in the app Bar could show Empty Room, and it will not update unless you
// switch space
return selectedSpaceDataSource.currentValue?.orNull()?.let {
if (it is RoomGroupingMethod.BySpace) {
// try to refresh sum?
it.spaceSummary?.roomId?.let { activeSessionHolder.getSafeActiveSession()?.getRoomSummary(it) }?.let {
RoomGroupingMethod.BySpace(it)
} ?: it
} else it
}
}
fun setCurrentSpace(spaceId: String?, session: Session? = null) { fun setCurrentSpace(spaceId: String?, session: Session? = null) {
val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return

View file

@ -143,9 +143,11 @@ import im.vector.app.features.signout.soft.SoftLogoutFragment
import im.vector.app.features.spaces.SpaceListFragment import im.vector.app.features.spaces.SpaceListFragment
import im.vector.app.features.spaces.create.ChoosePrivateSpaceTypeFragment import im.vector.app.features.spaces.create.ChoosePrivateSpaceTypeFragment
import im.vector.app.features.spaces.create.ChooseSpaceTypeFragment import im.vector.app.features.spaces.create.ChooseSpaceTypeFragment
import im.vector.app.features.spaces.create.CreateSpaceAdd3pidInvitesFragment
import im.vector.app.features.spaces.create.CreateSpaceDefaultRoomsFragment import im.vector.app.features.spaces.create.CreateSpaceDefaultRoomsFragment
import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment
import im.vector.app.features.spaces.explore.SpaceDirectoryFragment import im.vector.app.features.spaces.explore.SpaceDirectoryFragment
import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedFragment
import im.vector.app.features.spaces.manage.SpaceAddRoomFragment import im.vector.app.features.spaces.manage.SpaceAddRoomFragment
import im.vector.app.features.spaces.manage.SpaceManageRoomsFragment import im.vector.app.features.spaces.manage.SpaceManageRoomsFragment
import im.vector.app.features.spaces.manage.SpaceSettingsFragment import im.vector.app.features.spaces.manage.SpaceSettingsFragment
@ -799,6 +801,11 @@ interface FragmentModule {
@FragmentKey(ChoosePrivateSpaceTypeFragment::class) @FragmentKey(ChoosePrivateSpaceTypeFragment::class)
fun bindChoosePrivateSpaceTypeFragment(fragment: ChoosePrivateSpaceTypeFragment): Fragment fun bindChoosePrivateSpaceTypeFragment(fragment: ChoosePrivateSpaceTypeFragment): Fragment
@Binds
@IntoMap
@FragmentKey(CreateSpaceAdd3pidInvitesFragment::class)
fun bindCreateSpaceAdd3pidInvitesFragment(fragment: CreateSpaceAdd3pidInvitesFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(SpaceAddRoomFragment::class) @FragmentKey(SpaceAddRoomFragment::class)
@ -828,4 +835,9 @@ interface FragmentModule {
@IntoMap @IntoMap
@FragmentKey(RoomJoinRuleChooseRestrictedFragment::class) @FragmentKey(RoomJoinRuleChooseRestrictedFragment::class)
fun bindRoomJoinRuleChooseRestrictedFragment(fragment: RoomJoinRuleChooseRestrictedFragment): Fragment fun bindRoomJoinRuleChooseRestrictedFragment(fragment: RoomJoinRuleChooseRestrictedFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SpaceLeaveAdvancedFragment::class)
fun bindSpaceLeaveAdvancedFragment(fragment: SpaceLeaveAdvancedFragment): Fragment
} }

View file

@ -84,10 +84,12 @@ import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet
import im.vector.app.features.share.IncomingShareActivity import im.vector.app.features.share.IncomingShareActivity
import im.vector.app.features.signout.soft.SoftLogoutActivity import im.vector.app.features.signout.soft.SoftLogoutActivity
import im.vector.app.features.spaces.InviteRoomSpaceChooserBottomSheet import im.vector.app.features.spaces.InviteRoomSpaceChooserBottomSheet
import im.vector.app.features.spaces.LeaveSpaceBottomSheet
import im.vector.app.features.spaces.SpaceCreationActivity import im.vector.app.features.spaces.SpaceCreationActivity
import im.vector.app.features.spaces.SpaceExploreActivity import im.vector.app.features.spaces.SpaceExploreActivity
import im.vector.app.features.spaces.SpaceSettingsMenuBottomSheet import im.vector.app.features.spaces.SpaceSettingsMenuBottomSheet
import im.vector.app.features.spaces.invite.SpaceInviteBottomSheet import im.vector.app.features.spaces.invite.SpaceInviteBottomSheet
import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedActivity
import im.vector.app.features.spaces.manage.SpaceManageActivity import im.vector.app.features.spaces.manage.SpaceManageActivity
import im.vector.app.features.spaces.share.ShareSpaceBottomSheet import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
import im.vector.app.features.terms.ReviewTermsActivity import im.vector.app.features.terms.ReviewTermsActivity
@ -96,6 +98,7 @@ import im.vector.app.features.usercode.UserCodeActivity
import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetActivity
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
import im.vector.app.features.workers.signout.SignOutBottomSheetDialogFragment import im.vector.app.features.workers.signout.SignOutBottomSheetDialogFragment
import kotlinx.coroutines.CoroutineScope
@Component( @Component(
dependencies = [ dependencies = [
@ -127,6 +130,7 @@ interface ScreenComponent {
fun uiStateRepository(): UiStateRepository fun uiStateRepository(): UiStateRepository
fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog
fun autoAcceptInvites(): AutoAcceptInvites fun autoAcceptInvites(): AutoAcceptInvites
fun appCoroutineScope(): CoroutineScope
/* ========================================================================================== /* ==========================================================================================
* Activities * Activities
@ -171,6 +175,7 @@ interface ScreenComponent {
fun inject(activity: SpaceExploreActivity) fun inject(activity: SpaceExploreActivity)
fun inject(activity: SpaceManageActivity) fun inject(activity: SpaceManageActivity)
fun inject(activity: RoomJoinRuleActivity) fun inject(activity: RoomJoinRuleActivity)
fun inject(activity: SpaceLeaveAdvancedActivity)
/* ========================================================================================== /* ==========================================================================================
* BottomSheets * BottomSheets
@ -199,6 +204,7 @@ interface ScreenComponent {
fun inject(bottomSheet: SpaceInviteBottomSheet) fun inject(bottomSheet: SpaceInviteBottomSheet)
fun inject(bottomSheet: JoinReplacementRoomBottomSheet) fun inject(bottomSheet: JoinReplacementRoomBottomSheet)
fun inject(bottomSheet: MigrateRoomBottomSheet) fun inject(bottomSheet: MigrateRoomBottomSheet)
fun inject(bottomSheet: LeaveSpaceBottomSheet)
/* ========================================================================================== /* ==========================================================================================
* Others * Others

View file

@ -58,8 +58,10 @@ import im.vector.app.features.rageshake.VectorFileLogger
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.app.features.reactions.data.EmojiDataSource import im.vector.app.features.reactions.data.EmojiDataSource
import im.vector.app.features.session.SessionListener import im.vector.app.features.session.SessionListener
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.ui.UiStateRepository import im.vector.app.features.ui.UiStateRepository
import kotlinx.coroutines.CoroutineScope
import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.HomeServerHistoryService
@ -145,6 +147,8 @@ interface VectorComponent {
fun vectorPreferences(): VectorPreferences fun vectorPreferences(): VectorPreferences
fun vectorDataStore(): VectorDataStore
fun wifiDetector(): WifiDetector fun wifiDetector(): WifiDetector
fun vectorFileLogger(): VectorFileLogger fun vectorFileLogger(): VectorFileLogger
@ -165,6 +169,8 @@ interface VectorComponent {
fun webRtcCallManager(): WebRtcCallManager fun webRtcCallManager(): WebRtcCallManager
fun appCoroutineScope(): CoroutineScope
fun jitsiActiveConferenceHolder(): JitsiActiveConferenceHolder fun jitsiActiveConferenceHolder(): JitsiActiveConferenceHolder
@Component.Factory @Component.Factory

View file

@ -33,12 +33,16 @@ import im.vector.app.features.pin.PinCodeStore
import im.vector.app.features.pin.SharedPrefPinCodeStore import im.vector.app.features.pin.SharedPrefPinCodeStore
import im.vector.app.features.ui.SharedPreferencesUiStateRepository import im.vector.app.features.ui.SharedPreferencesUiStateRepository
import im.vector.app.features.ui.UiStateRepository import im.vector.app.features.ui.UiStateRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import javax.inject.Singleton
@Module @Module
abstract class VectorModule { abstract class VectorModule {
@ -94,6 +98,13 @@ abstract class VectorModule {
fun providesHomeServerHistoryService(matrix: Matrix): HomeServerHistoryService { fun providesHomeServerHistoryService(matrix: Matrix): HomeServerHistoryService {
return matrix.homeServerHistoryService() return matrix.homeServerHistoryService()
} }
@Provides
@JvmStatic
@Singleton
fun providesApplicationCoroutineScope(): CoroutineScope {
return CoroutineScope(SupervisorJob() + Dispatchers.Main)
}
} }
@Binds @Binds

View file

@ -39,13 +39,15 @@ import im.vector.app.features.themes.ThemeUtils
/** /**
* Set a text in the TextView, or set visibility to GONE if the text is null * Set a text in the TextView, or set visibility to GONE if the text is null
*/ */
fun TextView.setTextOrHide(newText: CharSequence?, hideWhenBlank: Boolean = true) { fun TextView.setTextOrHide(newText: CharSequence?, hideWhenBlank: Boolean = true, vararg relatedViews: View = emptyArray()) {
if (newText == null if (newText == null
|| (newText.isBlank() && hideWhenBlank)) { || (newText.isBlank() && hideWhenBlank)) {
isVisible = false isVisible = false
relatedViews.forEach { it.isVisible = false }
} else { } else {
this.text = newText this.text = newText
isVisible = true isVisible = true
relatedViews.forEach { it.isVisible = true }
} }
} }

View file

@ -16,11 +16,14 @@
package im.vector.app.core.extensions package im.vector.app.core.extensions
import android.graphics.drawable.Drawable
import android.text.InputType import android.text.InputType
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import im.vector.app.R import im.vector.app.R
/** /**
@ -50,3 +53,8 @@ fun View.getMeasurements(): Pair<Int, Int> {
val height = measuredHeight val height = measuredHeight
return width to height return width to height
} }
fun ImageView.setDrawableOrHide(drawableRes: Drawable?) {
setImageDrawable(drawableRes)
isVisible = drawableRes != null
}

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2021 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.core.platform
import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
fun <T> LifecycleOwner.lifecycleAwareLazy(initializer: () -> T): Lazy<T> = LifecycleAwareLazy(this, initializer)
private object UninitializedValue
class LifecycleAwareLazy<out T>(
private val owner: LifecycleOwner,
initializer: () -> T
) : Lazy<T>, LifecycleObserver {
private var initializer: (() -> T)? = initializer
private var _value: Any? = UninitializedValue
@Suppress("UNCHECKED_CAST")
override val value: T
@MainThread
get() {
if (_value === UninitializedValue) {
_value = initializer!!()
attachToLifecycle()
}
return _value as T
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun resetValue() {
_value = UninitializedValue
detachFromLifecycle()
}
private fun attachToLifecycle() {
if (getLifecycleOwner().lifecycle.currentState == Lifecycle.State.DESTROYED) {
throw IllegalStateException("Initialization failed because lifecycle has been destroyed!")
}
getLifecycleOwner().lifecycle.addObserver(this)
}
private fun detachFromLifecycle() {
getLifecycleOwner().lifecycle.removeObserver(this)
}
private fun getLifecycleOwner() = when (owner) {
is Fragment -> owner.viewLifecycleOwner
else -> owner
}
override fun isInitialized(): Boolean = _value !== UninitializedValue
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
}

View file

@ -62,6 +62,23 @@ class PushersManager @Inject constructor(
) )
} }
fun registerEmailForPush(email: String) {
val currentSession = activeSessionHolder.getActiveSession()
val appName = appNameProvider.getAppName()
currentSession.addEmailPusher(
email = email,
lang = localeProvider.current().language,
emailBranding = appName,
appDisplayName = appName,
deviceDisplayName = currentSession.sessionParams.deviceId ?: "MOBILE"
)
}
suspend fun unregisterEmailPusher(email: String) {
val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
currentSession.removeEmailPusher(email)
}
suspend fun unregisterPusher(context: Context, pushKey: String) { suspend fun unregisterPusher(context: Context, pushKey: String) {
val currentSession = activeSessionHolder.getSafeActiveSession() ?: return val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
currentSession.removeHttpPusher(pushKey, getPusherAppId(context)) currentSession.removeHttpPusher(pushKey, getPusherAppId(context))

View file

@ -40,12 +40,14 @@ import im.vector.app.features.notifications.NotifiableEventResolver
import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.BackgroundSyncMode
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.unifiedpush.android.connector.MessagingReceiver import org.unifiedpush.android.connector.MessagingReceiver
import org.unifiedpush.android.connector.MessagingReceiverHandler import org.unifiedpush.android.connector.MessagingReceiverHandler
@ -69,6 +71,8 @@ data class Counts(
val unread: Int = 0 val unread: Int = 0
) )
private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
/** /**
* UnifiedPush handler. * UnifiedPush handler.
*/ */
@ -79,6 +83,7 @@ val upHandler = object: MessagingReceiverHandler {
private lateinit var pusherManager: PushersManager private lateinit var pusherManager: PushersManager
private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var activeSessionHolder: ActiveSessionHolder
private lateinit var vectorPreferences: VectorPreferences private lateinit var vectorPreferences: VectorPreferences
private lateinit var vectorDataStore: VectorDataStore
private lateinit var wifiDetector: WifiDetector private lateinit var wifiDetector: WifiDetector
private val coroutineScope = CoroutineScope(SupervisorJob()) private val coroutineScope = CoroutineScope(SupervisorJob())
@ -95,6 +100,7 @@ val upHandler = object: MessagingReceiverHandler {
pusherManager = pusherManager() pusherManager = pusherManager()
activeSessionHolder = activeSessionHolder() activeSessionHolder = activeSessionHolder()
vectorPreferences = vectorPreferences() vectorPreferences = vectorPreferences()
vectorDataStore = vectorDataStore()
wifiDetector = wifiDetector() wifiDetector = wifiDetector()
} }
} }
@ -108,9 +114,14 @@ val upHandler = object: MessagingReceiverHandler {
override fun onMessage(context: Context?, message: String, instance: String) { override fun onMessage(context: Context?, message: String, instance: String) {
initVar(context!!) initVar(context!!)
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.d("## onMessageReceived() %s", message) Timber.tag(loggerTag.value).d("## onMessageReceived() %s", message)
} else {
Timber.tag(loggerTag.value).d("## onMessageReceived()")
}
runBlocking {
vectorDataStore.incrementPushCounter()
} }
Timber.d("## onMessage() received")
val moshi: Moshi = Moshi.Builder() val moshi: Moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory()) .add(KotlinJsonAdapterFactory())
@ -127,6 +138,7 @@ val upHandler = object: MessagingReceiverHandler {
notification.unread = notification.counts.unread notification.unread = notification.counts.unread
} }
// Diagnostic Push // Diagnostic Push
if (notification.eventId == PushersManager.TEST_EVENT_ID) { if (notification.eventId == PushersManager.TEST_EVENT_ID) {
val intent = Intent(NotificationUtils.PUSH_ACTION) val intent = Intent(NotificationUtils.PUSH_ACTION)
@ -135,14 +147,14 @@ val upHandler = object: MessagingReceiverHandler {
} }
if (!vectorPreferences.areNotificationEnabledForDevice()) { if (!vectorPreferences.areNotificationEnabledForDevice()) {
Timber.i("Notification are disabled for this device") Timber.tag(loggerTag.value).i("Notification are disabled for this device")
return return
} }
mUIHandler.post { mUIHandler.post {
if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// we are in foreground, let the sync do the things? // we are in foreground, let the sync do the things?
Timber.d("PUSH received in a foreground state, ignore") Timber.tag(loggerTag.value).d("PUSH received in a foreground state, ignore")
} else { } else {
onMessageReceivedInternal(context, notification) onMessageReceivedInternal(context, notification)
} }
@ -157,7 +169,7 @@ val upHandler = object: MessagingReceiverHandler {
*/ */
override fun onNewEndpoint(context: Context?, endpoint: String, instance: String) { override fun onNewEndpoint(context: Context?, endpoint: String, instance: String) {
initVar(context!!) initVar(context!!)
Timber.i("onNewEndpoint: adding $endpoint") Timber.tag(loggerTag.value).i("onNewEndpoint: adding $endpoint")
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) { if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
val gateway = UPHelper.customOrDefaultGateway(context, endpoint) val gateway = UPHelper.customOrDefaultGateway(context, endpoint)
if (UPHelper.getUpEndpoint(context) != endpoint if (UPHelper.getUpEndpoint(context) != endpoint
@ -166,7 +178,7 @@ val upHandler = object: MessagingReceiverHandler {
UPHelper.storeUpEndpoint(context, endpoint) UPHelper.storeUpEndpoint(context, endpoint)
pusherManager.registerPusher(context, endpoint, gateway) pusherManager.registerPusher(context, endpoint, gateway)
} else { } else {
Timber.i("onNewEndpoint: skipped") Timber.tag(loggerTag.value).i("onNewEndpoint: skipped")
} }
} }
if (!UPHelper.allowBackgroundSync(context)) { if (!UPHelper.allowBackgroundSync(context)) {
@ -184,7 +196,7 @@ val upHandler = object: MessagingReceiverHandler {
} }
override fun onUnregistered(context: Context?, instance: String) { override fun onUnregistered(context: Context?, instance: String) {
Timber.d("Unifiedpush: Unregistered") Timber.tag(loggerTag.value).d("Unifiedpush: Unregistered")
initVar(context!!) initVar(context!!)
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY
vectorPreferences.setFdroidSyncBackgroundMode(mode) vectorPreferences.setFdroidSyncBackgroundMode(mode)
@ -192,7 +204,7 @@ val upHandler = object: MessagingReceiverHandler {
try { try {
pusherManager.unregisterPusher(context, UPHelper.getUpEndpoint(context)!!) pusherManager.unregisterPusher(context, UPHelper.getUpEndpoint(context)!!)
} catch (e: Exception) { } catch (e: Exception) {
Timber.d("Probably unregistering a non existant pusher") Timber.tag(loggerTag.value).d("Probably unregistering a non existant pusher")
} }
} }
} }
@ -205,7 +217,9 @@ val upHandler = object: MessagingReceiverHandler {
private fun onMessageReceivedInternal(context: Context, notification: Notification) { private fun onMessageReceivedInternal(context: Context, notification: Notification) {
try { try {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.d("## onMessageReceivedInternal() : $notification") Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $notification")
} else {
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
} }
// update the badge counter // update the badge counter
@ -214,21 +228,21 @@ val upHandler = object: MessagingReceiverHandler {
val session = activeSessionHolder.getSafeActiveSession() val session = activeSessionHolder.getSafeActiveSession()
if (session == null) { if (session == null) {
Timber.w("## Can't sync from push, no current session") Timber.tag(loggerTag.value).w("## Can't sync from push, no current session")
} else { } else {
if (isEventAlreadyKnown(notification.eventId, notification.roomId)) { if (isEventAlreadyKnown(notification.eventId, notification.roomId)) {
Timber.d("Ignoring push, event already known") Timber.tag(loggerTag.value).d("Ignoring push, event already known")
} else { } else {
// Try to get the Event content faster // Try to get the Event content faster
Timber.d("Requesting event in fast lane") Timber.tag(loggerTag.value).d("Requesting event in fast lane")
getEventFastLane(session, notification.roomId, notification.eventId) getEventFastLane(session, notification.roomId, notification.eventId)
Timber.d("Requesting background sync") Timber.tag(loggerTag.value).d("Requesting background sync")
session.requireBackgroundSync() session.requireBackgroundSync()
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## onMessageReceivedInternal() failed") Timber.tag(loggerTag.value).e(e, "## onMessageReceivedInternal() failed")
} }
} }
@ -242,18 +256,18 @@ val upHandler = object: MessagingReceiverHandler {
} }
if (wifiDetector.isConnectedToWifi().not()) { if (wifiDetector.isConnectedToWifi().not()) {
Timber.d("No WiFi network, do not get Event") Timber.tag(loggerTag.value).d("No WiFi network, do not get Event")
return return
} }
coroutineScope.launch { coroutineScope.launch {
Timber.d("Fast lane: start request") Timber.tag(loggerTag.value).d("Fast lane: start request")
val event = tryOrNull { session.getEvent(roomId, eventId) } ?: return@launch val event = tryOrNull { session.getEvent(roomId, eventId) } ?: return@launch
val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event) val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event)
resolvedEvent resolvedEvent
?.also { Timber.d("Fast lane: notify drawer") } ?.also { Timber.tag(loggerTag.value).d("Fast lane: notify drawer") }
?.let { ?.let {
it.isPushGatewayEvent = true it.isPushGatewayEvent = true
notificationDrawerManager.onNotifiableEventReceived(it) notificationDrawerManager.onNotifiableEventReceived(it)
@ -271,7 +285,7 @@ val upHandler = object: MessagingReceiverHandler {
val room = session.getRoom(roomId) ?: return false val room = session.getRoom(roomId) ?: return false
return room.getTimeLineEvent(eventId) != null return room.getTimeLineEvent(eventId) != null
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined") Timber.tag(loggerTag.value).e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined")
} }
} }
return false return false

View file

@ -50,7 +50,7 @@ private val loggerTag = LoggerTag("CallService", LoggerTag.VOIP)
class CallService : VectorService() { class CallService : VectorService() {
private val connections = mutableMapOf<String, CallConnection>() private val connections = mutableMapOf<String, CallConnection>()
private val knownCalls = mutableSetOf<CallInformation>() private val knownCalls = mutableMapOf<String, CallInformation>()
private val connectedCallIds = mutableSetOf<String>() private val connectedCallIds = mutableSetOf<String>()
private lateinit var notificationManager: NotificationManagerCompat private lateinit var notificationManager: NotificationManagerCompat
@ -190,7 +190,7 @@ class CallService : VectorService() {
} else { } else {
notificationManager.notify(callId.hashCode(), notification) notificationManager.notify(callId.hashCode(), notification)
} }
knownCalls.add(callInformation) knownCalls[callId] = callInformation
} }
private fun handleCallTerminated(intent: Intent) { private fun handleCallTerminated(intent: Intent) {
@ -198,20 +198,22 @@ class CallService : VectorService() {
val endCallReason = intent.getSerializableExtra(EXTRA_END_CALL_REASON) as EndCallReason val endCallReason = intent.getSerializableExtra(EXTRA_END_CALL_REASON) as EndCallReason
val rejected = intent.getBooleanExtra(EXTRA_END_CALL_REJECTED, false) val rejected = intent.getBooleanExtra(EXTRA_END_CALL_REJECTED, false)
alertManager.cancelAlert(callId) alertManager.cancelAlert(callId)
val terminatedCall = knownCalls.firstOrNull { it.callId == callId } val terminatedCall = knownCalls.remove(callId)
if (terminatedCall == null) { if (terminatedCall == null) {
Timber.tag(loggerTag.value).v("Call terminated for unknown call $callId$") Timber.tag(loggerTag.value).v("Call terminated for unknown call $callId")
handleUnexpectedState(callId) handleUnexpectedState(callId)
return return
} }
knownCalls.remove(terminatedCall) val notification = notificationUtils.buildCallEndedNotification(false)
val notificationId = callId.hashCode()
startForeground(notificationId, notification)
if (knownCalls.isEmpty()) { if (knownCalls.isEmpty()) {
Timber.tag(loggerTag.value).v("No more call, stop the service")
stopForeground(true)
mediaSession?.isActive = false mediaSession?.isActive = false
myStopSelf() myStopSelf()
} }
val wasConnected = connectedCallIds.remove(callId) val wasConnected = connectedCallIds.remove(callId)
val notification = notificationUtils.buildCallEndedNotification(terminatedCall.isVideoCall)
notificationManager.notify(callId.hashCode(), notification)
if (!wasConnected && !terminatedCall.isOutgoing && !rejected && endCallReason != EndCallReason.ANSWERED_ELSEWHERE) { if (!wasConnected && !terminatedCall.isOutgoing && !rejected && endCallReason != EndCallReason.ANSWERED_ELSEWHERE) {
val missedCallNotification = notificationUtils.buildCallMissedNotification(terminatedCall) val missedCallNotification = notificationUtils.buildCallMissedNotification(terminatedCall)
notificationManager.notify(MISSED_CALL_TAG, terminatedCall.nativeRoomId.hashCode(), missedCallNotification) notificationManager.notify(MISSED_CALL_TAG, terminatedCall.nativeRoomId.hashCode(), missedCallNotification)
@ -243,7 +245,7 @@ class CallService : VectorService() {
} else { } else {
notificationManager.notify(callId.hashCode(), notification) notificationManager.notify(callId.hashCode(), notification)
} }
knownCalls.add(callInformation) knownCalls[callId] = callInformation
} }
/** /**
@ -267,18 +269,19 @@ class CallService : VectorService() {
} else { } else {
notificationManager.notify(callId.hashCode(), notification) notificationManager.notify(callId.hashCode(), notification)
} }
knownCalls.add(callInformation) knownCalls[callId] = callInformation
} }
private fun handleUnexpectedState(callId: String?) { private fun handleUnexpectedState(callId: String?) {
Timber.tag(loggerTag.value).v("Fallback to clear everything") Timber.tag(loggerTag.value).v("Fallback to clear everything")
callRingPlayerIncoming?.stop() callRingPlayerIncoming?.stop()
callRingPlayerOutgoing?.stop() callRingPlayerOutgoing?.stop()
if (callId != null) {
notificationManager.cancel(callId.hashCode())
}
val notification = notificationUtils.buildCallEndedNotification(false) val notification = notificationUtils.buildCallEndedNotification(false)
startForeground(DEFAULT_NOTIFICATION_ID, notification) if (callId != null) {
startForeground(callId.hashCode(), notification)
} else {
startForeground(DEFAULT_NOTIFICATION_ID, notification)
}
if (knownCalls.isEmpty()) { if (knownCalls.isEmpty()) {
mediaSession?.isActive = false mediaSession?.isActive = false
myStopSelf() myStopSelf()
@ -371,7 +374,7 @@ class CallService : VectorService() {
putExtra(EXTRA_END_CALL_REASON, endCallReason) putExtra(EXTRA_END_CALL_REASON, endCallReason)
putExtra(EXTRA_END_CALL_REJECTED, rejected) putExtra(EXTRA_END_CALL_REJECTED, rejected)
} }
ContextCompat.startForegroundService(context, intent) context.startService(intent)
} }
} }

View file

@ -26,6 +26,7 @@ import androidx.core.view.isGone
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.setDrawableOrHide
import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.databinding.ViewBottomSheetActionButtonBinding import im.vector.app.databinding.ViewBottomSheetActionButtonBinding
@ -81,7 +82,7 @@ class BottomSheetActionButton @JvmOverloads constructor(
var rightIcon: Drawable? = null var rightIcon: Drawable? = null
set(value) { set(value) {
field = value field = value
views.bottomSheetActionIcon.setImageDrawable(value) views.bottomSheetActionIcon.setDrawableOrHide(value)
} }
var tint: Int? = null var tint: Int? = null
@ -96,6 +97,12 @@ class BottomSheetActionButton @JvmOverloads constructor(
value?.let { views.bottomSheetActionTitle.setTextColor(it) } value?.let { views.bottomSheetActionTitle.setTextColor(it) }
} }
var isBetaAction: Boolean? = null
set(value) {
field = value
views.bottomSheetActionBeta.isVisible = field ?: false
}
init { init {
inflate(context, R.layout.view_bottom_sheet_action_button, this) inflate(context, R.layout.view_bottom_sheet_action_button, this)
views = ViewBottomSheetActionButtonBinding.bind(this) views = ViewBottomSheetActionButtonBinding.bind(this)
@ -110,6 +117,8 @@ class BottomSheetActionButton @JvmOverloads constructor(
tint = getColor(R.styleable.BottomSheetActionButton_tint, ThemeUtils.getColor(context, android.R.attr.textColor)) tint = getColor(R.styleable.BottomSheetActionButton_tint, ThemeUtils.getColor(context, android.R.attr.textColor))
titleTextColor = getColor(R.styleable.BottomSheetActionButton_titleTextColor, ThemeUtils.getColor(context, R.attr.colorPrimary)) titleTextColor = getColor(R.styleable.BottomSheetActionButton_titleTextColor, ThemeUtils.getColor(context, R.attr.colorPrimary))
isBetaAction = getBoolean(R.styleable.BottomSheetActionButton_betaAction, false)
} }
} }
} }

View file

@ -36,7 +36,7 @@ class CurrentCallsViewPresenter {
this.currentCall = currentCall this.currentCall = currentCall
this.currentCall?.addListener(tickListener) this.currentCall?.addListener(tickListener)
this.calls = calls this.calls = calls
val hasActiveCall = currentCall != null val hasActiveCall = calls.isNotEmpty()
currentCallsView?.isVisible = hasActiveCall currentCallsView?.isVisible = hasActiveCall
currentCallsView?.render(calls, currentCall?.formattedDuration() ?: "") currentCallsView?.render(calls, currentCall?.formattedDuration() ?: "")
} }

View file

@ -19,7 +19,6 @@ package im.vector.app.core.ui.views
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import im.vector.app.R import im.vector.app.R
import im.vector.app.databinding.ViewFailedMessagesWarningBinding import im.vector.app.databinding.ViewFailedMessagesWarningBinding
@ -49,8 +48,4 @@ class FailedMessagesWarningView @JvmOverloads constructor(
views.failedMessagesDeleteAllButton.setOnClickListener { callback?.onDeleteAllClicked() } views.failedMessagesDeleteAllButton.setOnClickListener { callback?.onDeleteAllClicked() }
views.failedMessagesRetryButton.setOnClickListener { callback?.onRetryClicked() } views.failedMessagesRetryButton.setOnClickListener { callback?.onRetryClicked() }
} }
fun render(hasFailedMessages: Boolean) {
isVisible = hasFailedMessages
}
} }

View file

@ -20,6 +20,7 @@ import android.content.Context
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
/** /**
* Open a web view above the current activity. * Open a web view above the current activity.
@ -38,3 +39,14 @@ fun Context.displayInWebView(url: String) {
.setPositiveButton(android.R.string.ok, null) .setPositiveButton(android.R.string.ok, null)
.show() .show()
} }
fun Context.showIdentityServerConsentDialog(configuredIdentityServer: String?, consentCallBack: (() -> Unit)) {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.identity_server_consent_dialog_title)
.setMessage(getString(R.string.identity_server_consent_dialog_content, configuredIdentityServer ?: ""))
.setPositiveButton(R.string.yes) { _, _ ->
consentCallBack.invoke()
}
.setNegativeButton(R.string.no, null)
.show()
}

View file

@ -21,6 +21,11 @@ import androidx.recyclerview.widget.RecyclerView
import im.vector.app.features.autocomplete.AutocompleteClickListener import im.vector.app.features.autocomplete.AutocompleteClickListener
import im.vector.app.features.autocomplete.RecyclerViewPresenter import im.vector.app.features.autocomplete.RecyclerViewPresenter
import im.vector.app.features.reactions.data.EmojiDataSource import im.vector.app.features.reactions.data.EmojiDataSource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class AutocompleteEmojiPresenter @Inject constructor(context: Context, class AutocompleteEmojiPresenter @Inject constructor(context: Context,
@ -28,11 +33,14 @@ class AutocompleteEmojiPresenter @Inject constructor(context: Context,
private val controller: AutocompleteEmojiController) : private val controller: AutocompleteEmojiController) :
RecyclerViewPresenter<String>(context), AutocompleteClickListener<String> { RecyclerViewPresenter<String>(context), AutocompleteClickListener<String> {
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
init { init {
controller.listener = this controller.listener = this
} }
fun clear() { fun clear() {
coroutineScope.coroutineContext.cancelChildren()
controller.listener = null controller.listener = null
} }
@ -45,12 +53,14 @@ class AutocompleteEmojiPresenter @Inject constructor(context: Context,
} }
override fun onQuery(query: CharSequence?) { override fun onQuery(query: CharSequence?) {
val data = if (query.isNullOrBlank()) { coroutineScope.launch {
// Return common emojis val data = if (query.isNullOrBlank()) {
emojiDataSource.getQuickReactions() // Return common emojis
} else { emojiDataSource.getQuickReactions()
emojiDataSource.filterWith(query.toString()) } else {
emojiDataSource.filterWith(query.toString())
}
controller.setData(data)
} }
controller.setData(data)
} }
} }

View file

@ -19,8 +19,8 @@ package im.vector.app.features.autocomplete.member
import android.content.Context import android.content.Context
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.features.autocomplete.AutocompleteClickListener import im.vector.app.features.autocomplete.AutocompleteClickListener
import im.vector.app.features.autocomplete.RecyclerViewPresenter import im.vector.app.features.autocomplete.RecyclerViewPresenter
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
@ -35,7 +35,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
private val controller: AutocompleteMemberController private val controller: AutocompleteMemberController
) : RecyclerViewPresenter<RoomMemberSummary>(context), AutocompleteClickListener<RoomMemberSummary> { ) : RecyclerViewPresenter<RoomMemberSummary>(context), AutocompleteClickListener<RoomMemberSummary> {
private val room = session.getRoom(roomId)!! private val room by lazy { session.getRoom(roomId)!! }
init { init {
controller.listener = this controller.listener = this

View file

@ -41,7 +41,7 @@ class SharedKnownCallsViewModel @Inject constructor(
} }
} }
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener { private val callManagerListener = object : WebRtcCallManager.Listener {
override fun onCurrentCallChange(call: WebRtcCall?) { override fun onCurrentCallChange(call: WebRtcCall?) {
val knownCalls = callManager.getCalls() val knownCalls = callManager.getCalls()
liveKnownCalls.postValue(knownCalls) liveKnownCalls.postValue(knownCalls)
@ -50,12 +50,17 @@ class SharedKnownCallsViewModel @Inject constructor(
it.addListener(callListener) it.addListener(callListener)
} }
} }
override fun onCallEnded(callId: String) {
val knownCalls = callManager.getCalls()
liveKnownCalls.postValue(knownCalls)
}
} }
init { init {
val knownCalls = callManager.getCalls() val knownCalls = callManager.getCalls()
liveKnownCalls.postValue(knownCalls) liveKnownCalls.postValue(knownCalls)
callManager.addCurrentCallListener(currentCallListener) callManager.addListener(callManagerListener)
knownCalls.forEach { knownCalls.forEach {
it.addListener(callListener) it.addListener(callListener)
} }
@ -65,7 +70,7 @@ class SharedKnownCallsViewModel @Inject constructor(
callManager.getCalls().forEach { callManager.getCalls().forEach {
it.removeListener(callListener) it.removeListener(callListener)
} }
callManager.removeCurrentCallListener(currentCallListener) callManager.removeListener(callManagerListener)
super.onCleared() super.onCleared()
} }
} }

View file

@ -134,7 +134,15 @@ class VectorCallViewModel @AssistedInject constructor(
} ?: VectorCallViewState.TransfereeState.UnknownTransferee } ?: VectorCallViewState.TransfereeState.UnknownTransferee
} }
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener { private val callManagerListener = object : WebRtcCallManager.Listener {
override fun onCallEnded(callId: String) {
withState { state ->
if (state.otherKnownCallInfo?.callId == callId) {
setState { copy(otherKnownCallInfo = null) }
}
}
}
override fun onCurrentCallChange(call: WebRtcCall?) { override fun onCurrentCallChange(call: WebRtcCall?) {
if (call != null) { if (call != null) {
@ -159,9 +167,7 @@ class VectorCallViewModel @AssistedInject constructor(
} }
private fun updateOtherKnownCall(currentCall: WebRtcCall) { private fun updateOtherKnownCall(currentCall: WebRtcCall) {
val otherCall = callManager.getCalls().firstOrNull { val otherCall = getOtherKnownCall(currentCall)
it.callId != currentCall.callId && it.mxCall.state is CallState.Connected
}
setState { setState {
if (otherCall == null) { if (otherCall == null) {
copy(otherKnownCallInfo = null) copy(otherKnownCallInfo = null)
@ -171,6 +177,12 @@ class VectorCallViewModel @AssistedInject constructor(
} }
} }
private fun getOtherKnownCall(currentCall: WebRtcCall): WebRtcCall? {
return callManager.getCalls().firstOrNull {
it.callId != currentCall.callId && it.mxCall.state is CallState.Connected
}
}
init { init {
setupCallWithCurrentState() setupCallWithCurrentState()
} }
@ -184,7 +196,7 @@ class VectorCallViewModel @AssistedInject constructor(
} }
} else { } else {
call = webRtcCall call = webRtcCall
callManager.addCurrentCallListener(currentCallListener) callManager.addListener(callManagerListener)
webRtcCall.addListener(callListener) webRtcCall.addListener(callListener)
val currentSoundDevice = callManager.audioManager.selectedDevice val currentSoundDevice = callManager.audioManager.selectedDevice
if (currentSoundDevice == CallAudioManager.Device.Phone) { if (currentSoundDevice == CallAudioManager.Device.Phone) {
@ -230,7 +242,7 @@ class VectorCallViewModel @AssistedInject constructor(
} }
override fun onCleared() { override fun onCleared() {
callManager.removeCurrentCallListener(currentCallListener) callManager.removeListener(callManagerListener)
call?.removeListener(callListener) call?.removeListener(callListener)
call = null call = null
proximityManager.stop() proximityManager.stop()
@ -310,10 +322,10 @@ class VectorCallViewModel @AssistedInject constructor(
VectorCallViewEvents.ShowCallTransferScreen VectorCallViewEvents.ShowCallTransferScreen
) )
} }
VectorCallViewActions.TransferCall -> { VectorCallViewActions.TransferCall -> {
handleCallTransfer() handleCallTransfer()
} }
is VectorCallViewActions.SwitchCall -> { is VectorCallViewActions.SwitchCall -> {
setState { VectorCallViewState(action.callArgs) } setState { VectorCallViewState(action.callArgs) }
setupCallWithCurrentState() setupCallWithCurrentState()
} }

View file

@ -28,20 +28,21 @@ import com.facebook.react.bridge.JavaOnlyMap
import org.jitsi.meet.sdk.BroadcastEmitter import org.jitsi.meet.sdk.BroadcastEmitter
import org.jitsi.meet.sdk.BroadcastEvent import org.jitsi.meet.sdk.BroadcastEvent
import org.jitsi.meet.sdk.JitsiMeet import org.jitsi.meet.sdk.JitsiMeet
import org.matrix.android.sdk.api.extensions.tryOrNull import timber.log.Timber
private const val CONFERENCE_URL_DATA_KEY = "url" private const val CONFERENCE_URL_DATA_KEY = "url"
fun BroadcastEvent.extractConferenceUrl(): String? { sealed class ConferenceEvent(open val data: Map<String, Any>) {
return when (type) { data class Terminated(override val data: Map<String, Any>) : ConferenceEvent(data)
BroadcastEvent.Type.CONFERENCE_TERMINATED, data class WillJoin(override val data: Map<String, Any>) : ConferenceEvent(data)
BroadcastEvent.Type.CONFERENCE_WILL_JOIN, data class Joined(override val data: Map<String, Any>) : ConferenceEvent(data)
BroadcastEvent.Type.CONFERENCE_JOINED -> data[CONFERENCE_URL_DATA_KEY] as? String
else -> null fun extractConferenceUrl(): String? {
return data[CONFERENCE_URL_DATA_KEY] as? String
} }
} }
class JitsiBroadcastEmitter(private val context: Context) { class ConferenceEventEmitter(private val context: Context) {
fun emitConferenceEnded() { fun emitConferenceEnded() {
val broadcastEventData = JavaOnlyMap.of(CONFERENCE_URL_DATA_KEY, JitsiMeet.getCurrentConference()) val broadcastEventData = JavaOnlyMap.of(CONFERENCE_URL_DATA_KEY, JitsiMeet.getCurrentConference())
@ -49,8 +50,9 @@ class JitsiBroadcastEmitter(private val context: Context) {
} }
} }
class JitsiBroadcastEventObserver(private val context: Context, class ConferenceEventObserver(private val context: Context,
private val onBroadcastEvent: (BroadcastEvent) -> Unit) : LifecycleObserver { private val onBroadcastEvent: (ConferenceEvent) -> Unit)
: LifecycleObserver {
// See https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-android-sdk#listening-for-broadcasted-events // See https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-android-sdk#listening-for-broadcasted-events
private val broadcastReceiver = object : BroadcastReceiver() { private val broadcastReceiver = object : BroadcastReceiver() {
@ -61,8 +63,10 @@ class JitsiBroadcastEventObserver(private val context: Context,
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun unregisterForBroadcastMessages() { fun unregisterForBroadcastMessages() {
tryOrNull("Unable to unregister receiver") { try {
LocalBroadcastManager.getInstance(context).unregisterReceiver(broadcastReceiver) LocalBroadcastManager.getInstance(context).unregisterReceiver(broadcastReceiver)
} catch (throwable: Throwable) {
Timber.v("Unable to unregister receiver")
} }
} }
@ -72,13 +76,23 @@ class JitsiBroadcastEventObserver(private val context: Context,
for (type in BroadcastEvent.Type.values()) { for (type in BroadcastEvent.Type.values()) {
intentFilter.addAction(type.action) intentFilter.addAction(type.action)
} }
tryOrNull("Unable to register receiver") { try {
LocalBroadcastManager.getInstance(context).registerReceiver(broadcastReceiver, intentFilter) LocalBroadcastManager.getInstance(context).registerReceiver(broadcastReceiver, intentFilter)
} catch (throwable: Throwable) {
Timber.v("Unable to register receiver")
} }
} }
private fun onBroadcastReceived(intent: Intent) { private fun onBroadcastReceived(intent: Intent) {
val event = BroadcastEvent(intent) val event = BroadcastEvent(intent)
onBroadcastEvent(event) val conferenceEvent = when (event.type) {
BroadcastEvent.Type.CONFERENCE_JOINED -> ConferenceEvent.Joined(event.data)
BroadcastEvent.Type.CONFERENCE_TERMINATED -> ConferenceEvent.Terminated(event.data)
BroadcastEvent.Type.CONFERENCE_WILL_JOIN -> ConferenceEvent.WillJoin(event.data)
else -> null
}
if (conferenceEvent != null) {
onBroadcastEvent(conferenceEvent)
}
} }
} }

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