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
- [ ] Checkout `develop`
- [ ] Increase version in `./vector/build.gradle`
- [ ] Change the value of SDK_VERSION in the file `./matrix-sdk-android/build.gradle`
- [ ] Commit and push `develop`
- [ ] 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.
@ -72,9 +73,25 @@ body:
- [ ] 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.
- [ ] 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 value of VERSION_NAME in the file gradle.properties
- [ ] 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)
- [ ] Upload the AAR on the GitHub release
@ -82,7 +99,7 @@ body:
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
- [ ] Commit and push directly on `main`
validations:

View file

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

View file

@ -5,6 +5,12 @@ on:
push:
branches: [ main, develop ]
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx2g
-Porg.gradle.parallel=false
jobs:
# 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
@ -22,7 +28,7 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Compile Android tests
run: ./gradlew clean assembleAndroidTest --stacktrace -PallWarningsAsErrors=false
run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace -PallWarningsAsErrors=false
integration-tests:
name: Integration Tests (Synapse)
@ -30,9 +36,14 @@ jobs:
strategy:
fail-fast: false
matrix:
api-level: [21, 28, 30]
api-level: [28]
steps:
- 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
uses: actions/setup-python@v2
with:
@ -64,5 +75,12 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2
with:
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 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:
branches: [ main, develop ]
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx2g
-Porg.gradle.parallel=false
jobs:
integration-tests:
name: Sanity Tests (Synapse)
@ -46,5 +52,5 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2
with:
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:
branches: [main, develop]
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx2g
-Porg.gradle.parallel=false
jobs:
unit-tests:
name: Run Unit Tests
@ -20,4 +26,11 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- 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
.DS_Store
/build
/benchmark-out
/captures
.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)
======================================

View file

@ -18,13 +18,12 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 30
compileSdk versions.compileSdk
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
minSdk versions.minSdk
targetSdk versions.targetSdk
}
buildTypes {
@ -34,8 +33,8 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility versions.sourceCompat
targetCompatibility versions.targetCompat
}
kotlinOptions {
jvmTarget = "11"
@ -51,13 +50,13 @@ dependencies {
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation libs.rx.rxKotlin
implementation libs.rx.rxAndroid
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation libs.jetbrains.kotlinStdlib
implementation libs.androidx.core
implementation libs.androidx.appCompat
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.
buildscript {
// Ref: https://kotlinlang.org/releases.html
ext.kotlin_version = '1.5.21'
ext.kotlin_coroutines_version = "1.5.0"
apply from: 'dependencies.gradle'
repositories {
google()
jcenter()
@ -11,12 +11,13 @@ buildscript {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
// Release notes of Android Gradle Plugin (AGP):
// 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 "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4'
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
- 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>
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.
@ -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.
<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>
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
# Specifies the JVM arguments used for the daemon process.
# 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
# Android Settings
android.enableJetifier=true
android.useAndroidX=true
#Project Settings
# Change debugPrivateData to true for debugging
vector.debugPrivateData=false
# httpLogLevel values: NONE, BASIC, HEADERS, BODY
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 {
compileSdkVersion 30
buildToolsVersion "30.0.3"
compileSdk versions.compileSdk
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
minSdk versions.minSdk
targetSdk versions.targetSdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
@ -41,8 +38,8 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility versions.sourceCompat
targetCompatibility versions.targetCompat
}
kotlinOptions {
@ -55,10 +52,10 @@ android {
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation libs.androidx.appCompat
implementation libs.google.material
// Pref theme
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation libs.androidx.preferenceKtx
// PFLockScreen attrs
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
// dialpad dimen

View file

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

View file

@ -3,13 +3,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 30
compileSdk versions.compileSdk
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
minSdk versions.minSdk
targetSdk versions.targetSdk
// Multidex is useful for tests
multiDexEnabled true
@ -24,8 +22,8 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility versions.sourceCompat
targetCompatibility versions.targetCompat
}
kotlinOptions {
@ -34,15 +32,16 @@ android {
}
dependencies {
implementation project(":matrix-sdk-android")
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlin_coroutines_version"
implementation libs.androidx.appCompat
implementation libs.rx.rxKotlin
implementation libs.rx.rxAndroid
implementation libs.jetbrains.coroutinesRx2
// Paging
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
implementation libs.androidx.pagingRuntimeKtx
// 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.group.GroupSummaryQueryParams
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.pushers.Pusher
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
@ -239,6 +240,10 @@ class RxSession(private val session: Session) {
)
.distinctUntilChanged()
}
fun lookupThreePid(threePid: ThreePid): Single<Optional<FoundThreePid>> = rxSingle {
session.identityService().lookUp(listOf(threePid)).firstOrNull().toOptional()
}
}
fun Session.rx(): RxSession {

View file

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

View file

@ -32,10 +32,19 @@ import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
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.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.RoomType
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.common.CommonTestHelper
import org.matrix.android.sdk.common.SessionTestParams
@ -386,6 +395,8 @@ class SpaceHierarchyTest : InstrumentedTest {
// The room should have disapear from flat children
GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
}
commonTestHelper.signOutAndClose(session)
}
data class TestSpaceCreationResult(
@ -434,6 +445,57 @@ class SpaceHierarchyTest : InstrumentedTest {
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
fun testRootSpaces() {
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
@ -473,5 +535,111 @@ class SpaceHierarchyTest : InstrumentedTest {
val rootSpaces = session.spaceService().getRootSpaceSummaries()
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 {
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) {
object SYNC : LoggerTag("SYNC")
object VOIP : LoggerTag("VOIP")
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.homeserver.HomeServerCapabilitiesService
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.media.MediaService
import org.matrix.android.sdk.api.session.openid.OpenIdService
@ -75,7 +75,7 @@ interface Session :
ProfileService,
PushRuleService,
PushersService,
InitialSyncProgressService,
SyncStatusService,
HomeServerCapabilitiesService,
SecureStorageService,
AccountService {

View file

@ -239,7 +239,7 @@ data class Event(
fun Event.isTextMessage(): Boolean {
return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
&& when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
MessageType.MSGTYPE_TEXT,
MessageType.MSGTYPE_EMOTE,
MessageType.MSGTYPE_NOTICE -> true
@ -249,7 +249,7 @@ fun Event.isTextMessage(): Boolean {
fun Event.isImageMessage(): Boolean {
return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
&& when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
MessageType.MSGTYPE_IMAGE -> true
else -> false
}
@ -257,7 +257,7 @@ fun Event.isImageMessage(): Boolean {
fun Event.isVideoMessage(): Boolean {
return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
&& when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
MessageType.MSGTYPE_VIDEO -> true
else -> false
}
@ -265,7 +265,7 @@ fun Event.isVideoMessage(): Boolean {
fun Event.isAudioMessage(): Boolean {
return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
&& when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
MessageType.MSGTYPE_AUDIO -> true
else -> false
}
@ -273,7 +273,7 @@ fun Event.isAudioMessage(): Boolean {
fun Event.isFileMessage(): Boolean {
return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
&& when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
MessageType.MSGTYPE_FILE -> true
else -> false
}
@ -281,7 +281,7 @@ fun Event.isFileMessage(): Boolean {
fun Event.isAttachmentMessage(): Boolean {
return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
&& when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_AUDIO,
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 state: PusherState
)
) {
companion object {
const val KIND_EMAIL = "email"
const val KIND_HTTP = "http"
const val APP_ID_EMAIL = "m.email"
}
}
enum class PusherState {
UNREGISTERED,

View file

@ -27,14 +27,12 @@ interface PushersService {
/**
* 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
*
* @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,
* 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.
* If the kind is "email", this is the email address to send notifications to.
* @param appId the application id
* 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
@ -64,6 +62,30 @@ interface PushersService {
append: Boolean,
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
* 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)
/**
* 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)
/**
* Remove an Email pusher
* @see addEmailPusher
*/
suspend fun removeEmailPusher(email: String)
/**
* 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'.
*/
@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'.

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
interface MessageContent {
companion object {
const val MSG_TYPE_JSON_KEY = "msgtype"
}
val msgType: String
val body: String
val relatesTo: RelationDefaultContent?

View file

@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon
@JsonClass(generateAdapter = true)
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 = "m.relates_to") override val relatesTo: RelationDefaultContent? = 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'.
*/
@Json(name = "msgtype") override val msgType: String,
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
/**
* Required. The emote action to perform.

View file

@ -28,7 +28,7 @@ data class MessageFileContent(
/**
* 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.

View file

@ -27,7 +27,7 @@ data class MessageImageContent(
/**
* 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,

View file

@ -26,7 +26,7 @@ data class MessageLocationContent(
/**
* 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

View file

@ -26,7 +26,7 @@ data class MessageNoticeContent(
/**
* 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.

View file

@ -30,7 +30,7 @@ const val OPTION_TYPE_BUTTONS = "org.matrix.buttons"
*/
@JsonClass(generateAdapter = true)
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 = "body") override val body: 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)
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 = "m.relates_to") override val relatesTo: RelationDefaultContent? = 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'.
*/
@Json(name = "msgtype") override val msgType: String,
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
/**
* Required. The body of the message.

View file

@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReque
@JsonClass(generateAdapter = true)
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 = "from_device") override val fromDevice: String?,
@Json(name = "methods") override val methods: List<String>,

View file

@ -27,7 +27,7 @@ data class MessageVideoContent(
/**
* 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'.

View file

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

View file

@ -71,18 +71,24 @@ internal class InboundGroupSessionStore @Inject constructor(
}
@Synchronized
fun storeInBoundGroupSession(wrapper: OlmInboundGroupSessionWrapper2) {
fun storeInBoundGroupSession(wrapper: OlmInboundGroupSessionWrapper2, sessionId: String, senderKey: String) {
Timber.v("## Inbound: getInboundGroupSession mark as dirty ${wrapper.roomId}-${wrapper.senderKey}")
// We want to batch this a bit for performances
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 = object : TimerTask() {
override fun run() {
batchSave()
}
}
timer.schedule(timerTask!!, 2_000)
timer.schedule(timerTask!!, 300)
}
@Synchronized

View file

@ -577,7 +577,8 @@ internal class MXOlmDevice @Inject constructor(
session.keysClaimed = keysClaimed
session.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain
store.storeInboundGroupSessions(listOf(session))
inboundGroupSessionStore.storeInBoundGroupSession(session, sessionId, senderKey)
// store.storeInboundGroupSessions(listOf(session))
return true
}
@ -703,7 +704,7 @@ internal class MXOlmDevice @Inject constructor(
timelineSet.add(messageIndexKey)
}
inboundGroupSessionStore.storeInBoundGroupSession(session)
inboundGroupSessionStore.storeInBoundGroupSession(session, sessionId, senderKey)
val payload = try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
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.
* 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
*/
@ -74,13 +74,13 @@ internal class UserAgentHolder @Inject constructor(private val context: Context,
// if there is no user agent or cannot parse it
if (null == systemUserAgent || systemUserAgent.lastIndexOf(")") == -1 || !systemUserAgent.contains("(")) {
userAgent = (appName + "/" + appVersion + " ( Flavour " + flavorDescription
+ "; MatrixAndroidSDK_X " + BuildConfig.VERSION_NAME + ")")
+ "; MatrixAndroidSdk2 " + BuildConfig.SDK_VERSION + ")")
} else {
// update
userAgent = appName + "/" + appVersion + " " +
systemUserAgent.substring(systemUserAgent.indexOf("("), systemUserAgent.lastIndexOf(")") - 1) +
"; 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.group.GroupService
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.media.MediaService
import org.matrix.android.sdk.api.session.openid.OpenIdService
@ -115,7 +115,7 @@ internal class DefaultSession @Inject constructor(
private val contentUploadProgressTracker: ContentUploadStateTracker,
private val typingUsersTracker: TypingUsersTracker,
private val contentDownloadStateTracker: ContentDownloadStateTracker,
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
private val syncStatusService: Lazy<SyncStatusService>,
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
private val accountDataService: Lazy<SessionAccountDataService>,
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
@ -141,7 +141,7 @@ internal class DefaultSession @Inject constructor(
PushersService by pushersService.get(),
EventService by eventService.get(),
TermsService by termsService.get(),
InitialSyncProgressService by initialSyncProgressService.get(),
SyncStatusService by syncStatusService.get(),
SecureStorageService by secureStorageService.get(),
HomeServerCapabilitiesService by homeServerCapabilitiesService.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.openid.OpenIdModule
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.room.RoomModule
import org.matrix.android.sdk.internal.session.room.relation.SendRelationWorker
@ -127,7 +127,7 @@ internal interface SessionComponent {
fun inject(worker: SyncWorker)
fun inject(worker: AddHttpPusherWorker)
fun inject(worker: AddPusherWorker)
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.events.EventService
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.permalinks.PermalinkService
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.homeserver.DefaultHomeServerCapabilitiesService
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.openid.DefaultOpenIdService
import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
@ -355,7 +355,7 @@ internal abstract class SessionModule {
abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver
@Binds
abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService
abstract fun bindSyncStatusService(service: DefaultSyncStatusService): SyncStatusService
@Binds
abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService

View file

@ -20,10 +20,16 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
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.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure
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.homeserver.HomeServerCapabilitiesService
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.extensions.observeNotNull
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.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.profile.BindThreePidsTask
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.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.UserAccountDataDataSource
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
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 javax.inject.Inject
import javax.net.ssl.HttpsURLConnection
@ -202,6 +202,8 @@ internal class DefaultIdentityService @Inject constructor(
identityStore.setUrl(urlCandidate)
identityStore.setToken(token)
// could we remember if it was previously given?
identityStore.setUserConsent(false)
updateIdentityAPI(urlCandidate)
updateAccountData(urlCandidate)
@ -230,6 +232,8 @@ internal class DefaultIdentityService @Inject constructor(
}
override suspend fun lookUp(threePids: List<ThreePid>): List<FoundThreePid> {
if (getCurrentIdentityServerUrl() == null) throw IdentityServiceError.NoIdentityServerConfigured
if (!getUserConsent()) {
throw IdentityServiceError.UserConsentNotProvided
}

View file

@ -18,23 +18,28 @@ package org.matrix.android.sdk.internal.session.initsync
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
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 javax.inject.Inject
@SessionScope
internal class DefaultInitialSyncProgressService @Inject constructor()
: InitialSyncProgressService,
internal class DefaultSyncStatusService @Inject constructor()
: SyncStatusService,
ProgressReporter {
private val status = MutableLiveData<InitialSyncProgressService.Status>()
private val status = MutableLiveData<SyncStatusService.Status>()
private var rootTask: TaskInfo? = null
override fun getInitialSyncProgressStatus(): LiveData<InitialSyncProgressService.Status> {
override fun getSyncStatusLive(): LiveData<SyncStatusService.Status> {
return status
}
// Only to be used for incremental sync
fun setStatus(newStatus: SyncStatusService.Status.IncrementalSyncStatus) {
status.postValue(newStatus)
}
/**
* Create a rootTask
*/
@ -67,7 +72,7 @@ internal class DefaultInitialSyncProgressService @Inject constructor()
// Update the progress of the leaf and all its parents
leaf.setProgress(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
endedTask.parent.child = null
} else {
status.postValue(InitialSyncProgressService.Status.Idle)
status.postValue(SyncStatusService.Status.Idle)
}
}
}
fun endAll() {
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 javax.inject.Inject
internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
: SessionSafeCoroutineWorker<AddHttpPusherWorker.Params>(context, params, Params::class.java) {
internal class AddPusherWorker(context: Context, params: WorkerParameters)
: SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(

View file

@ -66,27 +66,45 @@ internal class DefaultPushersService @Inject constructor(
deviceDisplayName: String,
url: String,
append: Boolean,
withEventIdOnly: Boolean)
: UUID {
// 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")
if ("/_matrix/push/v1/notify" !in url) throw InvalidParameterException("url should contain '/_matrix/push/v1/notify'")
withEventIdOnly: Boolean
) = addPusher(
JsonPusher(
pushKey = pushkey,
kind = Pusher.KIND_HTTP,
appId = appId,
profileTag = profileTag,
lang = lang,
appDisplayName = appDisplayName,
deviceDisplayName = deviceDisplayName,
data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }),
append = append
)
)
val pusher = JsonPusher(
pushKey = pushkey,
kind = "http",
appId = appId,
appDisplayName = appDisplayName,
deviceDisplayName = deviceDisplayName,
profileTag = profileTag,
lang = lang,
data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }),
append = append)
override fun addEmailPusher(email: String,
lang: String,
emailBranding: String,
appDisplayName: String,
deviceDisplayName: String,
append: Boolean
) = addPusher(
JsonPusher(
pushKey = email,
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)
val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddHttpPusherWorker>()
private fun addPusher(pusher: JsonPusher): UUID {
pusher.validateParameters()
val params = AddPusherWorker.Params(sessionId, pusher)
val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddPusherWorker>()
.setConstraints(WorkManagerProvider.workConstraints)
.setInputData(WorkerParamsFactory.toData(params))
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
@ -95,8 +113,27 @@ internal class DefaultPushersService @Inject constructor(
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) {
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)
}

View file

@ -32,5 +32,8 @@ internal data class JsonPusherData(
* Currently the only format available is 'event_id_only'.
*/
@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.Event
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.PublicRoomsResponse
import org.matrix.android.sdk.api.util.JsonDict
@ -254,7 +255,7 @@ internal interface RoomAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}")
suspend fun join(@Path("roomIdOrAlias") roomIdOrAlias: String,
@Query("server_name") viaServers: List<String>,
@Body params: JsonDict): JoinRoomResponse
@Body params: JsonDict): JoinRoomResponse
/**
* Leave the given room.
@ -381,4 +382,14 @@ internal interface RoomAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade")
suspend fun upgradeRoom(@Path("roomId") roomId: String,
@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
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.peeking.PeekResult
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.directory.GetPublicRoomTask
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 getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask,
private val getPublicRoomTask: GetPublicRoomTask,
private val getRoomSummaryTask: GetRoomSummaryTask,
private val resolveRoomStateTask: ResolveRoomStateTask
) : PeekRoomTask {
@ -70,6 +72,25 @@ internal class DefaultPeekRoomTask @Inject constructor(
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?
val visibilityRes = tryOrNull("## PEEK: failed to get visibility") {
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.room.accountdata.RoomAccountDataTypes
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.RoomCanonicalAliasContent
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.VersioningState
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.internal.crypto.EventDecryptor
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
@ -226,63 +228,102 @@ internal class RoomSummaryUpdater @Inject constructor(
}
.toMap()
lookupMap.keys.forEach { lookedUp ->
if (lookedUp.roomType == RoomType.SPACE) {
// get childrens
// First handle child relations
lookupMap.keys.asSequence()
.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(
realm.createObject<SpaceChildSummaryEntity>().apply {
this.childRoomId = child.roomId
this.childSummaryEntity = RoomSummaryEntity.where(realm, child.roomId).findFirst()
this.order = child.order
lookedUp.children.add(
realm.createObject<SpaceChildSummaryEntity>().apply {
this.childRoomId = child.roomId
this.childSummaryEntity = RoomSummaryEntity.where(realm, child.roomId).findFirst()
this.order = child.order
// this.autoJoin = child.autoJoin
this.viaServers.addAll(child.viaServers)
}
)
this.viaServers.addAll(child.viaServers)
}
)
RoomSummaryEntity.where(realm, child.roomId)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
.findFirst()
?.let { childSum ->
lookupMap.entries.firstOrNull { it.key.roomId == lookedUp.roomId }?.let { entry ->
if (entry.value.indexOfFirst { it.roomId == childSum.roomId } == -1) {
// add looked up as a parent
entry.value.add(childSum)
RoomSummaryEntity.where(realm, child.roomId)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
.findFirst()
?.let { childSum ->
lookupMap.entries.firstOrNull { it.key.roomId == lookedUp.roomId }?.let { entry ->
if (entry.value.indexOfFirst { it.roomId == childSum.roomId } == -1) {
// add looked up as a parent
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
// 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(
order = null,
via = null,
// autoJoin = null,
suggested = null
).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.create.CreateRoomPreset
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.JoinSpaceResult
import org.matrix.android.sdk.api.session.space.Space
@ -77,7 +78,7 @@ internal class DefaultSpaceService @Inject constructor(
if (isPublic) {
this.roomAliasName = roomAliasLocalPart
this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy(
invite = 0
invite = if (isPublic) Role.Default.value else Role.Moderator.value
)
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE
@ -221,4 +222,23 @@ internal class DefaultSpaceService @Inject constructor(
).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
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.SyncStatusService
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
import org.matrix.android.sdk.internal.di.UserId
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.session.filter.FilterRepository
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.sync.model.LazyRoomSyncEphemeral
import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser
@ -40,6 +42,8 @@ import java.io.File
import java.net.SocketTimeoutException
import javax.inject.Inject
private val loggerTag = LoggerTag("SyncTask", LoggerTag.SYNC)
internal interface SyncTask : Task<SyncTask.Params, Unit> {
data class Params(
@ -53,7 +57,7 @@ internal class DefaultSyncTask @Inject constructor(
@UserId private val userId: String,
private val filterRepository: FilterRepository,
private val syncResponseHandler: SyncResponseHandler,
private val initialSyncProgressService: DefaultInitialSyncProgressService,
private val defaultSyncStatusService: DefaultSyncStatusService,
private val syncTokenStore: SyncTokenStore,
private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask,
private val userStore: UserStore,
@ -75,7 +79,7 @@ internal class DefaultSyncTask @Inject constructor(
}
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>()
var timeout = 0L
@ -92,7 +96,7 @@ internal class DefaultSyncTask @Inject constructor(
if (isInitialSync) {
// We might want to get the user information in parallel too
userStore.createOrUpdate(userId)
initialSyncProgressService.startRoot(InitSyncStep.ImportingAccount, 100)
defaultSyncStatusService.startRoot(InitSyncStep.ImportingAccount, 100)
}
// Maybe refresh the homeserver capabilities data we know
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)
if (isInitialSync) {
Timber.d("INIT_SYNC with filter: ${requestParams["filter"]}")
Timber.tag(loggerTag.value).d("INIT_SYNC with filter: ${requestParams["filter"]}")
val initSyncStrategy = initialSyncStrategy
logDuration("INIT_SYNC strategy: $initSyncStrategy") {
logDuration("INIT_SYNC strategy: $initSyncStrategy", loggerTag) {
if (initSyncStrategy is InitialSyncStrategy.Optimized) {
roomSyncEphemeralTemporaryStore.reset()
workingDir.mkdirs()
val file = downloadInitSyncResponse(requestParams)
reportSubtask(initialSyncProgressService, InitSyncStep.ImportingAccount, 1, 0.7F) {
reportSubtask(defaultSyncStatusService, InitSyncStep.ImportingAccount, 1, 0.7F) {
handleSyncFile(file, initSyncStrategy)
}
// Delete all files
workingDir.deleteRecursively()
} else {
val syncResponse = logDuration("INIT_SYNC Request") {
val syncResponse = logDuration("INIT_SYNC Request", loggerTag) {
executeRequest(globalErrorReceiver) {
syncAPI.sync(
params = requestParams,
@ -122,43 +126,60 @@ internal class DefaultSyncTask @Inject constructor(
}
}
logDuration("INIT_SYNC Database insertion") {
syncResponseHandler.handleResponse(syncResponse, token, initialSyncProgressService)
logDuration("INIT_SYNC Database insertion", loggerTag) {
syncResponseHandler.handleResponse(syncResponse, token, defaultSyncStatusService)
}
}
}
initialSyncProgressService.endAll()
defaultSyncStatusService.endAll()
} else {
val syncResponse = executeRequest(globalErrorReceiver) {
syncAPI.sync(
params = requestParams,
readTimeOut = readTimeOut
)
Timber.tag(loggerTag.value).d("Start incremental sync request")
defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncIdle)
val syncResponse = try {
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)
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 {
val workingFile = File(workingDir, "initSync.json")
val status = initialSyncStatusRepository.getStep()
if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) {
Timber.d("INIT_SYNC file is already here")
reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.3f) {
Timber.tag(loggerTag.value).d("INIT_SYNC file is already here")
reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.3f) {
// Empty task
}
} else {
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADING)
val syncResponse = logDuration("INIT_SYNC Perform server request") {
reportSubtask(initialSyncProgressService, InitSyncStep.ServerComputing, 1, 0.2f) {
val syncResponse = logDuration("INIT_SYNC Perform server request", loggerTag) {
reportSubtask(defaultSyncStatusService, InitSyncStep.ServerComputing, 1, 0.2f) {
getSyncResponse(requestParams, MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT)
}
}
if (syncResponse.isSuccessful) {
logDuration("INIT_SYNC Download and save to file") {
reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.1f) {
logDuration("INIT_SYNC Download and save to file", loggerTag) {
reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.1f) {
syncResponse.body()?.byteStream()?.use { inputStream ->
workingFile.outputStream().use { outputStream ->
inputStream.copyTo(outputStream)
@ -168,7 +189,7 @@ internal class DefaultSyncTask @Inject constructor(
}
} else {
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)
}
@ -185,9 +206,9 @@ internal class DefaultSyncTask @Inject constructor(
).awaitResponse()
} catch (throwable: Throwable) {
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 {
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
}
}
@ -195,18 +216,18 @@ internal class DefaultSyncTask @Inject constructor(
}
private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized) {
logDuration("INIT_SYNC handleSyncFile()") {
val syncResponse = logDuration("INIT_SYNC Read file and parse") {
logDuration("INIT_SYNC handleSyncFile()", loggerTag) {
val syncResponse = logDuration("INIT_SYNC Read file and parse", loggerTag) {
syncResponseParser.parse(initSyncStrategy, workingFile)
}
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_PARSED)
// Log some stats
val nbOfJoinedRooms = syncResponse.rooms?.join?.size ?: 0
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") {
syncResponseHandler.handleResponse(syncResponse, null, initialSyncProgressService)
logDuration("INIT_SYNC Database insertion", loggerTag) {
syncResponseHandler.handleResponse(syncResponse, null, defaultSyncStatusService)
}
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS)
}

View file

@ -36,6 +36,7 @@ import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
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.internal.session.call.ActiveCallHandler
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 DEFAULT_LONG_POOL_TIMEOUT = 30_000L
private val loggerTag = LoggerTag("SyncThread", LoggerTag.SYNC)
internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
private val networkConnectivityChecker: NetworkConnectivityChecker,
private val backgroundDetectionObserver: BackgroundDetectionObserver,
@ -83,7 +86,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
fun restart() = synchronized(lock) {
if (!isStarted) {
Timber.v("Resume sync...")
Timber.tag(loggerTag.value).d("Resume sync...")
isStarted = true
// Check again server availability and the token validity
canReachServer = true
@ -94,7 +97,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
fun pause() = synchronized(lock) {
if (isStarted) {
Timber.v("Pause sync...")
Timber.tag(loggerTag.value).d("Pause sync...")
isStarted = false
retryNoNetworkTask?.cancel()
syncScope.coroutineContext.cancelChildren()
@ -102,7 +105,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
}
fun kill() = synchronized(lock) {
Timber.v("Kill sync...")
Timber.tag(loggerTag.value).d("Kill sync...")
updateStateTo(SyncState.Killing)
retryNoNetworkTask?.cancel()
syncScope.coroutineContext.cancelChildren()
@ -124,21 +127,21 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
}
override fun run() {
Timber.v("Start syncing...")
Timber.tag(loggerTag.value).d("Start syncing...")
isStarted = true
networkConnectivityChecker.register(this)
backgroundDetectionObserver.register(this)
registerActiveCallsObserver()
while (state != SyncState.Killing) {
Timber.v("Entering loop, state: $state")
Timber.tag(loggerTag.value).d("Entering loop, state: $state")
if (!isStarted) {
Timber.v("Sync is Paused. Waiting...")
Timber.tag(loggerTag.value).d("Sync is Paused. Waiting...")
updateStateTo(SyncState.Paused)
synchronized(lock) { lock.wait() }
Timber.v("...unlocked")
Timber.tag(loggerTag.value).d("...unlocked")
} else if (!canReachServer) {
Timber.v("No network. Waiting...")
Timber.tag(loggerTag.value).d("No network. Waiting...")
updateStateTo(SyncState.NoNetwork)
// 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) {
@ -148,19 +151,19 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
}
}
synchronized(lock) { lock.wait() }
Timber.v("...retry")
Timber.tag(loggerTag.value).d("...retry")
} else if (!isTokenValid) {
Timber.v("Token is invalid. Waiting...")
Timber.tag(loggerTag.value).d("Token is invalid. Waiting...")
updateStateTo(SyncState.InvalidToken)
synchronized(lock) { lock.wait() }
Timber.v("...unlocked")
Timber.tag(loggerTag.value).d("...unlocked")
} else {
if (state !is SyncState.Running) {
updateStateTo(SyncState.Running(afterPause = true))
}
// No timeout after a pause
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 sync = syncScope.launch {
doSync(params)
@ -168,10 +171,10 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
runBlocking {
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)
backgroundDetectionObserver.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) {
// Timeout are not critical
Timber.v("Timeout")
Timber.tag(loggerTag.value).d("Timeout")
} else if (failure is CancellationException) {
Timber.v("Cancelled")
Timber.tag(loggerTag.value).d("Cancelled")
} else if (failure.isTokenError()) {
// No token or invalid token, stop the thread
Timber.w(failure, "Token error")
Timber.tag(loggerTag.value).w(failure, "Token error")
isStarted = false
isTokenValid = false
} else {
Timber.e(failure)
Timber.tag(loggerTag.value).e(failure)
if (failure !is Failure.NetworkConnection || failure.cause is JsonEncodingException) {
// Wait 10s before retrying
Timber.v("Wait 10s")
Timber.tag(loggerTag.value).d("Wait 10s")
delay(RETRY_WAIT_TIME_MS)
}
}
@ -225,7 +228,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
}
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) {
return
}

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.util
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.logger.LoggerTag
import timber.log.Timber
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,
loggerTag: LoggerTag,
block: suspend () -> T): T {
Timber.d("$message -- BEGIN")
Timber.tag(loggerTag.value).d("$message -- BEGIN")
val start = System.currentTimeMillis()
val result = logRamUsage(message) {
block()
}
val duration = System.currentTimeMillis() - start
Timber.d("$message -- END duration: $duration ms")
Timber.tag(loggerTag.value).d("$message -- END duration: $duration ms")
return result
}

View file

@ -19,14 +19,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
android {
compileSdkVersion 30
compileSdk versions.compileSdk
defaultConfig {
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
minSdk versions.minSdk
targetSdk versions.targetSdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
}
@ -41,11 +38,11 @@ android {
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation "androidx.fragment:fragment-ktx:1.3.6"
implementation 'androidx.exifinterface:exifinterface:1.3.3'
implementation libs.jetbrains.kotlinStdlibJdk7
implementation libs.androidx.appCompat
implementation libs.androidx.fragmentKtx
implementation libs.androidx.exifinterface
// 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
### 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
import org.matrix.android.sdk.internal.legacy.riot===3

View file

@ -13,8 +13,8 @@ kapt {
// Note: 2 digits max for each value
ext.versionMajor = 1
ext.versionMinor = 2
ext.versionPatch = 2
ext.versionMinor = 3
ext.versionPatch = 0
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
android {
compileSdkVersion 30
// 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
ndkVersion "21.3.6528147"
compileSdk versions.compileSdk
defaultConfig {
applicationId "de.spiritcroc.riotx"
// Set to API 21: see #405
minSdkVersion 21
targetSdkVersion 30
minSdk versions.minSdk
targetSdk versions.targetSdk
multiDexEnabled true
renderscriptTargetApi 24
@ -294,8 +297,8 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility versions.sourceCompat
targetCompatibility versions.targetCompat
}
kotlinOptions {
@ -322,26 +325,6 @@ android {
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-rx")
implementation project(":diff-match-patch")
@ -350,75 +333,79 @@ dependencies {
implementation project(":library:ui-styles")
implementation 'androidx.multidex:multidex:2.0.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation libs.jetbrains.kotlinStdlibJdk7
implementation libs.jetbrains.kotlinReflect
implementation libs.jetbrains.coroutinesCore
implementation libs.jetbrains.coroutinesAndroid
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation libs.androidx.recyclerview
implementation libs.androidx.appCompat
implementation libs.androidx.fragmentKtx
implementation libs.androidx.constraintLayout
implementation "androidx.sharetarget:sharetarget:1.1.0"
implementation 'androidx.core:core-ktx:1.6.0'
implementation "androidx.media:media:1.4.1"
implementation libs.androidx.core
implementation "androidx.media:media:1.4.2"
implementation "androidx.transition:transition:1.4.1"
implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0"
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
implementation libs.squareup.moshi
implementation libs.squareup.moshiKt
kapt libs.squareup.moshiKotlin
implementation libs.androidx.lifecycleExtensions
implementation libs.androidx.lifecycleLivedata
implementation libs.androidx.datastore
implementation libs.androidx.datastorepreferences
// Log
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation libs.jakewharton.timber
// Debug
implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.31'
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.33'
// rx
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation libs.rx.rxKotlin
implementation libs.rx.rxAndroid
implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.1'
// RXBinding
implementation "com.jakewharton.rxbinding3:rxbinding:$rxbinding_version"
implementation "com.jakewharton.rxbinding3:rxbinding-appcompat:$rxbinding_version"
implementation "com.jakewharton.rxbinding3:rxbinding-material:$rxbinding_version"
implementation libs.jakewharton.rxbinding
implementation libs.jakewharton.rxbindingAppcompat
implementation libs.jakewharton.rxbindingMaterial
implementation("com.airbnb.android:epoxy:$epoxy_version")
implementation "com.airbnb.android:epoxy-glide-preloading:$epoxy_version"
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
implementation "com.airbnb.android:epoxy-paging:$epoxy_version"
implementation 'com.airbnb.android:mvrx:1.5.1'
implementation libs.airbnb.epoxy
implementation libs.airbnb.epoxyGlide
kapt libs.airbnb.epoxyProcessor
implementation libs.airbnb.epoxyPaging
implementation libs.airbnb.mvrx
// Work
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation libs.androidx.work
// Paging
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
implementation libs.androidx.pagingRuntimeKtx
// Functional Programming
implementation "io.arrow-kt:arrow-core:$arrow_version"
implementation libs.arrow.core
// Pref
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation libs.androidx.preferenceKtx
// UI
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 "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:html:$markwon_version"
implementation libs.markwon.core
implementation libs.markwon.html
implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.5.2'
implementation 'me.saket:better-link-movement-method:2.2.0'
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 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
implementation 'com.github.hyuwah:DraggableView:1.0.0'
@ -433,7 +420,7 @@ dependencies {
// To convert voice message on old platforms
implementation 'com.arthenica:ffmpeg-kit-audio:4.4.LTS'
//Alerter
// Alerter
implementation 'com.tapadoo.android:alerter:7.0.1'
implementation 'com.otaliastudios:autocomplete:1.1.0'
@ -442,16 +429,16 @@ dependencies {
implementation 'com.squareup:seismic:1.0.2'
// Image Loading
implementation "com.github.piasy:BigImageViewer:$big_image_viewer_version"
implementation "com.github.piasy:GlideImageLoader:$big_image_viewer_version"
implementation "com.github.piasy:ProgressPieIndicator:$big_image_viewer_version"
implementation "com.github.piasy:GlideImageViewFactory:$big_image_viewer_version"
implementation libs.github.bigImageViewer
implementation libs.github.glideImageLoader
implementation libs.github.progressPieIndicator
implementation libs.github.glideImageViewFactory
// implementation 'com.github.MikeOrtiz:TouchImageView:3.0.2'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation "com.github.bumptech.glide:glide:$glide_version"
kapt "com.github.bumptech.glide:compiler:$glide_version"
implementation libs.github.glide
kapt libs.github.glideCompiler
implementation 'com.danikula:videocache:2.7.1'
implementation 'com.github.yalantis:ucrop:2.2.7'
@ -462,8 +449,8 @@ dependencies {
implementation 'nl.dionsegijn:konfetti:1.3.2'
implementation 'com.github.jetradarmobile:android-snowfall:1.2.1'
// DI
implementation "com.google.dagger:dagger:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
implementation libs.dagger.dagger
kapt libs.dagger.daggerCompiler
// UnifiedPush
implementation 'com.github.UnifiedPush:android-connector:1.2.0'
@ -505,36 +492,37 @@ dependencies {
implementation 'im.dlg:android-dialer:1.2.5'
// JWT
api "io.jsonwebtoken:jjwt-api:$jjwt_version"
runtimeOnly "io.jsonwebtoken:jjwt-impl:$jjwt_version"
runtimeOnly("io.jsonwebtoken:jjwt-orgjson:$jjwt_version") {
api libs.jsonwebtoken.jjwtApi
runtimeOnly libs.jsonwebtoken.jjwtImpl
runtimeOnly(libs.jsonwebtoken.jjwtOrgjson) {
exclude group: 'org.json', module: 'json' //provided by Android natively
}
implementation 'commons-codec:commons-codec:1.15'
// TESTS
testImplementation 'junit:junit:4.13.2'
testImplementation "org.amshove.kluent:kluent-android:$kluent_version"
testImplementation libs.tests.junit
testImplementation libs.tests.kluent
// 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.
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
androidTestImplementation "androidx.test:core:$androidxTest_version"
androidTestImplementation "androidx.test:runner:$androidxTest_version"
androidTestImplementation "androidx.test:rules:$androidxTest_version"
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version"
androidTestImplementation "org.amshove.kluent:kluent-android:$kluent_version"
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
androidTestImplementation libs.androidx.testCore
androidTestImplementation libs.androidx.testRunner
androidTestImplementation libs.androidx.testRules
androidTestImplementation libs.androidx.junit
androidTestImplementation libs.androidx.espressoCore
androidTestImplementation libs.androidx.espressoContrib
androidTestImplementation libs.androidx.espressoIntents
androidTestImplementation libs.tests.kluent
androidTestImplementation libs.androidx.coreTesting
// 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"
androidTestImplementation('com.adevinta.android:barista:4.1.0') {
androidTestImplementation('com.adevinta.android:barista:4.2.0') {
exclude group: 'org.jetbrains.kotlin'
}
androidTestUtil libs.androidx.orchestrator
}

View file

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

View file

@ -17,6 +17,10 @@
package im.vector.app.features.reactions.data
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.assertTrue
import org.junit.FixMethodOrder
@ -30,64 +34,80 @@ import kotlin.system.measureTimeMillis
@FixMethodOrder(MethodSorters.JVM)
class EmojiDataSourceTest : InstrumentedTest {
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
@Test
fun checkParsingTime() {
val time = measureTimeMillis {
EmojiDataSource(context().resources)
createEmojiDataSource()
}
assertTrue("Too long to parse", time < 100)
}
@Test
fun checkNumberOfResult() {
val emojiDataSource = EmojiDataSource(context().resources)
assertTrue("Wrong number of emojis", emojiDataSource.rawData.emojis.size >= 500)
assertTrue("Wrong number of categories", emojiDataSource.rawData.categories.size >= 8)
val emojiDataSource = createEmojiDataSource()
val rawData = runBlocking {
emojiDataSource.rawData.await()
}
assertTrue("Wrong number of emojis", rawData.emojis.size >= 500)
assertTrue("Wrong number of categories", rawData.categories.size >= 8)
}
@Test
fun searchTestEmptySearch() {
val emojiDataSource = EmojiDataSource(context().resources)
assertTrue("Empty search should return at least 500 results", emojiDataSource.filterWith("").size >= 500)
val emojiDataSource = createEmojiDataSource()
val result = runBlocking {
emojiDataSource.filterWith("")
}
assertTrue("Empty search should return at least 500 results", result.size >= 500)
}
@Test
fun searchTestNoResult() {
val emojiDataSource = EmojiDataSource(context().resources)
assertTrue("Should not have result", emojiDataSource.filterWith("noresult").isEmpty())
val emojiDataSource = createEmojiDataSource()
val result = runBlocking {
emojiDataSource.filterWith("noresult")
}
assertTrue("Should not have result", result.isEmpty())
}
@Test
fun searchTestOneResult() {
val emojiDataSource = EmojiDataSource(context().resources)
assertEquals("Should have 1 result", 1, emojiDataSource.filterWith("france").size)
val emojiDataSource = createEmojiDataSource()
val result = runBlocking {
emojiDataSource.filterWith("france")
}
assertEquals("Should have 1 result", 1, result.size)
}
@Test
fun searchTestManyResult() {
val emojiDataSource = EmojiDataSource(context().resources)
assertTrue("Should have many result", emojiDataSource.filterWith("fra").size > 1)
val emojiDataSource = createEmojiDataSource()
val result = runBlocking {
emojiDataSource.filterWith("fra")
}
assertTrue("Should have many result", result.size > 1)
}
@Test
fun testTada() {
val emojiDataSource = EmojiDataSource(context().resources)
val result = emojiDataSource.filterWith("tada")
val emojiDataSource = createEmojiDataSource()
val result = runBlocking {
emojiDataSource.filterWith("tada")
}
assertEquals("Should find tada emoji", 1, result.size)
assertEquals("Should find tada emoji", "🎉", result[0].emoji)
}
@Test
fun testQuickReactions() {
val emojiDataSource = EmojiDataSource(context().resources)
assertEquals("Should have 8 quick reactions", 8, emojiDataSource.getQuickReactions().size)
val emojiDataSource = createEmojiDataSource()
val result = runBlocking {
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)
// Filter
// TODO clickMenu(R.id.search)
// Wait for emoji to load, it's async now
sleep(1_000)
clickListItem(R.id.emojiRecyclerView, 4)
// Test Edit mode
@ -283,6 +285,7 @@ class UiAllScreensSanityTest {
clickListItem(R.id.matrixProfileRecyclerView, 9)
// File tab
clickOn(R.string.uploads_files_title)
sleep(1000)
pressBack()
assertDisplayed(R.id.roomProfileAvatarView)
@ -334,6 +337,7 @@ class UiAllScreensSanityTest {
private fun navigateToRoomPeople() {
// Open first user
clickListItem(R.id.roomSettingsRecyclerView, 1)
sleep(1000)
assertDisplayed(R.id.memberProfilePowerLevelView)
// Verification
@ -342,8 +346,9 @@ class UiAllScreensSanityTest {
// Role
clickListItem(R.id.matrixProfileRecyclerView, 3)
sleep(1000)
clickDialogNegativeButton()
sleep(1000)
clickBack()
}

View file

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

View file

@ -61,7 +61,20 @@ class AppStateHandler @Inject constructor(
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) {
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.create.ChoosePrivateSpaceTypeFragment
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.CreateSpaceDetailsFragment
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.SpaceManageRoomsFragment
import im.vector.app.features.spaces.manage.SpaceSettingsFragment
@ -799,6 +801,11 @@ interface FragmentModule {
@FragmentKey(ChoosePrivateSpaceTypeFragment::class)
fun bindChoosePrivateSpaceTypeFragment(fragment: ChoosePrivateSpaceTypeFragment): Fragment
@Binds
@IntoMap
@FragmentKey(CreateSpaceAdd3pidInvitesFragment::class)
fun bindCreateSpaceAdd3pidInvitesFragment(fragment: CreateSpaceAdd3pidInvitesFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SpaceAddRoomFragment::class)
@ -828,4 +835,9 @@ interface FragmentModule {
@IntoMap
@FragmentKey(RoomJoinRuleChooseRestrictedFragment::class)
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.signout.soft.SoftLogoutActivity
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.SpaceExploreActivity
import im.vector.app.features.spaces.SpaceSettingsMenuBottomSheet
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.share.ShareSpaceBottomSheet
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.permissions.RoomWidgetPermissionBottomSheet
import im.vector.app.features.workers.signout.SignOutBottomSheetDialogFragment
import kotlinx.coroutines.CoroutineScope
@Component(
dependencies = [
@ -127,6 +130,7 @@ interface ScreenComponent {
fun uiStateRepository(): UiStateRepository
fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog
fun autoAcceptInvites(): AutoAcceptInvites
fun appCoroutineScope(): CoroutineScope
/* ==========================================================================================
* Activities
@ -171,6 +175,7 @@ interface ScreenComponent {
fun inject(activity: SpaceExploreActivity)
fun inject(activity: SpaceManageActivity)
fun inject(activity: RoomJoinRuleActivity)
fun inject(activity: SpaceLeaveAdvancedActivity)
/* ==========================================================================================
* BottomSheets
@ -199,6 +204,7 @@ interface ScreenComponent {
fun inject(bottomSheet: SpaceInviteBottomSheet)
fun inject(bottomSheet: JoinReplacementRoomBottomSheet)
fun inject(bottomSheet: MigrateRoomBottomSheet)
fun inject(bottomSheet: LeaveSpaceBottomSheet)
/* ==========================================================================================
* 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.reactions.data.EmojiDataSource
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.ui.UiStateRepository
import kotlinx.coroutines.CoroutineScope
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
@ -145,6 +147,8 @@ interface VectorComponent {
fun vectorPreferences(): VectorPreferences
fun vectorDataStore(): VectorDataStore
fun wifiDetector(): WifiDetector
fun vectorFileLogger(): VectorFileLogger
@ -165,6 +169,8 @@ interface VectorComponent {
fun webRtcCallManager(): WebRtcCallManager
fun appCoroutineScope(): CoroutineScope
fun jitsiActiveConferenceHolder(): JitsiActiveConferenceHolder
@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.ui.SharedPreferencesUiStateRepository
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.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
import javax.inject.Singleton
@Module
abstract class VectorModule {
@ -94,6 +98,13 @@ abstract class VectorModule {
fun providesHomeServerHistoryService(matrix: Matrix): HomeServerHistoryService {
return matrix.homeServerHistoryService()
}
@Provides
@JvmStatic
@Singleton
fun providesApplicationCoroutineScope(): CoroutineScope {
return CoroutineScope(SupervisorJob() + Dispatchers.Main)
}
}
@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
*/
fun TextView.setTextOrHide(newText: CharSequence?, hideWhenBlank: Boolean = true) {
fun TextView.setTextOrHide(newText: CharSequence?, hideWhenBlank: Boolean = true, vararg relatedViews: View = emptyArray()) {
if (newText == null
|| (newText.isBlank() && hideWhenBlank)) {
isVisible = false
relatedViews.forEach { it.isVisible = false }
} else {
this.text = newText
isVisible = true
relatedViews.forEach { it.isVisible = true }
}
}

View file

@ -16,11 +16,14 @@
package im.vector.app.core.extensions
import android.graphics.drawable.Drawable
import android.text.InputType
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageView
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import im.vector.app.R
/**
@ -50,3 +53,8 @@ fun View.getMeasurements(): Pair<Int, Int> {
val height = measuredHeight
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) {
val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
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.NotificationUtils
import im.vector.app.features.settings.BackgroundSyncMode
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
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.unifiedpush.android.connector.MessagingReceiver
import org.unifiedpush.android.connector.MessagingReceiverHandler
@ -69,6 +71,8 @@ data class Counts(
val unread: Int = 0
)
private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
/**
* UnifiedPush handler.
*/
@ -79,6 +83,7 @@ val upHandler = object: MessagingReceiverHandler {
private lateinit var pusherManager: PushersManager
private lateinit var activeSessionHolder: ActiveSessionHolder
private lateinit var vectorPreferences: VectorPreferences
private lateinit var vectorDataStore: VectorDataStore
private lateinit var wifiDetector: WifiDetector
private val coroutineScope = CoroutineScope(SupervisorJob())
@ -95,6 +100,7 @@ val upHandler = object: MessagingReceiverHandler {
pusherManager = pusherManager()
activeSessionHolder = activeSessionHolder()
vectorPreferences = vectorPreferences()
vectorDataStore = vectorDataStore()
wifiDetector = wifiDetector()
}
}
@ -108,9 +114,14 @@ val upHandler = object: MessagingReceiverHandler {
override fun onMessage(context: Context?, message: String, instance: String) {
initVar(context!!)
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()
.add(KotlinJsonAdapterFactory())
@ -127,6 +138,7 @@ val upHandler = object: MessagingReceiverHandler {
notification.unread = notification.counts.unread
}
// Diagnostic Push
if (notification.eventId == PushersManager.TEST_EVENT_ID) {
val intent = Intent(NotificationUtils.PUSH_ACTION)
@ -135,14 +147,14 @@ val upHandler = object: MessagingReceiverHandler {
}
if (!vectorPreferences.areNotificationEnabledForDevice()) {
Timber.i("Notification are disabled for this device")
Timber.tag(loggerTag.value).i("Notification are disabled for this device")
return
}
mUIHandler.post {
if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// 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 {
onMessageReceivedInternal(context, notification)
}
@ -157,7 +169,7 @@ val upHandler = object: MessagingReceiverHandler {
*/
override fun onNewEndpoint(context: Context?, endpoint: String, instance: String) {
initVar(context!!)
Timber.i("onNewEndpoint: adding $endpoint")
Timber.tag(loggerTag.value).i("onNewEndpoint: adding $endpoint")
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
val gateway = UPHelper.customOrDefaultGateway(context, endpoint)
if (UPHelper.getUpEndpoint(context) != endpoint
@ -166,7 +178,7 @@ val upHandler = object: MessagingReceiverHandler {
UPHelper.storeUpEndpoint(context, endpoint)
pusherManager.registerPusher(context, endpoint, gateway)
} else {
Timber.i("onNewEndpoint: skipped")
Timber.tag(loggerTag.value).i("onNewEndpoint: skipped")
}
}
if (!UPHelper.allowBackgroundSync(context)) {
@ -184,7 +196,7 @@ val upHandler = object: MessagingReceiverHandler {
}
override fun onUnregistered(context: Context?, instance: String) {
Timber.d("Unifiedpush: Unregistered")
Timber.tag(loggerTag.value).d("Unifiedpush: Unregistered")
initVar(context!!)
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY
vectorPreferences.setFdroidSyncBackgroundMode(mode)
@ -192,7 +204,7 @@ val upHandler = object: MessagingReceiverHandler {
try {
pusherManager.unregisterPusher(context, UPHelper.getUpEndpoint(context)!!)
} 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) {
try {
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
@ -214,21 +228,21 @@ val upHandler = object: MessagingReceiverHandler {
val session = activeSessionHolder.getSafeActiveSession()
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 {
if (isEventAlreadyKnown(notification.eventId, notification.roomId)) {
Timber.d("Ignoring push, event already known")
Timber.tag(loggerTag.value).d("Ignoring push, event already known")
} else {
// 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)
Timber.d("Requesting background sync")
Timber.tag(loggerTag.value).d("Requesting background sync")
session.requireBackgroundSync()
}
}
} 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()) {
Timber.d("No WiFi network, do not get Event")
Timber.tag(loggerTag.value).d("No WiFi network, do not get Event")
return
}
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 resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event)
resolvedEvent
?.also { Timber.d("Fast lane: notify drawer") }
?.also { Timber.tag(loggerTag.value).d("Fast lane: notify drawer") }
?.let {
it.isPushGatewayEvent = true
notificationDrawerManager.onNotifiableEventReceived(it)
@ -271,7 +285,7 @@ val upHandler = object: MessagingReceiverHandler {
val room = session.getRoom(roomId) ?: return false
return room.getTimeLineEvent(eventId) != null
} 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

View file

@ -50,7 +50,7 @@ private val loggerTag = LoggerTag("CallService", LoggerTag.VOIP)
class CallService : VectorService() {
private val connections = mutableMapOf<String, CallConnection>()
private val knownCalls = mutableSetOf<CallInformation>()
private val knownCalls = mutableMapOf<String, CallInformation>()
private val connectedCallIds = mutableSetOf<String>()
private lateinit var notificationManager: NotificationManagerCompat
@ -190,7 +190,7 @@ class CallService : VectorService() {
} else {
notificationManager.notify(callId.hashCode(), notification)
}
knownCalls.add(callInformation)
knownCalls[callId] = callInformation
}
private fun handleCallTerminated(intent: Intent) {
@ -198,20 +198,22 @@ class CallService : VectorService() {
val endCallReason = intent.getSerializableExtra(EXTRA_END_CALL_REASON) as EndCallReason
val rejected = intent.getBooleanExtra(EXTRA_END_CALL_REJECTED, false)
alertManager.cancelAlert(callId)
val terminatedCall = knownCalls.firstOrNull { it.callId == callId }
val terminatedCall = knownCalls.remove(callId)
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)
return
}
knownCalls.remove(terminatedCall)
val notification = notificationUtils.buildCallEndedNotification(false)
val notificationId = callId.hashCode()
startForeground(notificationId, notification)
if (knownCalls.isEmpty()) {
Timber.tag(loggerTag.value).v("No more call, stop the service")
stopForeground(true)
mediaSession?.isActive = false
myStopSelf()
}
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) {
val missedCallNotification = notificationUtils.buildCallMissedNotification(terminatedCall)
notificationManager.notify(MISSED_CALL_TAG, terminatedCall.nativeRoomId.hashCode(), missedCallNotification)
@ -243,7 +245,7 @@ class CallService : VectorService() {
} else {
notificationManager.notify(callId.hashCode(), notification)
}
knownCalls.add(callInformation)
knownCalls[callId] = callInformation
}
/**
@ -267,18 +269,19 @@ class CallService : VectorService() {
} else {
notificationManager.notify(callId.hashCode(), notification)
}
knownCalls.add(callInformation)
knownCalls[callId] = callInformation
}
private fun handleUnexpectedState(callId: String?) {
Timber.tag(loggerTag.value).v("Fallback to clear everything")
callRingPlayerIncoming?.stop()
callRingPlayerOutgoing?.stop()
if (callId != null) {
notificationManager.cancel(callId.hashCode())
}
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()) {
mediaSession?.isActive = false
myStopSelf()
@ -371,7 +374,7 @@ class CallService : VectorService() {
putExtra(EXTRA_END_CALL_REASON, endCallReason)
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.isVisible
import im.vector.app.R
import im.vector.app.core.extensions.setDrawableOrHide
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.resources.ColorProvider
import im.vector.app.databinding.ViewBottomSheetActionButtonBinding
@ -81,7 +82,7 @@ class BottomSheetActionButton @JvmOverloads constructor(
var rightIcon: Drawable? = null
set(value) {
field = value
views.bottomSheetActionIcon.setImageDrawable(value)
views.bottomSheetActionIcon.setDrawableOrHide(value)
}
var tint: Int? = null
@ -96,6 +97,12 @@ class BottomSheetActionButton @JvmOverloads constructor(
value?.let { views.bottomSheetActionTitle.setTextColor(it) }
}
var isBetaAction: Boolean? = null
set(value) {
field = value
views.bottomSheetActionBeta.isVisible = field ?: false
}
init {
inflate(context, R.layout.view_bottom_sheet_action_button, 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))
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?.addListener(tickListener)
this.calls = calls
val hasActiveCall = currentCall != null
val hasActiveCall = calls.isNotEmpty()
currentCallsView?.isVisible = hasActiveCall
currentCallsView?.render(calls, currentCall?.formattedDuration() ?: "")
}

View file

@ -19,7 +19,6 @@ package im.vector.app.core.ui.views
import android.content.Context
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.databinding.ViewFailedMessagesWarningBinding
@ -49,8 +48,4 @@ class FailedMessagesWarningView @JvmOverloads constructor(
views.failedMessagesDeleteAllButton.setOnClickListener { callback?.onDeleteAllClicked() }
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.WebViewClient
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
/**
* Open a web view above the current activity.
@ -38,3 +39,14 @@ fun Context.displayInWebView(url: String) {
.setPositiveButton(android.R.string.ok, null)
.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.RecyclerViewPresenter
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
class AutocompleteEmojiPresenter @Inject constructor(context: Context,
@ -28,11 +33,14 @@ class AutocompleteEmojiPresenter @Inject constructor(context: Context,
private val controller: AutocompleteEmojiController) :
RecyclerViewPresenter<String>(context), AutocompleteClickListener<String> {
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
init {
controller.listener = this
}
fun clear() {
coroutineScope.coroutineContext.cancelChildren()
controller.listener = null
}
@ -45,12 +53,14 @@ class AutocompleteEmojiPresenter @Inject constructor(context: Context,
}
override fun onQuery(query: CharSequence?) {
val data = if (query.isNullOrBlank()) {
// Return common emojis
emojiDataSource.getQuickReactions()
} else {
emojiDataSource.filterWith(query.toString())
coroutineScope.launch {
val data = if (query.isNullOrBlank()) {
// Return common emojis
emojiDataSource.getQuickReactions()
} 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 androidx.recyclerview.widget.RecyclerView
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.features.autocomplete.AutocompleteClickListener
import im.vector.app.features.autocomplete.RecyclerViewPresenter
import org.matrix.android.sdk.api.query.QueryStringValue
@ -35,7 +35,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
private val controller: AutocompleteMemberController
) : RecyclerViewPresenter<RoomMemberSummary>(context), AutocompleteClickListener<RoomMemberSummary> {
private val room = session.getRoom(roomId)!!
private val room by lazy { session.getRoom(roomId)!! }
init {
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?) {
val knownCalls = callManager.getCalls()
liveKnownCalls.postValue(knownCalls)
@ -50,12 +50,17 @@ class SharedKnownCallsViewModel @Inject constructor(
it.addListener(callListener)
}
}
override fun onCallEnded(callId: String) {
val knownCalls = callManager.getCalls()
liveKnownCalls.postValue(knownCalls)
}
}
init {
val knownCalls = callManager.getCalls()
liveKnownCalls.postValue(knownCalls)
callManager.addCurrentCallListener(currentCallListener)
callManager.addListener(callManagerListener)
knownCalls.forEach {
it.addListener(callListener)
}
@ -65,7 +70,7 @@ class SharedKnownCallsViewModel @Inject constructor(
callManager.getCalls().forEach {
it.removeListener(callListener)
}
callManager.removeCurrentCallListener(currentCallListener)
callManager.removeListener(callManagerListener)
super.onCleared()
}
}

View file

@ -134,7 +134,15 @@ class VectorCallViewModel @AssistedInject constructor(
} ?: 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?) {
if (call != null) {
@ -159,9 +167,7 @@ class VectorCallViewModel @AssistedInject constructor(
}
private fun updateOtherKnownCall(currentCall: WebRtcCall) {
val otherCall = callManager.getCalls().firstOrNull {
it.callId != currentCall.callId && it.mxCall.state is CallState.Connected
}
val otherCall = getOtherKnownCall(currentCall)
setState {
if (otherCall == 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 {
setupCallWithCurrentState()
}
@ -184,7 +196,7 @@ class VectorCallViewModel @AssistedInject constructor(
}
} else {
call = webRtcCall
callManager.addCurrentCallListener(currentCallListener)
callManager.addListener(callManagerListener)
webRtcCall.addListener(callListener)
val currentSoundDevice = callManager.audioManager.selectedDevice
if (currentSoundDevice == CallAudioManager.Device.Phone) {
@ -230,7 +242,7 @@ class VectorCallViewModel @AssistedInject constructor(
}
override fun onCleared() {
callManager.removeCurrentCallListener(currentCallListener)
callManager.removeListener(callManagerListener)
call?.removeListener(callListener)
call = null
proximityManager.stop()
@ -310,10 +322,10 @@ class VectorCallViewModel @AssistedInject constructor(
VectorCallViewEvents.ShowCallTransferScreen
)
}
VectorCallViewActions.TransferCall -> {
VectorCallViewActions.TransferCall -> {
handleCallTransfer()
}
is VectorCallViewActions.SwitchCall -> {
is VectorCallViewActions.SwitchCall -> {
setState { VectorCallViewState(action.callArgs) }
setupCallWithCurrentState()
}

View file

@ -28,20 +28,21 @@ import com.facebook.react.bridge.JavaOnlyMap
import org.jitsi.meet.sdk.BroadcastEmitter
import org.jitsi.meet.sdk.BroadcastEvent
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"
fun BroadcastEvent.extractConferenceUrl(): String? {
return when (type) {
BroadcastEvent.Type.CONFERENCE_TERMINATED,
BroadcastEvent.Type.CONFERENCE_WILL_JOIN,
BroadcastEvent.Type.CONFERENCE_JOINED -> data[CONFERENCE_URL_DATA_KEY] as? String
else -> null
sealed class ConferenceEvent(open val data: Map<String, Any>) {
data class Terminated(override val data: Map<String, Any>) : ConferenceEvent(data)
data class WillJoin(override val data: Map<String, Any>) : ConferenceEvent(data)
data class Joined(override val data: Map<String, Any>) : ConferenceEvent(data)
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() {
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,
private val onBroadcastEvent: (BroadcastEvent) -> Unit) : LifecycleObserver {
class ConferenceEventObserver(private val context: Context,
private val onBroadcastEvent: (ConferenceEvent) -> Unit)
: LifecycleObserver {
// See https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-android-sdk#listening-for-broadcasted-events
private val broadcastReceiver = object : BroadcastReceiver() {
@ -61,8 +63,10 @@ class JitsiBroadcastEventObserver(private val context: Context,
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun unregisterForBroadcastMessages() {
tryOrNull("Unable to unregister receiver") {
try {
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()) {
intentFilter.addAction(type.action)
}
tryOrNull("Unable to register receiver") {
try {
LocalBroadcastManager.getInstance(context).registerReceiver(broadcastReceiver, intentFilter)
} catch (throwable: Throwable) {
Timber.v("Unable to register receiver")
}
}
private fun onBroadcastReceived(intent: 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