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:
commit
4f93eb041c
|
@ -36,6 +36,7 @@ body:
|
||||||
- [ ] Push `main` and the new tag `v1.1.10` to origin
|
- [ ] Push `main` and the new tag `v1.1.10` to origin
|
||||||
- [ ] Checkout `develop`
|
- [ ] Checkout `develop`
|
||||||
- [ ] Increase version in `./vector/build.gradle`
|
- [ ] Increase version in `./vector/build.gradle`
|
||||||
|
- [ ] Change the value of SDK_VERSION in the file `./matrix-sdk-android/build.gradle`
|
||||||
- [ ] Commit and push `develop`
|
- [ ] Commit and push `develop`
|
||||||
- [ ] Wait for [Buildkite](https://buildkite.com/matrix-dot-org/element-android/builds?branch=main) to build the `main` branch.
|
- [ ] Wait for [Buildkite](https://buildkite.com/matrix-dot-org/element-android/builds?branch=main) to build the `main` branch.
|
||||||
- [ ] Run the script `~/scripts/releaseElement.sh`. It will download the APKs from Buildkite check them and sign them.
|
- [ ] Run the script `~/scripts/releaseElement.sh`. It will download the APKs from Buildkite check them and sign them.
|
||||||
|
@ -72,9 +73,25 @@ body:
|
||||||
- [ ] Create a release with GitFlow
|
- [ ] Create a release with GitFlow
|
||||||
- [ ] Update the files `./build.gradle` and `./gradle/gradle-wrapper.properties` manually, to use the latest version for the dependency. You can get inspired by the same files on Element Android project.
|
- [ ] Update the files `./build.gradle` and `./gradle/gradle-wrapper.properties` manually, to use the latest version for the dependency. You can get inspired by the same files on Element Android project.
|
||||||
- [ ] Run the script `./tools/import_from_element.sh`
|
- [ ] Run the script `./tools/import_from_element.sh`
|
||||||
- [ ] Update the version in `./matrix-sdk-android/build.gradle` and let the script finish to build the library
|
- [ ] Update the version in `./matrix-sdk-android/build.gradle`
|
||||||
|
- [ ] Check the diff on this file and restore what may have been erased (in particular the line `apply plugin: "com.vanniktech.maven.publish"`)
|
||||||
|
- [ ] Let the script finish to build the library
|
||||||
- [ ] Update the file `CHANGES.md`
|
- [ ] Update the file `CHANGES.md`
|
||||||
|
- [ ] Update the value of VERSION_NAME in the file gradle.properties
|
||||||
- [ ] Finish the release using GitFlow
|
- [ ] Finish the release using GitFlow
|
||||||
|
|
||||||
|
##### Release on MavenCentral
|
||||||
|
|
||||||
|
- [ ] Run the command `./gradlew publish --no-daemon --no-parallel`. You'll need some non-public element to do so
|
||||||
|
- [ ] Connect to https://s01.oss.sonatype.org
|
||||||
|
- [ ] Click on Staging Repositories and check the the files have been uploaded
|
||||||
|
- [ ] Click on close
|
||||||
|
- [ ] Wait (check Activity tab until step "Repository closed" is displayed)
|
||||||
|
- [ ] Click on release. The staging repository will disappear
|
||||||
|
- [ ] Check that the release is available in https://repo1.maven.org/maven2/org/matrix/android/matrix-android-sdk2/ (it can take a few minutes)
|
||||||
|
|
||||||
|
##### Release on GitHub
|
||||||
|
|
||||||
- [ ] Create the release on GitHub from [the tag](https://github.com/matrix-org/matrix-android-sdk2/tags)
|
- [ ] Create the release on GitHub from [the tag](https://github.com/matrix-org/matrix-android-sdk2/tags)
|
||||||
- [ ] Upload the AAR on the GitHub release
|
- [ ] Upload the AAR on the GitHub release
|
||||||
|
|
||||||
|
@ -82,7 +99,7 @@ body:
|
||||||
|
|
||||||
https://github.com/matrix-org/matrix-android-sdk2-sample
|
https://github.com/matrix-org/matrix-android-sdk2-sample
|
||||||
|
|
||||||
- [ ] Update the dependency to the new version of the SDK2. Jitpack will have to build the AAR, it can take a few minutes. You can check status on https://jitpack.io/#matrix-org/matrix-android-sdk2
|
- [ ] Update the dependency to the new version of the SDK2. It can take some time for MavenCentral to make the librarie available. You can check status on https://repo1.maven.org/maven2/org/matrix/android/matrix-android-sdk2/
|
||||||
- [ ] Build and run the sample, you may have to fix some API break
|
- [ ] Build and run the sample, you may have to fix some API break
|
||||||
- [ ] Commit and push directly on `main`
|
- [ ] Commit and push directly on `main`
|
||||||
validations:
|
validations:
|
||||||
|
|
|
@ -5,6 +5,12 @@ on:
|
||||||
push:
|
push:
|
||||||
branches: [ main, develop ]
|
branches: [ main, develop ]
|
||||||
|
|
||||||
|
# Enrich gradle.properties for CI/CD
|
||||||
|
env:
|
||||||
|
CI_GRADLE_ARG_PROPERTIES: >
|
||||||
|
-Porg.gradle.jvmargs=-Xmx2g
|
||||||
|
-Porg.gradle.parallel=false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
debug:
|
debug:
|
||||||
name: Build debug APKs (${{ matrix.target }})
|
name: Build debug APKs (${{ matrix.target }})
|
||||||
|
@ -25,7 +31,7 @@ jobs:
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-gradle-
|
${{ runner.os }}-gradle-
|
||||||
- name: Assemble ${{ matrix.target }} debug apk
|
- name: Assemble ${{ matrix.target }} debug apk
|
||||||
run: ./gradlew assemble${{ matrix.target }}Debug --stacktrace
|
run: ./gradlew assemble${{ matrix.target }}Debug $CI_GRADLE_ARG_PROPERTIES --stacktrace
|
||||||
- name: Upload ${{ matrix.target }} debug APKs
|
- name: Upload ${{ matrix.target }} debug APKs
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
|
@ -48,7 +54,7 @@ jobs:
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-gradle-
|
${{ runner.os }}-gradle-
|
||||||
- name: Assemble GPlay unsigned apk
|
- name: Assemble GPlay unsigned apk
|
||||||
run: ./gradlew clean assembleGplayRelease --stacktrace
|
run: ./gradlew clean assembleGplayRelease $CI_GRADLE_ARG_PROPERTIES --stacktrace
|
||||||
- name: Upload Gplay unsigned APKs
|
- name: Upload Gplay unsigned APKs
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -5,6 +5,12 @@ on:
|
||||||
push:
|
push:
|
||||||
branches: [ main, develop ]
|
branches: [ main, develop ]
|
||||||
|
|
||||||
|
# Enrich gradle.properties for CI/CD
|
||||||
|
env:
|
||||||
|
CI_GRADLE_ARG_PROPERTIES: >
|
||||||
|
-Porg.gradle.jvmargs=-Xmx2g
|
||||||
|
-Porg.gradle.parallel=false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Temporary add build of Android tests, which cannot be run on the CI right now, but they need to at least compile
|
# Temporary add build of Android tests, which cannot be run on the CI right now, but they need to at least compile
|
||||||
# So it will be mandatory for this action to be successful on every PRs
|
# So it will be mandatory for this action to be successful on every PRs
|
||||||
|
@ -22,7 +28,7 @@ jobs:
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-gradle-
|
${{ runner.os }}-gradle-
|
||||||
- name: Compile Android tests
|
- name: Compile Android tests
|
||||||
run: ./gradlew clean assembleAndroidTest --stacktrace -PallWarningsAsErrors=false
|
run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace -PallWarningsAsErrors=false
|
||||||
|
|
||||||
integration-tests:
|
integration-tests:
|
||||||
name: Integration Tests (Synapse)
|
name: Integration Tests (Synapse)
|
||||||
|
@ -30,9 +36,14 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
api-level: [21, 28, 30]
|
api-level: [28]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- uses: gradle/wrapper-validation-action@v1
|
||||||
|
- uses: actions/setup-java@v2
|
||||||
|
with:
|
||||||
|
distribution: 'adopt'
|
||||||
|
java-version: 11
|
||||||
- name: Set up Python 3.8
|
- name: Set up Python 3.8
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
|
@ -64,5 +75,12 @@ jobs:
|
||||||
uses: reactivecircus/android-emulator-runner@v2
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
with:
|
with:
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
|
#arch: x86_64
|
||||||
|
#disable-animations: true
|
||||||
# script: ./gradlew -PallWarningsAsErrors=false vector:connectedAndroidTest matrix-sdk-android:connectedAndroidTest
|
# script: ./gradlew -PallWarningsAsErrors=false vector:connectedAndroidTest matrix-sdk-android:connectedAndroidTest
|
||||||
script: ./gradlew -PallWarningsAsErrors=false connectedCheck
|
arch: x86
|
||||||
|
profile: Nexus 5X
|
||||||
|
force-avd-creation: false
|
||||||
|
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||||
|
emulator-build: 7425822
|
||||||
|
script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace
|
||||||
|
|
|
@ -5,6 +5,12 @@ on:
|
||||||
push:
|
push:
|
||||||
branches: [ main, develop ]
|
branches: [ main, develop ]
|
||||||
|
|
||||||
|
# Enrich gradle.properties for CI/CD
|
||||||
|
env:
|
||||||
|
CI_GRADLE_ARG_PROPERTIES: >
|
||||||
|
-Porg.gradle.jvmargs=-Xmx2g
|
||||||
|
-Porg.gradle.parallel=false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
integration-tests:
|
integration-tests:
|
||||||
name: Sanity Tests (Synapse)
|
name: Sanity Tests (Synapse)
|
||||||
|
@ -46,5 +52,5 @@ jobs:
|
||||||
uses: reactivecircus/android-emulator-runner@v2
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
with:
|
with:
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
script: ./gradlew -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest
|
script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,12 @@ on:
|
||||||
push:
|
push:
|
||||||
branches: [main, develop]
|
branches: [main, develop]
|
||||||
|
|
||||||
|
# Enrich gradle.properties for CI/CD
|
||||||
|
env:
|
||||||
|
CI_GRADLE_ARG_PROPERTIES: >
|
||||||
|
-Porg.gradle.jvmargs=-Xmx2g
|
||||||
|
-Porg.gradle.parallel=false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
unit-tests:
|
unit-tests:
|
||||||
name: Run Unit Tests
|
name: Run Unit Tests
|
||||||
|
@ -20,4 +26,11 @@ jobs:
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-gradle-
|
${{ runner.os }}-gradle-
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: ./gradlew clean test --stacktrace -PallWarningsAsErrors=false
|
run: ./gradlew clean test $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false --stacktrace
|
||||||
|
- name: Publish Unit Test Results
|
||||||
|
uses: EnricoMi/publish-unit-test-result-action@v1
|
||||||
|
if: always() &&
|
||||||
|
github.event.sender.login != 'dependabot[bot]' &&
|
||||||
|
( github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository )
|
||||||
|
with:
|
||||||
|
files: ./**/build/test-results/**/*.xml
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
.idea/*.xml
|
.idea/*.xml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
|
/benchmark-out
|
||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
|
||||||
|
|
50
CHANGES.md
50
CHANGES.md
|
@ -1,3 +1,53 @@
|
||||||
|
Changes in Element v1.3.0 (2021-09-27)
|
||||||
|
======================================
|
||||||
|
|
||||||
|
Features ✨
|
||||||
|
----------
|
||||||
|
- Spaces!
|
||||||
|
- Adds email notification registration to Settings ([#2243](https://github.com/vector-im/element-android/issues/2243))
|
||||||
|
- Spaces | M3.23 Invite by email in create private space flow ([#3678](https://github.com/vector-im/element-android/issues/3678))
|
||||||
|
- Improve space invite bottom sheet ([#4057](https://github.com/vector-im/element-android/issues/4057))
|
||||||
|
- Allow to also leave rooms when leaving a space ([#3692](https://github.com/vector-im/element-android/issues/3692))
|
||||||
|
- Better expose adding spaces as Subspaces ([#3752](https://github.com/vector-im/element-android/issues/3752))
|
||||||
|
- Push and syncs: add debug info on room list and on room detail screen and improves the log format. ([#4046](https://github.com/vector-im/element-android/issues/4046))
|
||||||
|
|
||||||
|
Bugfixes 🐛
|
||||||
|
----------
|
||||||
|
- Remove the "Teammate spaces aren't quite ready" bottom sheet ([#3945](https://github.com/vector-im/element-android/issues/3945))
|
||||||
|
- Restricted Room previews aren't working ([#3946](https://github.com/vector-im/element-android/issues/3946))
|
||||||
|
- A removed room from a space can't be re-added as it won't be shown in add-room ([#3947](https://github.com/vector-im/element-android/issues/3947))
|
||||||
|
- "Non-Admin" user able to invite others to Private Space (by default) ([#3951](https://github.com/vector-im/element-android/issues/3951))
|
||||||
|
- Kick user dialog for spaces talks about rooms ([#3956](https://github.com/vector-im/element-android/issues/3956))
|
||||||
|
- Messages are displayed as unable to decrypt then decrypted a few seconds later ([#4011](https://github.com/vector-im/element-android/issues/4011))
|
||||||
|
- Fix DTMF not working ([#4015](https://github.com/vector-im/element-android/issues/4015))
|
||||||
|
- Fix sticky end call notification ([#4019](https://github.com/vector-im/element-android/issues/4019))
|
||||||
|
- Fix call screen stuck with some hanging up scenarios ([#4026](https://github.com/vector-im/element-android/issues/4026))
|
||||||
|
- Fix other call not always refreshed when ended ([#4028](https://github.com/vector-im/element-android/issues/4028))
|
||||||
|
- Private space invite bottomsheet only offering inviting by username not by email ([#4042](https://github.com/vector-im/element-android/issues/4042))
|
||||||
|
- Spaces invitation system notifications don't take me to the join space toast ([#4043](https://github.com/vector-im/element-android/issues/4043))
|
||||||
|
- Space Invites are not lighting up the drawer menu ([#4059](https://github.com/vector-im/element-android/issues/4059))
|
||||||
|
- MessageActionsBottomSheet not being shown on local echos ([#4068](https://github.com/vector-im/element-android/issues/4068))
|
||||||
|
|
||||||
|
SDK API changes ⚠️
|
||||||
|
------------------
|
||||||
|
- InitialSyncProgressService has been renamed to SyncStatusService and its function getInitialSyncProgressStatus() has been renamed to getSyncStatusLive() ([#4046](https://github.com/vector-im/element-android/issues/4046))
|
||||||
|
|
||||||
|
Other changes
|
||||||
|
-------------
|
||||||
|
- Better support for Sdk2 version. Also slight change in the default user agent: `MatrixAndroidSDK_X` is replaced by `MatrixAndroidSdk2` ([#3994](https://github.com/vector-im/element-android/issues/3994))
|
||||||
|
- Introduces ConferenceEvent to abstract usage of Jitsi BroadcastEvent class. ([#4014](https://github.com/vector-im/element-android/issues/4014))
|
||||||
|
- Improve performances on RoomDetail screen ([#4065](https://github.com/vector-im/element-android/issues/4065))
|
||||||
|
|
||||||
|
|
||||||
|
Changes in Element v1.2.2 (2021-09-13)
|
||||||
|
======================================
|
||||||
|
|
||||||
|
Bugfixes 🐛
|
||||||
|
----------
|
||||||
|
|
||||||
|
- Fix a security issue with message key sharing. See https://matrix.org/blog/2021/09/13/vulnerability-disclosure-key-sharing for details.
|
||||||
|
|
||||||
|
|
||||||
Changes in Element v1.2.1 (2021-09-08)
|
Changes in Element v1.2.1 (2021-09-08)
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,12 @@ apply plugin: 'com.android.library'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
|
||||||
|
compileSdk versions.compileSdk
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdk versions.minSdk
|
||||||
targetSdkVersion 30
|
targetSdk versions.targetSdk
|
||||||
versionCode 1
|
|
||||||
versionName "1.0"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@ -34,8 +33,8 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_11
|
sourceCompatibility versions.sourceCompat
|
||||||
targetCompatibility JavaVersion.VERSION_11
|
targetCompatibility versions.targetCompat
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "11"
|
||||||
|
@ -51,13 +50,13 @@ dependencies {
|
||||||
|
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
|
|
||||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
|
implementation libs.rx.rxKotlin
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
implementation libs.rx.rxAndroid
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
implementation libs.jetbrains.kotlinStdlib
|
||||||
implementation 'androidx.core:core-ktx:1.6.0'
|
implementation libs.androidx.core
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
implementation libs.androidx.appCompat
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
implementation libs.androidx.recyclerview
|
||||||
|
|
||||||
implementation 'com.google.android.material:material:1.4.0'
|
implementation libs.google.material
|
||||||
}
|
}
|
11
build.gradle
11
build.gradle
|
@ -1,9 +1,9 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
// Ref: https://kotlinlang.org/releases.html
|
|
||||||
ext.kotlin_version = '1.5.21'
|
apply from: 'dependencies.gradle'
|
||||||
ext.kotlin_coroutines_version = "1.5.0"
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
@ -11,12 +11,13 @@ buildscript {
|
||||||
url "https://plugins.gradle.org/m2/"
|
url "https://plugins.gradle.org/m2/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Release notes of Android Gradle Plugin (AGP):
|
// Release notes of Android Gradle Plugin (AGP):
|
||||||
// https://developer.android.com/studio/releases/gradle-plugin
|
// https://developer.android.com/studio/releases/gradle-plugin
|
||||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
classpath libs.gradle.gradlePlugin
|
||||||
|
classpath libs.gradle.kotlinPlugin
|
||||||
classpath 'com.google.gms:google-services:4.3.10'
|
classpath 'com.google.gms:google-services:4.3.10'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
|
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
|
||||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4'
|
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4'
|
||||||
classpath "com.likethesalad.android:string-reference:1.2.2"
|
classpath "com.likethesalad.android:string-reference:1.2.2"
|
||||||
|
|
|
@ -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"
|
||||||
|
]
|
||||||
|
]
|
|
@ -0,0 +1,2 @@
|
||||||
|
Hauptänderungen: Sprachnachrichten standardmäßig aktiviert.
|
||||||
|
Ganze Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.1.16
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -8,10 +8,10 @@ Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas ya
|
||||||
- Obrolan video dengan VoIP dan berbagi layar
|
- Obrolan video dengan VoIP dan berbagi layar
|
||||||
- Integrasi yang mudah dengan alat kolaborasi online favorit Anda, alat manajemen proyek, layanan VoIP dan aplikasi perpesanan tim lainnya
|
- Integrasi yang mudah dengan alat kolaborasi online favorit Anda, alat manajemen proyek, layanan VoIP dan aplikasi perpesanan tim lainnya
|
||||||
|
|
||||||
Element benar-benar berbeda dari aplikasi perpesanan dan kolaborasi lainnya. Ini beroperasi pada Matrix, jaringan terbuka untuk pengiriman pesan yang aman dan komunikasi terdesentralisasi. Ini memungkinkan hosting sendiri untuk memberi pengguna kepemilikan maksimum dan kontrol data dan pesan mereka.
|
Element benar-benar berbeda dari aplikasi perpesanan dan kolaborasi lainnya. Element beroperasi pada Matrix, jaringan terbuka untuk pengiriman pesan yang aman dan komunikasi terdesentralisasi. Matrix memungkinkan hosting sendiri untuk memberi pengguna kepemilikan maksimum dan kontrol data dan pesan mereka.
|
||||||
|
|
||||||
<b>Pesan privasi dan terenkripsi</b>
|
<b>Pesan privasi dan terenkripsi</b>
|
||||||
Element melindungi Anda dari iklan yang tidak diinginkan, data penambangan dan taman berdinding. Ini juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu melalui enkripsi ujung-ke-ujung dan verifikasi perangkat yang di-cross-signed.
|
Element melindungi Anda dari iklan yang tidak diinginkan, data penambangan dan taman berdinding. Element juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu melalui enkripsi ujung-ke-ujung dan verifikasi perangkat yang ditanda tangani silang.
|
||||||
|
|
||||||
Element memberi Anda kendali atas privasi Anda sambil memungkinkan Anda untuk berkomunikasi dengan aman dengan siapa pun di jaringan Matrix, atau alat kolaborasi bisnis lainnya dengan mengintegrasikan dengan aplikasi seperti Slack.
|
Element memberi Anda kendali atas privasi Anda sambil memungkinkan Anda untuk berkomunikasi dengan aman dengan siapa pun di jaringan Matrix, atau alat kolaborasi bisnis lainnya dengan mengintegrasikan dengan aplikasi seperti Slack.
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ Element menempatkan Anda dalam kendali dengan cara yang berbeda:
|
||||||
Anda dapat mengobrol dengan siapa saja di jaringan Matrix, apakah mereka menggunakan Element, aplikasi Matrix lain atau bahkan jika mereka menggunakan aplikasi perpesanan yang berbeda.
|
Anda dapat mengobrol dengan siapa saja di jaringan Matrix, apakah mereka menggunakan Element, aplikasi Matrix lain atau bahkan jika mereka menggunakan aplikasi perpesanan yang berbeda.
|
||||||
|
|
||||||
<b>Sangat aman</b>
|
<b>Sangat aman</b>
|
||||||
Enkripsi ujung-ke-ujung beneran (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan verifikasi perangkat yang di-cross-signed.
|
Enkripsi ujung-ke-ujung beneran (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan verifikasi perangkat yang ditanda tangani silang.
|
||||||
|
|
||||||
<b>Komunikasi dan integrasi lengkap</b>
|
<b>Komunikasi dan integrasi lengkap</b>
|
||||||
Perpesanan, panggilan suara dan video, berbagi file, berbagi layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal.
|
Perpesanan, panggilan suara dan video, berbagi file, berbagi layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal.
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Основные изменения в этой версии: реализация голосовых сообщений в настройках лабораторий.
|
||||||
|
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.15
|
|
@ -0,0 +1,2 @@
|
||||||
|
Основные изменения в этой версии: Исправление ошибки при отправке зашифрованного сообщения, если кто-то в комнате выходит.
|
||||||
|
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.16
|
|
@ -0,0 +1,2 @@
|
||||||
|
Основные изменения в этой версии: Голосовое сообщение включено по умолчанию.
|
||||||
|
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.16
|
|
@ -6,20 +6,20 @@
|
||||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
android.enableJetifier=true
|
|
||||||
android.useAndroidX=true
|
|
||||||
org.gradle.jvmargs=-Xmx4096m
|
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
|
||||||
# org.gradle.parallel=true
|
|
||||||
|
|
||||||
# Enable file system watch (https://docs.gradle.org/6.7/release-notes.html)
|
# Build Time Optimizations
|
||||||
|
org.gradle.jvmargs=-Xmx3g -Xms512M -XX:MaxPermSize=2048m -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC
|
||||||
|
org.gradle.configureondemand=true
|
||||||
|
org.gradle.parallel=true
|
||||||
org.gradle.vfs.watch=true
|
org.gradle.vfs.watch=true
|
||||||
|
|
||||||
|
# Android Settings
|
||||||
|
android.enableJetifier=true
|
||||||
|
android.useAndroidX=true
|
||||||
|
|
||||||
|
#Project Settings
|
||||||
|
# Change debugPrivateData to true for debugging
|
||||||
vector.debugPrivateData=false
|
vector.debugPrivateData=false
|
||||||
|
# httpLogLevel values: NONE, BASIC, HEADERS, BODY
|
||||||
vector.httpLogLevel=BASIC
|
vector.httpLogLevel=BASIC
|
||||||
|
|
||||||
# Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above
|
|
||||||
#vector.debugPrivateData=true
|
|
||||||
#vector.httpLogLevel=BODY
|
|
||||||
|
|
|
@ -20,14 +20,11 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
|
||||||
buildToolsVersion "30.0.3"
|
|
||||||
|
|
||||||
|
compileSdk versions.compileSdk
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdk versions.minSdk
|
||||||
targetSdkVersion 30
|
targetSdk versions.targetSdk
|
||||||
versionCode 1
|
|
||||||
versionName "1.0"
|
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles "consumer-rules.pro"
|
consumerProguardFiles "consumer-rules.pro"
|
||||||
|
@ -41,8 +38,8 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_11
|
sourceCompatibility versions.sourceCompat
|
||||||
targetCompatibility JavaVersion.VERSION_11
|
targetCompatibility versions.targetCompat
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
|
@ -55,10 +52,10 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
implementation libs.androidx.appCompat
|
||||||
implementation 'com.google.android.material:material:1.4.0'
|
implementation libs.google.material
|
||||||
// Pref theme
|
// Pref theme
|
||||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
implementation libs.androidx.preferenceKtx
|
||||||
// PFLockScreen attrs
|
// PFLockScreen attrs
|
||||||
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
|
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
|
||||||
// dialpad dimen
|
// dialpad dimen
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<attr name="actionDescription" format="string" />
|
<attr name="actionDescription" format="string" />
|
||||||
<attr name="leftIcon" format="reference" />
|
<attr name="leftIcon" format="reference" />
|
||||||
<attr name="rightIcon" format="reference" />
|
<attr name="rightIcon" format="reference" />
|
||||||
|
<attr name="betaAction" format="boolean" />
|
||||||
<attr name="forceStartPadding" format="boolean" />
|
<attr name="forceStartPadding" format="boolean" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,11 @@ apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdk versions.compileSdk
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdk versions.minSdk
|
||||||
targetSdkVersion 30
|
targetSdk versions.targetSdk
|
||||||
versionCode 1
|
|
||||||
versionName "1.0"
|
|
||||||
|
|
||||||
// Multidex is useful for tests
|
// Multidex is useful for tests
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
@ -24,8 +22,8 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_11
|
sourceCompatibility versions.sourceCompat
|
||||||
targetCompatibility JavaVersion.VERSION_11
|
targetCompatibility versions.targetCompat
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
|
@ -34,15 +32,16 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation project(":matrix-sdk-android")
|
implementation project(":matrix-sdk-android")
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
implementation libs.androidx.appCompat
|
||||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
|
implementation libs.rx.rxKotlin
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
implementation libs.rx.rxAndroid
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlin_coroutines_version"
|
implementation libs.jetbrains.coroutinesRx2
|
||||||
|
|
||||||
// Paging
|
// Paging
|
||||||
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
|
implementation libs.androidx.pagingRuntimeKtx
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation 'com.jakewharton.timber:timber:5.0.1'
|
implementation libs.jakewharton.timber
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_S
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
||||||
import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams
|
import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||||
|
import org.matrix.android.sdk.api.session.identity.FoundThreePid
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||||
import org.matrix.android.sdk.api.session.pushers.Pusher
|
import org.matrix.android.sdk.api.session.pushers.Pusher
|
||||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||||
|
@ -239,6 +240,10 @@ class RxSession(private val session: Session) {
|
||||||
)
|
)
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun lookupThreePid(threePid: ThreePid): Single<Optional<FoundThreePid>> = rxSingle {
|
||||||
|
session.identityService().lookUp(listOf(threePid)).firstOrNull().toOptional()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Session.rx(): RxSession {
|
fun Session.rx(): RxSession {
|
||||||
|
|
|
@ -14,14 +14,14 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
|
||||||
testOptions.unitTests.includeAndroidResources = true
|
testOptions.unitTests.includeAndroidResources = true
|
||||||
|
|
||||||
|
compileSdk versions.compileSdk
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdk versions.minSdk
|
||||||
targetSdkVersion 30
|
targetSdk versions.targetSdk
|
||||||
versionCode 1
|
|
||||||
versionName "0.0.1"
|
|
||||||
// Multidex is useful for tests
|
// Multidex is useful for tests
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
@ -31,9 +31,7 @@ android {
|
||||||
// that the app's state is completely cleared between tests.
|
// that the app's state is completely cleared between tests.
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||||
|
|
||||||
// Seems that the build tools 4.1.0 does not generate BuildConfig.VERSION_NAME anymore.
|
buildConfigField "String", "SDK_VERSION", "\"1.2.2\""
|
||||||
// Add it manually here. We may remove this trick in the future
|
|
||||||
buildConfigField "String", "VERSION_NAME", "\"0.0.1\""
|
|
||||||
|
|
||||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||||
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
||||||
|
@ -68,8 +66,8 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_11
|
sourceCompatibility versions.sourceCompat
|
||||||
targetCompatibility JavaVersion.VERSION_11
|
targetCompatibility versions.targetCompat
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
|
@ -103,92 +101,83 @@ static def gitRevisionDate() {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
def arrow_version = "0.8.2"
|
implementation libs.jetbrains.kotlinStdlibJdk7
|
||||||
def moshi_version = '1.12.0'
|
implementation libs.jetbrains.coroutinesCore
|
||||||
def lifecycle_version = '2.2.0'
|
implementation libs.jetbrains.coroutinesAndroid
|
||||||
def arch_version = '2.1.0'
|
|
||||||
def markwon_version = '3.1.0'
|
|
||||||
def daggerVersion = '2.38.1'
|
|
||||||
def work_version = '2.5.0'
|
|
||||||
def retrofit_version = '2.9.0'
|
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation libs.androidx.appCompat
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
implementation libs.androidx.core
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
|
||||||
|
|
||||||
implementation "androidx.appcompat:appcompat:1.3.1"
|
implementation libs.androidx.lifecycleExtensions
|
||||||
implementation "androidx.core:core-ktx:1.6.0"
|
implementation libs.androidx.lifecycleJava8
|
||||||
|
|
||||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
|
||||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
implementation libs.squareup.retrofit
|
||||||
implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"
|
implementation libs.squareup.retrofitMoshi
|
||||||
|
|
||||||
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.1"))
|
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.1"))
|
||||||
implementation 'com.squareup.okhttp3:okhttp'
|
implementation 'com.squareup.okhttp3:okhttp'
|
||||||
implementation 'com.squareup.okhttp3:logging-interceptor'
|
implementation 'com.squareup.okhttp3:logging-interceptor'
|
||||||
implementation 'com.squareup.okhttp3:okhttp-urlconnection'
|
implementation 'com.squareup.okhttp3:okhttp-urlconnection'
|
||||||
|
|
||||||
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
implementation libs.squareup.moshi
|
||||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
kapt libs.squareup.moshiKotlin
|
||||||
|
|
||||||
implementation "ru.noties.markwon:core:$markwon_version"
|
implementation libs.markwon.core
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.3.3'
|
implementation libs.androidx.exifinterface
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
implementation 'com.github.Zhuinden:realm-monarchy:0.7.1'
|
implementation 'com.github.Zhuinden:realm-monarchy:0.7.1'
|
||||||
kapt 'dk.ilios:realmfieldnameshelper:2.0.0'
|
kapt 'dk.ilios:realmfieldnameshelper:2.0.0'
|
||||||
|
|
||||||
// Work
|
// Work
|
||||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
implementation libs.androidx.work
|
||||||
|
|
||||||
// FP
|
// FP
|
||||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
implementation libs.arrow.core
|
||||||
implementation "io.arrow-kt:arrow-instances-core:$arrow_version"
|
implementation libs.arrow.instances
|
||||||
|
|
||||||
// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
|
// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
|
||||||
implementation 'org.matrix.gitlab.matrix-org:olm:3.2.4'
|
implementation 'org.matrix.gitlab.matrix-org:olm:3.2.4'
|
||||||
|
|
||||||
// DI
|
// DI
|
||||||
implementation "com.google.dagger:dagger:$daggerVersion"
|
implementation libs.dagger.dagger
|
||||||
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
|
kapt libs.dagger.daggerCompiler
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation 'com.jakewharton.timber:timber:5.0.1'
|
implementation libs.jakewharton.timber
|
||||||
implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
|
implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
|
||||||
|
|
||||||
// Video compression
|
// Video compression
|
||||||
implementation 'com.otaliastudios:transcoder:0.10.3'
|
implementation 'com.otaliastudios:transcoder:0.10.4'
|
||||||
|
|
||||||
// Phone number https://github.com/google/libphonenumber
|
// Phone number https://github.com/google/libphonenumber
|
||||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.31'
|
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.33'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation libs.tests.junit
|
||||||
testImplementation 'org.robolectric:robolectric:4.5.1'
|
testImplementation 'org.robolectric:robolectric:4.6.1'
|
||||||
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||||
testImplementation 'io.mockk:mockk:1.12.0'
|
testImplementation libs.mockk.mockk
|
||||||
testImplementation 'org.amshove.kluent:kluent-android:1.68'
|
testImplementation libs.tests.kluent
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
implementation libs.jetbrains.coroutinesAndroid
|
||||||
// Plant Timber tree for test
|
// Plant Timber tree for test
|
||||||
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
||||||
|
|
||||||
kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
|
kaptAndroidTest libs.dagger.daggerCompiler
|
||||||
androidTestImplementation 'androidx.test:core:1.4.0'
|
androidTestImplementation libs.androidx.testCore
|
||||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
androidTestImplementation libs.androidx.testRunner
|
||||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
androidTestImplementation libs.androidx.testRules
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation libs.androidx.junit
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation libs.androidx.espressoCore
|
||||||
androidTestImplementation 'org.amshove.kluent:kluent-android:1.68'
|
androidTestImplementation libs.tests.kluent
|
||||||
androidTestImplementation 'io.mockk:mockk-android:1.12.0'
|
androidTestImplementation libs.mockk.mockkAndroid
|
||||||
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
|
androidTestImplementation libs.androidx.coreTesting
|
||||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
androidTestImplementation libs.jetbrains.coroutinesAndroid
|
||||||
// Plant Timber tree for test
|
// Plant Timber tree for test
|
||||||
androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
androidTestImplementation libs.tests.timberJunitRule
|
||||||
|
|
||||||
androidTestUtil 'androidx.test:orchestrator:1.4.0'
|
androidTestUtil libs.androidx.orchestrator
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,7 +117,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSdkVersion(): String {
|
fun getSdkVersion(): String {
|
||||||
return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.matrix.android.sdk.common
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
|
||||||
|
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
@ -59,6 +61,7 @@ class CommonTestHelper(context: Context) {
|
||||||
fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestNetworkModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
|
fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestNetworkModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
UiThreadStatement.runOnUiThread {
|
||||||
Matrix.initialize(
|
Matrix.initialize(
|
||||||
context,
|
context,
|
||||||
MatrixConfiguration(
|
MatrixConfiguration(
|
||||||
|
@ -66,6 +69,7 @@ class CommonTestHelper(context: Context) {
|
||||||
roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider()
|
roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
matrix = Matrix.getInstance(context)
|
matrix = Matrix.getInstance(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,10 +32,19 @@ import org.junit.runners.JUnit4
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
||||||
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.Role
|
||||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
import org.matrix.android.sdk.common.SessionTestParams
|
import org.matrix.android.sdk.common.SessionTestParams
|
||||||
|
@ -386,6 +395,8 @@ class SpaceHierarchyTest : InstrumentedTest {
|
||||||
// The room should have disapear from flat children
|
// The room should have disapear from flat children
|
||||||
GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
|
GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commonTestHelper.signOutAndClose(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class TestSpaceCreationResult(
|
data class TestSpaceCreationResult(
|
||||||
|
@ -434,6 +445,57 @@ class SpaceHierarchyTest : InstrumentedTest {
|
||||||
return TestSpaceCreationResult(spaceId, roomIds)
|
return TestSpaceCreationResult(spaceId, roomIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||||
|
private fun createPrivateSpace(session: Session,
|
||||||
|
spaceName: String,
|
||||||
|
childInfo: List<Triple<String, Boolean, Boolean?>>
|
||||||
|
/** Name, auto-join, canonical*/
|
||||||
|
): TestSpaceCreationResult {
|
||||||
|
var spaceId = ""
|
||||||
|
commonTestHelper.waitWithLatch {
|
||||||
|
GlobalScope.launch {
|
||||||
|
spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false)
|
||||||
|
it.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||||
|
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||||
|
|
||||||
|
val roomIds =
|
||||||
|
childInfo.map { entry ->
|
||||||
|
var roomId = ""
|
||||||
|
commonTestHelper.waitWithLatch {
|
||||||
|
GlobalScope.launch {
|
||||||
|
val homeServerCapabilities = session
|
||||||
|
.getHomeServerCapabilities()
|
||||||
|
roomId = session.createRoom(CreateRoomParams().apply {
|
||||||
|
name = entry.first
|
||||||
|
this.featurePreset = RestrictedRoomPreset(
|
||||||
|
homeServerCapabilities,
|
||||||
|
listOf(
|
||||||
|
RoomJoinRulesAllowEntry.restrictedToRoom(spaceId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
roomId
|
||||||
|
}
|
||||||
|
|
||||||
|
roomIds.forEachIndexed { index, roomId ->
|
||||||
|
runBlocking {
|
||||||
|
syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
|
||||||
|
val canonical = childInfo[index].third
|
||||||
|
if (canonical != null) {
|
||||||
|
session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TestSpaceCreationResult(spaceId, roomIds)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testRootSpaces() {
|
fun testRootSpaces() {
|
||||||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||||
|
@ -473,5 +535,111 @@ class SpaceHierarchyTest : InstrumentedTest {
|
||||||
val rootSpaces = session.spaceService().getRootSpaceSummaries()
|
val rootSpaces = session.spaceService().getRootSpaceSummaries()
|
||||||
|
|
||||||
assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size)
|
assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size)
|
||||||
|
|
||||||
|
commonTestHelper.signOutAndClose(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testParentRelation() {
|
||||||
|
val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true))
|
||||||
|
val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true))
|
||||||
|
|
||||||
|
val spaceAInfo = createPrivateSpace(aliceSession, "Private Space A", listOf(
|
||||||
|
Triple("General", true /*suggested*/, true/*canonical*/),
|
||||||
|
Triple("Random", true, true)
|
||||||
|
))
|
||||||
|
|
||||||
|
commonTestHelper.runBlockingTest {
|
||||||
|
aliceSession.getRoom(spaceAInfo.spaceId)!!.invite(bobSession.myUserId, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
commonTestHelper.runBlockingTest {
|
||||||
|
bobSession.joinRoom(spaceAInfo.spaceId, null, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
var bobRoomId = ""
|
||||||
|
commonTestHelper.waitWithLatch {
|
||||||
|
GlobalScope.launch {
|
||||||
|
bobRoomId = bobSession.createRoom(CreateRoomParams().apply { name = "A Bob Room" })
|
||||||
|
bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId)
|
||||||
|
it.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commonTestHelper.runBlockingTest {
|
||||||
|
aliceSession.joinRoom(bobRoomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
commonTestHelper.waitWithLatch { latch ->
|
||||||
|
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
|
aliceSession.getRoomSummary(bobRoomId)?.membership?.isActive() == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commonTestHelper.waitWithLatch {
|
||||||
|
GlobalScope.launch {
|
||||||
|
bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
|
||||||
|
it.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commonTestHelper.waitWithLatch { latch ->
|
||||||
|
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
|
val stateEvent = aliceSession.getRoom(bobRoomId)!!.getStateEvent(EventType.STATE_SPACE_PARENT, QueryStringValue.Equals(spaceAInfo.spaceId))
|
||||||
|
stateEvent != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should be an invalid space parent relation, because no opposite child and bob is not admin of the space
|
||||||
|
commonTestHelper.runBlockingTest {
|
||||||
|
// we can see the state event
|
||||||
|
// but it is not valid and room is not in hierarchy
|
||||||
|
assertTrue("Bob Room should not be listed as a child of the space", aliceSession.getRoomSummary(bobRoomId)?.flattenParentIds?.isEmpty() == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's now try to make alice admin of the room
|
||||||
|
|
||||||
|
commonTestHelper.waitWithLatch {
|
||||||
|
GlobalScope.launch {
|
||||||
|
val room = bobSession.getRoom(bobRoomId)!!
|
||||||
|
val currentPLContent = room
|
||||||
|
.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
|
||||||
|
?.let { it.content.toModel<PowerLevelsContent>() }
|
||||||
|
|
||||||
|
val newPowerLevelsContent = currentPLContent
|
||||||
|
?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
|
||||||
|
?.toContent()
|
||||||
|
|
||||||
|
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent!!)
|
||||||
|
it.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commonTestHelper.waitWithLatch { latch ->
|
||||||
|
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
|
val powerLevelsHelper = aliceSession.getRoom(bobRoomId)!!
|
||||||
|
.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
|
||||||
|
?.content
|
||||||
|
?.toModel<PowerLevelsContent>()
|
||||||
|
?.let { PowerLevelsHelper(it) }
|
||||||
|
powerLevelsHelper!!.isUserAllowedToSend(aliceSession.myUserId, true, EventType.STATE_SPACE_PARENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commonTestHelper.waitWithLatch {
|
||||||
|
GlobalScope.launch {
|
||||||
|
aliceSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
|
||||||
|
it.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commonTestHelper.waitWithLatch { latch ->
|
||||||
|
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
|
bobSession.getRoomSummary(bobRoomId)?.flattenParentIds?.contains(spaceAInfo.spaceId) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commonTestHelper.signOutAndClose(aliceSession)
|
||||||
|
commonTestHelper.signOutAndClose(bobSession)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSdkVersion(): String {
|
fun getSdkVersion(): String {
|
||||||
return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ package org.matrix.android.sdk.api.logger
|
||||||
*/
|
*/
|
||||||
open class LoggerTag(_value: String, parentTag: LoggerTag? = null) {
|
open class LoggerTag(_value: String, parentTag: LoggerTag? = null) {
|
||||||
|
|
||||||
|
object SYNC : LoggerTag("SYNC")
|
||||||
object VOIP : LoggerTag("VOIP")
|
object VOIP : LoggerTag("VOIP")
|
||||||
|
|
||||||
val value: String = if (parentTag == null) {
|
val value: String = if (parentTag == null) {
|
||||||
|
|
|
@ -36,7 +36,7 @@ import org.matrix.android.sdk.api.session.file.FileService
|
||||||
import org.matrix.android.sdk.api.session.group.GroupService
|
import org.matrix.android.sdk.api.session.group.GroupService
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
import org.matrix.android.sdk.api.session.identity.IdentityService
|
import org.matrix.android.sdk.api.session.identity.IdentityService
|
||||||
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
|
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
|
||||||
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
|
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
|
||||||
import org.matrix.android.sdk.api.session.media.MediaService
|
import org.matrix.android.sdk.api.session.media.MediaService
|
||||||
import org.matrix.android.sdk.api.session.openid.OpenIdService
|
import org.matrix.android.sdk.api.session.openid.OpenIdService
|
||||||
|
@ -75,7 +75,7 @@ interface Session :
|
||||||
ProfileService,
|
ProfileService,
|
||||||
PushRuleService,
|
PushRuleService,
|
||||||
PushersService,
|
PushersService,
|
||||||
InitialSyncProgressService,
|
SyncStatusService,
|
||||||
HomeServerCapabilitiesService,
|
HomeServerCapabilitiesService,
|
||||||
SecureStorageService,
|
SecureStorageService,
|
||||||
AccountService {
|
AccountService {
|
||||||
|
|
|
@ -239,7 +239,7 @@ data class Event(
|
||||||
|
|
||||||
fun Event.isTextMessage(): Boolean {
|
fun Event.isTextMessage(): Boolean {
|
||||||
return getClearType() == EventType.MESSAGE
|
return getClearType() == EventType.MESSAGE
|
||||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
&& when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
|
||||||
MessageType.MSGTYPE_TEXT,
|
MessageType.MSGTYPE_TEXT,
|
||||||
MessageType.MSGTYPE_EMOTE,
|
MessageType.MSGTYPE_EMOTE,
|
||||||
MessageType.MSGTYPE_NOTICE -> true
|
MessageType.MSGTYPE_NOTICE -> true
|
||||||
|
@ -249,7 +249,7 @@ fun Event.isTextMessage(): Boolean {
|
||||||
|
|
||||||
fun Event.isImageMessage(): Boolean {
|
fun Event.isImageMessage(): Boolean {
|
||||||
return getClearType() == EventType.MESSAGE
|
return getClearType() == EventType.MESSAGE
|
||||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
&& when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
|
||||||
MessageType.MSGTYPE_IMAGE -> true
|
MessageType.MSGTYPE_IMAGE -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
@ -257,7 +257,7 @@ fun Event.isImageMessage(): Boolean {
|
||||||
|
|
||||||
fun Event.isVideoMessage(): Boolean {
|
fun Event.isVideoMessage(): Boolean {
|
||||||
return getClearType() == EventType.MESSAGE
|
return getClearType() == EventType.MESSAGE
|
||||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
&& when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
|
||||||
MessageType.MSGTYPE_VIDEO -> true
|
MessageType.MSGTYPE_VIDEO -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
@ -265,7 +265,7 @@ fun Event.isVideoMessage(): Boolean {
|
||||||
|
|
||||||
fun Event.isAudioMessage(): Boolean {
|
fun Event.isAudioMessage(): Boolean {
|
||||||
return getClearType() == EventType.MESSAGE
|
return getClearType() == EventType.MESSAGE
|
||||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
&& when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
|
||||||
MessageType.MSGTYPE_AUDIO -> true
|
MessageType.MSGTYPE_AUDIO -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
@ -273,7 +273,7 @@ fun Event.isAudioMessage(): Boolean {
|
||||||
|
|
||||||
fun Event.isFileMessage(): Boolean {
|
fun Event.isFileMessage(): Boolean {
|
||||||
return getClearType() == EventType.MESSAGE
|
return getClearType() == EventType.MESSAGE
|
||||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
&& when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
|
||||||
MessageType.MSGTYPE_FILE -> true
|
MessageType.MSGTYPE_FILE -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
@ -281,7 +281,7 @@ fun Event.isFileMessage(): Boolean {
|
||||||
|
|
||||||
fun Event.isAttachmentMessage(): Boolean {
|
fun Event.isAttachmentMessage(): Boolean {
|
||||||
return getClearType() == EventType.MESSAGE
|
return getClearType() == EventType.MESSAGE
|
||||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
&& when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
|
||||||
MessageType.MSGTYPE_IMAGE,
|
MessageType.MSGTYPE_IMAGE,
|
||||||
MessageType.MSGTYPE_AUDIO,
|
MessageType.MSGTYPE_AUDIO,
|
||||||
MessageType.MSGTYPE_VIDEO,
|
MessageType.MSGTYPE_VIDEO,
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,14 @@ data class Pusher(
|
||||||
val data: PusherData,
|
val data: PusherData,
|
||||||
|
|
||||||
val state: PusherState
|
val state: PusherState
|
||||||
)
|
) {
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val KIND_EMAIL = "email"
|
||||||
|
const val KIND_HTTP = "http"
|
||||||
|
const val APP_ID_EMAIL = "m.email"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum class PusherState {
|
enum class PusherState {
|
||||||
UNREGISTERED,
|
UNREGISTERED,
|
||||||
|
|
|
@ -27,14 +27,12 @@ interface PushersService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new HTTP pusher.
|
* Add a new HTTP pusher.
|
||||||
* Note that only `http` kind is supported by the SDK for now.
|
|
||||||
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
|
||||||
*
|
*
|
||||||
* @param pushkey This is a unique identifier for this pusher. The value you should use for
|
* @param pushkey This is a unique identifier for this pusher. The value you should use for
|
||||||
* this is the routing or destination address information for the notification,
|
* this is the routing or destination address information for the notification,
|
||||||
* for example, the APNS token for APNS or the Registration ID for GCM. If your
|
* for example, the APNS token for APNS or the Registration ID for GCM. If your
|
||||||
* notification client has no such concept, use any unique identifier. Max length, 512 chars.
|
* notification client has no such concept, use any unique identifier. Max length, 512 chars.
|
||||||
* If the kind is "email", this is the email address to send notifications to.
|
|
||||||
* @param appId the application id
|
* @param appId the application id
|
||||||
* This is a reverse-DNS style identifier for the application. It is recommended
|
* This is a reverse-DNS style identifier for the application. It is recommended
|
||||||
* that this end with the platform, such that different platform versions get
|
* that this end with the platform, such that different platform versions get
|
||||||
|
@ -64,6 +62,30 @@ interface PushersService {
|
||||||
append: Boolean,
|
append: Boolean,
|
||||||
withEventIdOnly: Boolean): UUID
|
withEventIdOnly: Boolean): UUID
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new Email pusher.
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
|
||||||
|
*
|
||||||
|
* @param email The email address to send notifications to.
|
||||||
|
* @param lang The preferred language for receiving notifications (e.g. "en" or "en-US").
|
||||||
|
* @param emailBranding The branding placeholder to include in the email communications.
|
||||||
|
* @param appDisplayName A human readable string that will allow the user to identify what application owns this pusher.
|
||||||
|
* @param deviceDisplayName A human readable string that will allow the user to identify what device owns this pusher.
|
||||||
|
* @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition
|
||||||
|
* to any others with different user IDs. Otherwise, the homeserver must remove any other pushers
|
||||||
|
* with the same App ID and pushkey for different users. Typically We always want to append for
|
||||||
|
* email pushers since we don't want to stop other accounts notifying to the same email address.
|
||||||
|
* @return A work request uuid. Can be used to listen to the status
|
||||||
|
* (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>))
|
||||||
|
* @throws [InvalidParameterException] if a parameter is not correct
|
||||||
|
*/
|
||||||
|
fun addEmailPusher(email: String,
|
||||||
|
lang: String,
|
||||||
|
emailBranding: String,
|
||||||
|
appDisplayName: String,
|
||||||
|
deviceDisplayName: String,
|
||||||
|
append: Boolean = true): UUID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directly ask the push gateway to send a push to this device
|
* Directly ask the push gateway to send a push to this device
|
||||||
* If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
|
* If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
|
||||||
|
@ -80,10 +102,23 @@ interface PushersService {
|
||||||
eventId: String)
|
eventId: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the http pusher
|
* Remove a registered pusher
|
||||||
|
* @param pusher the pusher to remove, can be http or email
|
||||||
|
*/
|
||||||
|
suspend fun removePusher(pusher: Pusher)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a Http pusher by its pushkey and appId
|
||||||
|
* @see addHttpPusher
|
||||||
*/
|
*/
|
||||||
suspend fun removeHttpPusher(pushkey: String, appId: String)
|
suspend fun removeHttpPusher(pushkey: String, appId: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an Email pusher
|
||||||
|
* @see addEmailPusher
|
||||||
|
*/
|
||||||
|
suspend fun removeEmailPusher(email: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current pushers, as a LiveData
|
* Get the current pushers, as a LiveData
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ data class MessageAudioContent(
|
||||||
/**
|
/**
|
||||||
* Required. Must be 'm.audio'.
|
* Required. Must be 'm.audio'.
|
||||||
*/
|
*/
|
||||||
@Json(name = "msgtype") override val msgType: String,
|
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. A description of the audio e.g. 'Bee Gees - Stayin' Alive', or some kind of content description for accessibility e.g. 'audio attachment'.
|
* Required. A description of the audio e.g. 'Bee Gees - Stayin' Alive', or some kind of content description for accessibility e.g. 'audio attachment'.
|
||||||
|
|
|
@ -20,6 +20,11 @@ import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
interface MessageContent {
|
interface MessageContent {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MSG_TYPE_JSON_KEY = "msgtype"
|
||||||
|
}
|
||||||
|
|
||||||
val msgType: String
|
val msgType: String
|
||||||
val body: String
|
val body: String
|
||||||
val relatesTo: RelationDefaultContent?
|
val relatesTo: RelationDefaultContent?
|
||||||
|
|
|
@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessageDefaultContent(
|
data class MessageDefaultContent(
|
||||||
@Json(name = "msgtype") override val msgType: String,
|
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
|
||||||
@Json(name = "body") override val body: String,
|
@Json(name = "body") override val body: String,
|
||||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||||
|
|
|
@ -26,7 +26,7 @@ data class MessageEmoteContent(
|
||||||
/**
|
/**
|
||||||
* Required. Must be 'm.emote'.
|
* Required. Must be 'm.emote'.
|
||||||
*/
|
*/
|
||||||
@Json(name = "msgtype") override val msgType: String,
|
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. The emote action to perform.
|
* Required. The emote action to perform.
|
||||||
|
|
|
@ -28,7 +28,7 @@ data class MessageFileContent(
|
||||||
/**
|
/**
|
||||||
* Required. Must be 'm.file'.
|
* Required. Must be 'm.file'.
|
||||||
*/
|
*/
|
||||||
@Json(name = "msgtype") override val msgType: String,
|
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. A human-readable description of the file. This is recommended to be the filename of the original upload.
|
* Required. A human-readable description of the file. This is recommended to be the filename of the original upload.
|
||||||
|
|
|
@ -27,7 +27,7 @@ data class MessageImageContent(
|
||||||
/**
|
/**
|
||||||
* Required. Must be 'm.image'.
|
* Required. Must be 'm.image'.
|
||||||
*/
|
*/
|
||||||
@Json(name = "msgtype") override val msgType: String,
|
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. A textual representation of the image. This could be the alt text of the image, the filename of the image,
|
* Required. A textual representation of the image. This could be the alt text of the image, the filename of the image,
|
||||||
|
|
|
@ -26,7 +26,7 @@ data class MessageLocationContent(
|
||||||
/**
|
/**
|
||||||
* Required. Must be 'm.location'.
|
* Required. Must be 'm.location'.
|
||||||
*/
|
*/
|
||||||
@Json(name = "msgtype") override val msgType: String,
|
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. A description of the location e.g. 'Big Ben, London, UK', or some kind
|
* Required. A description of the location e.g. 'Big Ben, London, UK', or some kind
|
||||||
|
|
|
@ -26,7 +26,7 @@ data class MessageNoticeContent(
|
||||||
/**
|
/**
|
||||||
* Required. Must be 'm.notice'.
|
* Required. Must be 'm.notice'.
|
||||||
*/
|
*/
|
||||||
@Json(name = "msgtype") override val msgType: String,
|
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. The notice text to send.
|
* Required. The notice text to send.
|
||||||
|
|
|
@ -30,7 +30,7 @@ const val OPTION_TYPE_BUTTONS = "org.matrix.buttons"
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessageOptionsContent(
|
data class MessageOptionsContent(
|
||||||
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_OPTIONS,
|
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_OPTIONS,
|
||||||
@Json(name = "type") val optionType: String? = null,
|
@Json(name = "type") val optionType: String? = null,
|
||||||
@Json(name = "body") override val body: String,
|
@Json(name = "body") override val body: String,
|
||||||
@Json(name = "label") val label: String?,
|
@Json(name = "label") val label: String?,
|
||||||
|
|
|
@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessagePollResponseContent(
|
data class MessagePollResponseContent(
|
||||||
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_RESPONSE,
|
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_RESPONSE,
|
||||||
@Json(name = "body") override val body: String,
|
@Json(name = "body") override val body: String,
|
||||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||||
|
|
|
@ -26,7 +26,7 @@ data class MessageTextContent(
|
||||||
/**
|
/**
|
||||||
* Required. Must be 'm.text'.
|
* Required. Must be 'm.text'.
|
||||||
*/
|
*/
|
||||||
@Json(name = "msgtype") override val msgType: String,
|
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. The body of the message.
|
* Required. The body of the message.
|
||||||
|
|
|
@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReque
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessageVerificationRequestContent(
|
data class MessageVerificationRequestContent(
|
||||||
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
|
@Json(name = MessageContent.MSG_TYPE_JSON_KEY)override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
|
||||||
@Json(name = "body") override val body: String,
|
@Json(name = "body") override val body: String,
|
||||||
@Json(name = "from_device") override val fromDevice: String?,
|
@Json(name = "from_device") override val fromDevice: String?,
|
||||||
@Json(name = "methods") override val methods: List<String>,
|
@Json(name = "methods") override val methods: List<String>,
|
||||||
|
|
|
@ -27,7 +27,7 @@ data class MessageVideoContent(
|
||||||
/**
|
/**
|
||||||
* Required. Must be 'm.video'.
|
* Required. Must be 'm.video'.
|
||||||
*/
|
*/
|
||||||
@Json(name = "msgtype") override val msgType: String,
|
@Json(name = MessageContent.MSG_TYPE_JSON_KEY)override val msgType: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'.
|
* Required. A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'.
|
||||||
|
|
|
@ -94,5 +94,7 @@ interface SpaceService {
|
||||||
*/
|
*/
|
||||||
suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List<String>)
|
suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List<String>)
|
||||||
|
|
||||||
|
suspend fun removeSpaceParent(childRoomId: String, parentSpaceId: String)
|
||||||
|
|
||||||
fun getRootSpaceSummaries(): List<RoomSummary>
|
fun getRootSpaceSummaries(): List<RoomSummary>
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,18 +71,24 @@ internal class InboundGroupSessionStore @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun storeInBoundGroupSession(wrapper: OlmInboundGroupSessionWrapper2) {
|
fun storeInBoundGroupSession(wrapper: OlmInboundGroupSessionWrapper2, sessionId: String, senderKey: String) {
|
||||||
Timber.v("## Inbound: getInboundGroupSession mark as dirty ${wrapper.roomId}-${wrapper.senderKey}")
|
Timber.v("## Inbound: getInboundGroupSession mark as dirty ${wrapper.roomId}-${wrapper.senderKey}")
|
||||||
// We want to batch this a bit for performances
|
// We want to batch this a bit for performances
|
||||||
dirtySession.add(wrapper)
|
dirtySession.add(wrapper)
|
||||||
|
|
||||||
|
if (sessionCache[CacheKey(sessionId, senderKey)] == null) {
|
||||||
|
// first time seen, put it in memory cache while waiting for batch insert
|
||||||
|
// If it's already known, no need to update cache it's already there
|
||||||
|
sessionCache.put(CacheKey(sessionId, senderKey), wrapper)
|
||||||
|
}
|
||||||
|
|
||||||
timerTask?.cancel()
|
timerTask?.cancel()
|
||||||
timerTask = object : TimerTask() {
|
timerTask = object : TimerTask() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
batchSave()
|
batchSave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timer.schedule(timerTask!!, 2_000)
|
timer.schedule(timerTask!!, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
|
|
|
@ -577,7 +577,8 @@ internal class MXOlmDevice @Inject constructor(
|
||||||
session.keysClaimed = keysClaimed
|
session.keysClaimed = keysClaimed
|
||||||
session.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain
|
session.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain
|
||||||
|
|
||||||
store.storeInboundGroupSessions(listOf(session))
|
inboundGroupSessionStore.storeInBoundGroupSession(session, sessionId, senderKey)
|
||||||
|
// store.storeInboundGroupSessions(listOf(session))
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -703,7 +704,7 @@ internal class MXOlmDevice @Inject constructor(
|
||||||
timelineSet.add(messageIndexKey)
|
timelineSet.add(messageIndexKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
inboundGroupSessionStore.storeInBoundGroupSession(session)
|
inboundGroupSessionStore.storeInBoundGroupSession(session, sessionId, senderKey)
|
||||||
val payload = try {
|
val payload = try {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||||
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
|
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
|
||||||
|
|
|
@ -36,7 +36,7 @@ internal class UserAgentHolder @Inject constructor(private val context: Context,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an user agent with the application version.
|
* Create an user agent with the application version.
|
||||||
* Ex: Element/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSDK_X 1.0)
|
* Ex: Element/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSdk2 1.0)
|
||||||
*
|
*
|
||||||
* @param flavorDescription the flavor description
|
* @param flavorDescription the flavor description
|
||||||
*/
|
*/
|
||||||
|
@ -74,13 +74,13 @@ internal class UserAgentHolder @Inject constructor(private val context: Context,
|
||||||
// if there is no user agent or cannot parse it
|
// if there is no user agent or cannot parse it
|
||||||
if (null == systemUserAgent || systemUserAgent.lastIndexOf(")") == -1 || !systemUserAgent.contains("(")) {
|
if (null == systemUserAgent || systemUserAgent.lastIndexOf(")") == -1 || !systemUserAgent.contains("(")) {
|
||||||
userAgent = (appName + "/" + appVersion + " ( Flavour " + flavorDescription
|
userAgent = (appName + "/" + appVersion + " ( Flavour " + flavorDescription
|
||||||
+ "; MatrixAndroidSDK_X " + BuildConfig.VERSION_NAME + ")")
|
+ "; MatrixAndroidSdk2 " + BuildConfig.SDK_VERSION + ")")
|
||||||
} else {
|
} else {
|
||||||
// update
|
// update
|
||||||
userAgent = appName + "/" + appVersion + " " +
|
userAgent = appName + "/" + appVersion + " " +
|
||||||
systemUserAgent.substring(systemUserAgent.indexOf("("), systemUserAgent.lastIndexOf(")") - 1) +
|
systemUserAgent.substring(systemUserAgent.indexOf("("), systemUserAgent.lastIndexOf(")") - 1) +
|
||||||
"; Flavour " + flavorDescription +
|
"; Flavour " + flavorDescription +
|
||||||
"; MatrixAndroidSDK_X " + BuildConfig.VERSION_NAME + ")"
|
"; MatrixAndroidSdk2 " + BuildConfig.SDK_VERSION + ")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
|
||||||
import org.matrix.android.sdk.api.session.file.FileService
|
import org.matrix.android.sdk.api.session.file.FileService
|
||||||
import org.matrix.android.sdk.api.session.group.GroupService
|
import org.matrix.android.sdk.api.session.group.GroupService
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
|
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
|
||||||
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
|
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
|
||||||
import org.matrix.android.sdk.api.session.media.MediaService
|
import org.matrix.android.sdk.api.session.media.MediaService
|
||||||
import org.matrix.android.sdk.api.session.openid.OpenIdService
|
import org.matrix.android.sdk.api.session.openid.OpenIdService
|
||||||
|
@ -115,7 +115,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
private val contentUploadProgressTracker: ContentUploadStateTracker,
|
private val contentUploadProgressTracker: ContentUploadStateTracker,
|
||||||
private val typingUsersTracker: TypingUsersTracker,
|
private val typingUsersTracker: TypingUsersTracker,
|
||||||
private val contentDownloadStateTracker: ContentDownloadStateTracker,
|
private val contentDownloadStateTracker: ContentDownloadStateTracker,
|
||||||
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
|
private val syncStatusService: Lazy<SyncStatusService>,
|
||||||
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
|
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
|
||||||
private val accountDataService: Lazy<SessionAccountDataService>,
|
private val accountDataService: Lazy<SessionAccountDataService>,
|
||||||
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
||||||
|
@ -141,7 +141,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
PushersService by pushersService.get(),
|
PushersService by pushersService.get(),
|
||||||
EventService by eventService.get(),
|
EventService by eventService.get(),
|
||||||
TermsService by termsService.get(),
|
TermsService by termsService.get(),
|
||||||
InitialSyncProgressService by initialSyncProgressService.get(),
|
SyncStatusService by syncStatusService.get(),
|
||||||
SecureStorageService by secureStorageService.get(),
|
SecureStorageService by secureStorageService.get(),
|
||||||
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
|
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
|
||||||
ProfileService by profileService.get(),
|
ProfileService by profileService.get(),
|
||||||
|
|
|
@ -43,7 +43,7 @@ import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationMan
|
||||||
import org.matrix.android.sdk.internal.session.media.MediaModule
|
import org.matrix.android.sdk.internal.session.media.MediaModule
|
||||||
import org.matrix.android.sdk.internal.session.openid.OpenIdModule
|
import org.matrix.android.sdk.internal.session.openid.OpenIdModule
|
||||||
import org.matrix.android.sdk.internal.session.profile.ProfileModule
|
import org.matrix.android.sdk.internal.session.profile.ProfileModule
|
||||||
import org.matrix.android.sdk.internal.session.pushers.AddHttpPusherWorker
|
import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
|
||||||
import org.matrix.android.sdk.internal.session.pushers.PushersModule
|
import org.matrix.android.sdk.internal.session.pushers.PushersModule
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomModule
|
import org.matrix.android.sdk.internal.session.room.RoomModule
|
||||||
import org.matrix.android.sdk.internal.session.room.relation.SendRelationWorker
|
import org.matrix.android.sdk.internal.session.room.relation.SendRelationWorker
|
||||||
|
@ -127,7 +127,7 @@ internal interface SessionComponent {
|
||||||
|
|
||||||
fun inject(worker: SyncWorker)
|
fun inject(worker: SyncWorker)
|
||||||
|
|
||||||
fun inject(worker: AddHttpPusherWorker)
|
fun inject(worker: AddPusherWorker)
|
||||||
|
|
||||||
fun inject(worker: SendVerificationMessageWorker)
|
fun inject(worker: SendVerificationMessageWorker)
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||||
import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
|
import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
|
||||||
import org.matrix.android.sdk.api.session.events.EventService
|
import org.matrix.android.sdk.api.session.events.EventService
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
|
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
|
||||||
import org.matrix.android.sdk.api.session.openid.OpenIdService
|
import org.matrix.android.sdk.api.session.openid.OpenIdService
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
||||||
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
|
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
|
||||||
|
@ -81,7 +81,7 @@ import org.matrix.android.sdk.internal.session.download.DownloadProgressIntercep
|
||||||
import org.matrix.android.sdk.internal.session.events.DefaultEventService
|
import org.matrix.android.sdk.internal.session.events.DefaultEventService
|
||||||
import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService
|
import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService
|
||||||
import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
|
import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
|
||||||
import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService
|
import org.matrix.android.sdk.internal.session.initsync.DefaultSyncStatusService
|
||||||
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
|
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
|
||||||
import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService
|
import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService
|
||||||
import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
|
import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
|
||||||
|
@ -355,7 +355,7 @@ internal abstract class SessionModule {
|
||||||
abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver
|
abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService
|
abstract fun bindSyncStatusService(service: DefaultSyncStatusService): SyncStatusService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService
|
abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService
|
||||||
|
|
|
@ -20,10 +20,16 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.LifecycleRegistry
|
import androidx.lifecycle.LifecycleRegistry
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.failure.MatrixError
|
import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||||
|
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
import org.matrix.android.sdk.api.session.identity.FoundThreePid
|
import org.matrix.android.sdk.api.session.identity.FoundThreePid
|
||||||
|
@ -36,23 +42,17 @@ import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
|
||||||
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
|
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
|
||||||
import org.matrix.android.sdk.internal.extensions.observeNotNull
|
import org.matrix.android.sdk.internal.extensions.observeNotNull
|
||||||
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||||
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
|
import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
|
||||||
|
import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
|
||||||
import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask
|
import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask
|
||||||
import org.matrix.android.sdk.internal.session.profile.BindThreePidsTask
|
import org.matrix.android.sdk.internal.session.profile.BindThreePidsTask
|
||||||
import org.matrix.android.sdk.internal.session.profile.UnbindThreePidsTask
|
import org.matrix.android.sdk.internal.session.profile.UnbindThreePidsTask
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentityServerContent
|
import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentityServerContent
|
||||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
|
||||||
import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
|
|
||||||
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
|
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||||
|
import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
|
||||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.internal.util.ensureProtocol
|
import org.matrix.android.sdk.internal.util.ensureProtocol
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
|
||||||
import org.matrix.android.sdk.api.session.Session
|
|
||||||
import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
@ -202,6 +202,8 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
|
|
||||||
identityStore.setUrl(urlCandidate)
|
identityStore.setUrl(urlCandidate)
|
||||||
identityStore.setToken(token)
|
identityStore.setToken(token)
|
||||||
|
// could we remember if it was previously given?
|
||||||
|
identityStore.setUserConsent(false)
|
||||||
updateIdentityAPI(urlCandidate)
|
updateIdentityAPI(urlCandidate)
|
||||||
|
|
||||||
updateAccountData(urlCandidate)
|
updateAccountData(urlCandidate)
|
||||||
|
@ -230,6 +232,8 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun lookUp(threePids: List<ThreePid>): List<FoundThreePid> {
|
override suspend fun lookUp(threePids: List<ThreePid>): List<FoundThreePid> {
|
||||||
|
if (getCurrentIdentityServerUrl() == null) throw IdentityServiceError.NoIdentityServerConfigured
|
||||||
|
|
||||||
if (!getUserConsent()) {
|
if (!getUserConsent()) {
|
||||||
throw IdentityServiceError.UserConsentNotProvided
|
throw IdentityServiceError.UserConsentNotProvided
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,23 +18,28 @@ package org.matrix.android.sdk.internal.session.initsync
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import org.matrix.android.sdk.api.session.initsync.InitSyncStep
|
import org.matrix.android.sdk.api.session.initsync.InitSyncStep
|
||||||
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
|
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class DefaultInitialSyncProgressService @Inject constructor()
|
internal class DefaultSyncStatusService @Inject constructor()
|
||||||
: InitialSyncProgressService,
|
: SyncStatusService,
|
||||||
ProgressReporter {
|
ProgressReporter {
|
||||||
|
|
||||||
private val status = MutableLiveData<InitialSyncProgressService.Status>()
|
private val status = MutableLiveData<SyncStatusService.Status>()
|
||||||
|
|
||||||
private var rootTask: TaskInfo? = null
|
private var rootTask: TaskInfo? = null
|
||||||
|
|
||||||
override fun getInitialSyncProgressStatus(): LiveData<InitialSyncProgressService.Status> {
|
override fun getSyncStatusLive(): LiveData<SyncStatusService.Status> {
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only to be used for incremental sync
|
||||||
|
fun setStatus(newStatus: SyncStatusService.Status.IncrementalSyncStatus) {
|
||||||
|
status.postValue(newStatus)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a rootTask
|
* Create a rootTask
|
||||||
*/
|
*/
|
||||||
|
@ -67,7 +72,7 @@ internal class DefaultInitialSyncProgressService @Inject constructor()
|
||||||
// Update the progress of the leaf and all its parents
|
// Update the progress of the leaf and all its parents
|
||||||
leaf.setProgress(progress)
|
leaf.setProgress(progress)
|
||||||
// Then update the live data using leaf wording and root progress
|
// Then update the live data using leaf wording and root progress
|
||||||
status.postValue(InitialSyncProgressService.Status.Progressing(leaf.initSyncStep, root.currentProgress.toInt()))
|
status.postValue(SyncStatusService.Status.Progressing(leaf.initSyncStep, root.currentProgress.toInt()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,13 +87,13 @@ internal class DefaultInitialSyncProgressService @Inject constructor()
|
||||||
// And close it
|
// And close it
|
||||||
endedTask.parent.child = null
|
endedTask.parent.child = null
|
||||||
} else {
|
} else {
|
||||||
status.postValue(InitialSyncProgressService.Status.Idle)
|
status.postValue(SyncStatusService.Status.Idle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun endAll() {
|
fun endAll() {
|
||||||
rootTask = null
|
rootTask = null
|
||||||
status.postValue(InitialSyncProgressService.Status.Idle)
|
status.postValue(SyncStatusService.Status.Idle)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -33,8 +33,8 @@ import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
|
internal class AddPusherWorker(context: Context, params: WorkerParameters)
|
||||||
: SessionSafeCoroutineWorker<AddHttpPusherWorker.Params>(context, params, Params::class.java) {
|
: SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
|
@ -66,27 +66,45 @@ internal class DefaultPushersService @Inject constructor(
|
||||||
deviceDisplayName: String,
|
deviceDisplayName: String,
|
||||||
url: String,
|
url: String,
|
||||||
append: Boolean,
|
append: Boolean,
|
||||||
withEventIdOnly: Boolean)
|
withEventIdOnly: Boolean
|
||||||
: UUID {
|
) = addPusher(
|
||||||
// Do some parameter checks. It's ok to throw Exception, to inform developer of the problem
|
JsonPusher(
|
||||||
if (pushkey.length > 512) throw InvalidParameterException("pushkey should not exceed 512 chars")
|
|
||||||
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'")
|
|
||||||
|
|
||||||
val pusher = JsonPusher(
|
|
||||||
pushKey = pushkey,
|
pushKey = pushkey,
|
||||||
kind = "http",
|
kind = Pusher.KIND_HTTP,
|
||||||
appId = appId,
|
appId = appId,
|
||||||
appDisplayName = appDisplayName,
|
|
||||||
deviceDisplayName = deviceDisplayName,
|
|
||||||
profileTag = profileTag,
|
profileTag = profileTag,
|
||||||
lang = lang,
|
lang = lang,
|
||||||
|
appDisplayName = appDisplayName,
|
||||||
|
deviceDisplayName = deviceDisplayName,
|
||||||
data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }),
|
data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }),
|
||||||
append = append)
|
append = append
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val params = AddHttpPusherWorker.Params(sessionId, pusher)
|
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 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)
|
.setConstraints(WorkManagerProvider.workConstraints)
|
||||||
.setInputData(WorkerParamsFactory.toData(params))
|
.setInputData(WorkerParamsFactory.toData(params))
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
|
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
|
||||||
|
@ -95,8 +113,27 @@ internal class DefaultPushersService @Inject constructor(
|
||||||
return request.id
|
return request.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun JsonPusher.validateParameters() {
|
||||||
|
// Do some parameter checks. It's ok to throw Exception, to inform developer of the problem
|
||||||
|
if (pushKey.length > 512) throw InvalidParameterException("pushkey should not exceed 512 chars")
|
||||||
|
if (appId.length > 64) throw InvalidParameterException("appId should not exceed 64 chars")
|
||||||
|
data?.url?.let { url -> if ("/_matrix/push/v1/notify" !in url) throw InvalidParameterException("url should contain '/_matrix/push/v1/notify'") }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removePusher(pusher: Pusher) {
|
||||||
|
removePusher(pusher.pushKey, pusher.appId)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun removeHttpPusher(pushkey: String, appId: String) {
|
override suspend fun removeHttpPusher(pushkey: String, appId: String) {
|
||||||
val params = RemovePusherTask.Params(pushkey, appId)
|
removePusher(pushkey, appId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeEmailPusher(email: String) {
|
||||||
|
removePusher(pushKey = email, Pusher.APP_ID_EMAIL)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun removePusher(pushKey: String, pushAppId: String) {
|
||||||
|
val params = RemovePusherTask.Params(pushKey, pushAppId)
|
||||||
removePusherTask.execute(params)
|
removePusherTask.execute(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,5 +32,8 @@ internal data class JsonPusherData(
|
||||||
* Currently the only format available is 'event_id_only'.
|
* Currently the only format available is 'event_id_only'.
|
||||||
*/
|
*/
|
||||||
@Json(name = "format")
|
@Json(name = "format")
|
||||||
val format: String? = null
|
val format: String? = null,
|
||||||
|
|
||||||
|
@Json(name = "brand")
|
||||||
|
val brand: String? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomStrippedState
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
@ -381,4 +382,14 @@ internal interface RoomAPI {
|
||||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade")
|
||||||
suspend fun upgradeRoom(@Path("roomId") roomId: String,
|
suspend fun upgradeRoom(@Path("roomId") roomId: String,
|
||||||
@Body body: RoomUpgradeBody): RoomUpgradeResponse
|
@Body body: RoomUpgradeBody): RoomUpgradeResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The API returns the summary of the specified room, if the room could be found and the client should be able to view
|
||||||
|
* its contents according to the join_rules, history visibility, space membership and similar rules outlined in MSC3173
|
||||||
|
* as well as if the user is already a member of that room.
|
||||||
|
* https://github.com/deepbluev7/matrix-doc/blob/room-summaries/proposals/3266-room-summary.md
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "im.nheko.summary/rooms/{roomIdOrAlias}/summary")
|
||||||
|
suspend fun getRoomSummary(@Path("roomIdOrAlias") roomidOrAlias: String,
|
||||||
|
@Query("via") viaServers: List<String>?): RoomStrippedState
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,4 +258,7 @@ internal abstract class RoomModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSign3pidInvitationTask(task: DefaultSign3pidInvitationTask): Sign3pidInvitationTask
|
abstract fun bindSign3pidInvitationTask(task: DefaultSign3pidInvitationTask): Sign3pidInvitationTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetRoomSummaryTask(task: DefaultGetRoomSummaryTask): GetRoomSummaryTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFi
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
|
||||||
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
import org.matrix.android.sdk.internal.session.room.GetRoomSummaryTask
|
||||||
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
|
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
|
||||||
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
|
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
|
||||||
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
|
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
|
||||||
|
@ -49,6 +50,7 @@ internal class DefaultPeekRoomTask @Inject constructor(
|
||||||
private val getRoomIdByAliasTask: GetRoomIdByAliasTask,
|
private val getRoomIdByAliasTask: GetRoomIdByAliasTask,
|
||||||
private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask,
|
private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask,
|
||||||
private val getPublicRoomTask: GetPublicRoomTask,
|
private val getPublicRoomTask: GetPublicRoomTask,
|
||||||
|
private val getRoomSummaryTask: GetRoomSummaryTask,
|
||||||
private val resolveRoomStateTask: ResolveRoomStateTask
|
private val resolveRoomStateTask: ResolveRoomStateTask
|
||||||
) : PeekRoomTask {
|
) : PeekRoomTask {
|
||||||
|
|
||||||
|
@ -70,6 +72,25 @@ internal class DefaultPeekRoomTask @Inject constructor(
|
||||||
serverList = emptyList()
|
serverList = emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the room summary API is available on the Home Server we should try it first
|
||||||
|
val strippedState = tryOrNull("Failed to get room stripped state roomId:$roomId") {
|
||||||
|
getRoomSummaryTask.execute(GetRoomSummaryTask.Params(roomId, serverList))
|
||||||
|
}
|
||||||
|
if (strippedState != null) {
|
||||||
|
return PeekResult.Success(
|
||||||
|
roomId = strippedState.roomId,
|
||||||
|
alias = strippedState.getPrimaryAlias() ?: params.roomIdOrAlias.takeIf { isAlias },
|
||||||
|
avatarUrl = strippedState.avatarUrl,
|
||||||
|
name = strippedState.name,
|
||||||
|
topic = strippedState.topic,
|
||||||
|
numJoinedMembers = strippedState.numJoinedMembers,
|
||||||
|
viaServers = serverList,
|
||||||
|
roomType = strippedState.roomType,
|
||||||
|
someMembers = null,
|
||||||
|
isPublic = strippedState.worldReadable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Is it a public room?
|
// Is it a public room?
|
||||||
val visibilityRes = tryOrNull("## PEEK: failed to get visibility") {
|
val visibilityRes = tryOrNull("## PEEK: failed to get visibility") {
|
||||||
getRoomDirectoryVisibilityTask.execute(GetRoomDirectoryVisibilityTask.Params(roomId))
|
getRoomDirectoryVisibilityTask.execute(GetRoomDirectoryVisibilityTask.Params(roomId))
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
|
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
|
import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
|
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
||||||
|
@ -31,6 +32,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
|
@ -226,8 +228,10 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
}
|
}
|
||||||
.toMap()
|
.toMap()
|
||||||
|
|
||||||
lookupMap.keys.forEach { lookedUp ->
|
// First handle child relations
|
||||||
if (lookedUp.roomType == RoomType.SPACE) {
|
lookupMap.keys.asSequence()
|
||||||
|
.filter { it.roomType == RoomType.SPACE }
|
||||||
|
.forEach { lookedUp ->
|
||||||
// get childrens
|
// get childrens
|
||||||
|
|
||||||
lookedUp.children.clearWith { it.deleteFromRealm() }
|
lookedUp.children.clearWith { it.deleteFromRealm() }
|
||||||
|
@ -256,12 +260,49 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// Now let's check parent relations
|
||||||
|
|
||||||
|
lookupMap.keys
|
||||||
|
.forEach { lookedUp ->
|
||||||
lookedUp.parents.clearWith { it.deleteFromRealm() }
|
lookedUp.parents.clearWith { it.deleteFromRealm() }
|
||||||
// can we check parent relations here??
|
// 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()
|
RoomChildRelationInfo(realm, lookedUp.roomId).getParentDescriptions()
|
||||||
.map { parentInfo ->
|
.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(
|
lookedUp.parents.add(
|
||||||
realm.createObject<SpaceParentSummaryEntity>().apply {
|
realm.createObject<SpaceParentSummaryEntity>().apply {
|
||||||
this.parentRoomId = parentInfo.roomId
|
this.parentRoomId = parentInfo.roomId
|
||||||
|
|
|
@ -89,7 +89,6 @@ internal class DefaultSpace(
|
||||||
body = SpaceChildContent(
|
body = SpaceChildContent(
|
||||||
order = null,
|
order = null,
|
||||||
via = null,
|
via = null,
|
||||||
// autoJoin = null,
|
|
||||||
suggested = null
|
suggested = null
|
||||||
).toContent()
|
).toContent()
|
||||||
)
|
)
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
|
||||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.Role
|
||||||
import org.matrix.android.sdk.api.session.space.CreateSpaceParams
|
import org.matrix.android.sdk.api.session.space.CreateSpaceParams
|
||||||
import org.matrix.android.sdk.api.session.space.JoinSpaceResult
|
import org.matrix.android.sdk.api.session.space.JoinSpaceResult
|
||||||
import org.matrix.android.sdk.api.session.space.Space
|
import org.matrix.android.sdk.api.session.space.Space
|
||||||
|
@ -77,7 +78,7 @@ internal class DefaultSpaceService @Inject constructor(
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
this.roomAliasName = roomAliasLocalPart
|
this.roomAliasName = roomAliasLocalPart
|
||||||
this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy(
|
this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy(
|
||||||
invite = 0
|
invite = if (isPublic) Role.Default.value else Role.Moderator.value
|
||||||
)
|
)
|
||||||
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
||||||
this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE
|
this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE
|
||||||
|
@ -221,4 +222,23 @@ internal class DefaultSpaceService @Inject constructor(
|
||||||
).toContent()
|
).toContent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun removeSpaceParent(childRoomId: String, parentSpaceId: String) {
|
||||||
|
val room = roomGetter.getRoom(childRoomId)
|
||||||
|
?: throw IllegalArgumentException("Unknown Room $childRoomId")
|
||||||
|
|
||||||
|
val existingEvent = room.getStateEvent(EventType.STATE_SPACE_PARENT, QueryStringValue.Equals(parentSpaceId))
|
||||||
|
if (existingEvent != null) {
|
||||||
|
// Should i check if it was sent by me?
|
||||||
|
// we don't check power level, it will throw if you cannot do that
|
||||||
|
room.sendStateEvent(
|
||||||
|
eventType = EventType.STATE_SPACE_PARENT,
|
||||||
|
stateKey = parentSpaceId,
|
||||||
|
body = SpaceParentContent(
|
||||||
|
via = null,
|
||||||
|
canonical = null
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
package org.matrix.android.sdk.internal.session.sync
|
package org.matrix.android.sdk.internal.session.sync
|
||||||
|
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
|
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||||
import org.matrix.android.sdk.api.session.initsync.InitSyncStep
|
import org.matrix.android.sdk.api.session.initsync.InitSyncStep
|
||||||
|
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
|
||||||
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
|
@ -26,7 +28,7 @@ import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.network.toFailure
|
import org.matrix.android.sdk.internal.network.toFailure
|
||||||
import org.matrix.android.sdk.internal.session.filter.FilterRepository
|
import org.matrix.android.sdk.internal.session.filter.FilterRepository
|
||||||
import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilitiesTask
|
import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilitiesTask
|
||||||
import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService
|
import org.matrix.android.sdk.internal.session.initsync.DefaultSyncStatusService
|
||||||
import org.matrix.android.sdk.internal.session.initsync.reportSubtask
|
import org.matrix.android.sdk.internal.session.initsync.reportSubtask
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral
|
import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral
|
||||||
import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser
|
import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser
|
||||||
|
@ -40,6 +42,8 @@ import java.io.File
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private val loggerTag = LoggerTag("SyncTask", LoggerTag.SYNC)
|
||||||
|
|
||||||
internal interface SyncTask : Task<SyncTask.Params, Unit> {
|
internal interface SyncTask : Task<SyncTask.Params, Unit> {
|
||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
|
@ -53,7 +57,7 @@ internal class DefaultSyncTask @Inject constructor(
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
private val filterRepository: FilterRepository,
|
private val filterRepository: FilterRepository,
|
||||||
private val syncResponseHandler: SyncResponseHandler,
|
private val syncResponseHandler: SyncResponseHandler,
|
||||||
private val initialSyncProgressService: DefaultInitialSyncProgressService,
|
private val defaultSyncStatusService: DefaultSyncStatusService,
|
||||||
private val syncTokenStore: SyncTokenStore,
|
private val syncTokenStore: SyncTokenStore,
|
||||||
private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask,
|
private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask,
|
||||||
private val userStore: UserStore,
|
private val userStore: UserStore,
|
||||||
|
@ -75,7 +79,7 @@ internal class DefaultSyncTask @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun doSync(params: SyncTask.Params) {
|
private suspend fun doSync(params: SyncTask.Params) {
|
||||||
Timber.v("Sync task started on Thread: ${Thread.currentThread().name}")
|
Timber.tag(loggerTag.value).d("Sync task started on Thread: ${Thread.currentThread().name}")
|
||||||
|
|
||||||
val requestParams = HashMap<String, String>()
|
val requestParams = HashMap<String, String>()
|
||||||
var timeout = 0L
|
var timeout = 0L
|
||||||
|
@ -92,7 +96,7 @@ internal class DefaultSyncTask @Inject constructor(
|
||||||
if (isInitialSync) {
|
if (isInitialSync) {
|
||||||
// We might want to get the user information in parallel too
|
// We might want to get the user information in parallel too
|
||||||
userStore.createOrUpdate(userId)
|
userStore.createOrUpdate(userId)
|
||||||
initialSyncProgressService.startRoot(InitSyncStep.ImportingAccount, 100)
|
defaultSyncStatusService.startRoot(InitSyncStep.ImportingAccount, 100)
|
||||||
}
|
}
|
||||||
// Maybe refresh the homeserver capabilities data we know
|
// Maybe refresh the homeserver capabilities data we know
|
||||||
getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false))
|
getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false))
|
||||||
|
@ -100,20 +104,20 @@ internal class DefaultSyncTask @Inject constructor(
|
||||||
val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT)
|
val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT)
|
||||||
|
|
||||||
if (isInitialSync) {
|
if (isInitialSync) {
|
||||||
Timber.d("INIT_SYNC with filter: ${requestParams["filter"]}")
|
Timber.tag(loggerTag.value).d("INIT_SYNC with filter: ${requestParams["filter"]}")
|
||||||
val initSyncStrategy = initialSyncStrategy
|
val initSyncStrategy = initialSyncStrategy
|
||||||
logDuration("INIT_SYNC strategy: $initSyncStrategy") {
|
logDuration("INIT_SYNC strategy: $initSyncStrategy", loggerTag) {
|
||||||
if (initSyncStrategy is InitialSyncStrategy.Optimized) {
|
if (initSyncStrategy is InitialSyncStrategy.Optimized) {
|
||||||
roomSyncEphemeralTemporaryStore.reset()
|
roomSyncEphemeralTemporaryStore.reset()
|
||||||
workingDir.mkdirs()
|
workingDir.mkdirs()
|
||||||
val file = downloadInitSyncResponse(requestParams)
|
val file = downloadInitSyncResponse(requestParams)
|
||||||
reportSubtask(initialSyncProgressService, InitSyncStep.ImportingAccount, 1, 0.7F) {
|
reportSubtask(defaultSyncStatusService, InitSyncStep.ImportingAccount, 1, 0.7F) {
|
||||||
handleSyncFile(file, initSyncStrategy)
|
handleSyncFile(file, initSyncStrategy)
|
||||||
}
|
}
|
||||||
// Delete all files
|
// Delete all files
|
||||||
workingDir.deleteRecursively()
|
workingDir.deleteRecursively()
|
||||||
} else {
|
} else {
|
||||||
val syncResponse = logDuration("INIT_SYNC Request") {
|
val syncResponse = logDuration("INIT_SYNC Request", loggerTag) {
|
||||||
executeRequest(globalErrorReceiver) {
|
executeRequest(globalErrorReceiver) {
|
||||||
syncAPI.sync(
|
syncAPI.sync(
|
||||||
params = requestParams,
|
params = requestParams,
|
||||||
|
@ -122,43 +126,60 @@ internal class DefaultSyncTask @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logDuration("INIT_SYNC Database insertion") {
|
logDuration("INIT_SYNC Database insertion", loggerTag) {
|
||||||
syncResponseHandler.handleResponse(syncResponse, token, initialSyncProgressService)
|
syncResponseHandler.handleResponse(syncResponse, token, defaultSyncStatusService)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initialSyncProgressService.endAll()
|
defaultSyncStatusService.endAll()
|
||||||
} else {
|
} else {
|
||||||
val syncResponse = executeRequest(globalErrorReceiver) {
|
Timber.tag(loggerTag.value).d("Start incremental sync request")
|
||||||
|
defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncIdle)
|
||||||
|
val syncResponse = try {
|
||||||
|
executeRequest(globalErrorReceiver) {
|
||||||
syncAPI.sync(
|
syncAPI.sync(
|
||||||
params = requestParams,
|
params = requestParams,
|
||||||
readTimeOut = readTimeOut
|
readTimeOut = readTimeOut
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
syncResponseHandler.handleResponse(syncResponse, token, null)
|
} catch (throwable: Throwable) {
|
||||||
|
Timber.tag(loggerTag.value).e(throwable, "Incremental sync request error")
|
||||||
|
defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncError)
|
||||||
|
throw throwable
|
||||||
}
|
}
|
||||||
Timber.v("Sync task finished on Thread: ${Thread.currentThread().name}")
|
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.tag(loggerTag.value).d("Sync task finished on Thread: ${Thread.currentThread().name}")
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun downloadInitSyncResponse(requestParams: Map<String, String>): File {
|
private suspend fun downloadInitSyncResponse(requestParams: Map<String, String>): File {
|
||||||
val workingFile = File(workingDir, "initSync.json")
|
val workingFile = File(workingDir, "initSync.json")
|
||||||
val status = initialSyncStatusRepository.getStep()
|
val status = initialSyncStatusRepository.getStep()
|
||||||
if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) {
|
if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) {
|
||||||
Timber.d("INIT_SYNC file is already here")
|
Timber.tag(loggerTag.value).d("INIT_SYNC file is already here")
|
||||||
reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.3f) {
|
reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.3f) {
|
||||||
// Empty task
|
// Empty task
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADING)
|
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADING)
|
||||||
val syncResponse = logDuration("INIT_SYNC Perform server request") {
|
val syncResponse = logDuration("INIT_SYNC Perform server request", loggerTag) {
|
||||||
reportSubtask(initialSyncProgressService, InitSyncStep.ServerComputing, 1, 0.2f) {
|
reportSubtask(defaultSyncStatusService, InitSyncStep.ServerComputing, 1, 0.2f) {
|
||||||
getSyncResponse(requestParams, MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT)
|
getSyncResponse(requestParams, MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syncResponse.isSuccessful) {
|
if (syncResponse.isSuccessful) {
|
||||||
logDuration("INIT_SYNC Download and save to file") {
|
logDuration("INIT_SYNC Download and save to file", loggerTag) {
|
||||||
reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.1f) {
|
reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.1f) {
|
||||||
syncResponse.body()?.byteStream()?.use { inputStream ->
|
syncResponse.body()?.byteStream()?.use { inputStream ->
|
||||||
workingFile.outputStream().use { outputStream ->
|
workingFile.outputStream().use { outputStream ->
|
||||||
inputStream.copyTo(outputStream)
|
inputStream.copyTo(outputStream)
|
||||||
|
@ -168,7 +189,7 @@ internal class DefaultSyncTask @Inject constructor(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw syncResponse.toFailure(globalErrorReceiver)
|
throw syncResponse.toFailure(globalErrorReceiver)
|
||||||
.also { Timber.w("INIT_SYNC request failure: $this") }
|
.also { Timber.tag(loggerTag.value).w("INIT_SYNC request failure: $this") }
|
||||||
}
|
}
|
||||||
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADED)
|
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADED)
|
||||||
}
|
}
|
||||||
|
@ -185,9 +206,9 @@ internal class DefaultSyncTask @Inject constructor(
|
||||||
).awaitResponse()
|
).awaitResponse()
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
if (throwable is SocketTimeoutException && retry > 0) {
|
if (throwable is SocketTimeoutException && retry > 0) {
|
||||||
Timber.w("INIT_SYNC timeout retry left: $retry")
|
Timber.tag(loggerTag.value).w("INIT_SYNC timeout retry left: $retry")
|
||||||
} else {
|
} else {
|
||||||
Timber.e(throwable, "INIT_SYNC timeout, no retry left, or other error")
|
Timber.tag(loggerTag.value).e(throwable, "INIT_SYNC timeout, no retry left, or other error")
|
||||||
throw throwable
|
throw throwable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,18 +216,18 @@ internal class DefaultSyncTask @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized) {
|
private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized) {
|
||||||
logDuration("INIT_SYNC handleSyncFile()") {
|
logDuration("INIT_SYNC handleSyncFile()", loggerTag) {
|
||||||
val syncResponse = logDuration("INIT_SYNC Read file and parse") {
|
val syncResponse = logDuration("INIT_SYNC Read file and parse", loggerTag) {
|
||||||
syncResponseParser.parse(initSyncStrategy, workingFile)
|
syncResponseParser.parse(initSyncStrategy, workingFile)
|
||||||
}
|
}
|
||||||
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_PARSED)
|
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_PARSED)
|
||||||
// Log some stats
|
// Log some stats
|
||||||
val nbOfJoinedRooms = syncResponse.rooms?.join?.size ?: 0
|
val nbOfJoinedRooms = syncResponse.rooms?.join?.size ?: 0
|
||||||
val nbOfJoinedRoomsInFile = syncResponse.rooms?.join?.values?.count { it.ephemeral is LazyRoomSyncEphemeral.Stored }
|
val nbOfJoinedRoomsInFile = syncResponse.rooms?.join?.values?.count { it.ephemeral is LazyRoomSyncEphemeral.Stored }
|
||||||
Timber.d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files")
|
Timber.tag(loggerTag.value).d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files")
|
||||||
|
|
||||||
logDuration("INIT_SYNC Database insertion") {
|
logDuration("INIT_SYNC Database insertion", loggerTag) {
|
||||||
syncResponseHandler.handleResponse(syncResponse, null, initialSyncProgressService)
|
syncResponseHandler.handleResponse(syncResponse, null, defaultSyncStatusService)
|
||||||
}
|
}
|
||||||
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS)
|
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import kotlinx.coroutines.cancelChildren
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||||
import org.matrix.android.sdk.api.session.call.MxCall
|
import org.matrix.android.sdk.api.session.call.MxCall
|
||||||
import org.matrix.android.sdk.internal.session.call.ActiveCallHandler
|
import org.matrix.android.sdk.internal.session.call.ActiveCallHandler
|
||||||
import org.matrix.android.sdk.internal.session.sync.SyncPresence
|
import org.matrix.android.sdk.internal.session.sync.SyncPresence
|
||||||
|
@ -49,6 +50,8 @@ import kotlin.concurrent.schedule
|
||||||
private const val RETRY_WAIT_TIME_MS = 10_000L
|
private const val RETRY_WAIT_TIME_MS = 10_000L
|
||||||
private const val DEFAULT_LONG_POOL_TIMEOUT = 30_000L
|
private const val DEFAULT_LONG_POOL_TIMEOUT = 30_000L
|
||||||
|
|
||||||
|
private val loggerTag = LoggerTag("SyncThread", LoggerTag.SYNC)
|
||||||
|
|
||||||
internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
private val networkConnectivityChecker: NetworkConnectivityChecker,
|
private val networkConnectivityChecker: NetworkConnectivityChecker,
|
||||||
private val backgroundDetectionObserver: BackgroundDetectionObserver,
|
private val backgroundDetectionObserver: BackgroundDetectionObserver,
|
||||||
|
@ -83,7 +86,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
|
|
||||||
fun restart() = synchronized(lock) {
|
fun restart() = synchronized(lock) {
|
||||||
if (!isStarted) {
|
if (!isStarted) {
|
||||||
Timber.v("Resume sync...")
|
Timber.tag(loggerTag.value).d("Resume sync...")
|
||||||
isStarted = true
|
isStarted = true
|
||||||
// Check again server availability and the token validity
|
// Check again server availability and the token validity
|
||||||
canReachServer = true
|
canReachServer = true
|
||||||
|
@ -94,7 +97,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
|
|
||||||
fun pause() = synchronized(lock) {
|
fun pause() = synchronized(lock) {
|
||||||
if (isStarted) {
|
if (isStarted) {
|
||||||
Timber.v("Pause sync...")
|
Timber.tag(loggerTag.value).d("Pause sync...")
|
||||||
isStarted = false
|
isStarted = false
|
||||||
retryNoNetworkTask?.cancel()
|
retryNoNetworkTask?.cancel()
|
||||||
syncScope.coroutineContext.cancelChildren()
|
syncScope.coroutineContext.cancelChildren()
|
||||||
|
@ -102,7 +105,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
}
|
}
|
||||||
|
|
||||||
fun kill() = synchronized(lock) {
|
fun kill() = synchronized(lock) {
|
||||||
Timber.v("Kill sync...")
|
Timber.tag(loggerTag.value).d("Kill sync...")
|
||||||
updateStateTo(SyncState.Killing)
|
updateStateTo(SyncState.Killing)
|
||||||
retryNoNetworkTask?.cancel()
|
retryNoNetworkTask?.cancel()
|
||||||
syncScope.coroutineContext.cancelChildren()
|
syncScope.coroutineContext.cancelChildren()
|
||||||
|
@ -124,21 +127,21 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
Timber.v("Start syncing...")
|
Timber.tag(loggerTag.value).d("Start syncing...")
|
||||||
|
|
||||||
isStarted = true
|
isStarted = true
|
||||||
networkConnectivityChecker.register(this)
|
networkConnectivityChecker.register(this)
|
||||||
backgroundDetectionObserver.register(this)
|
backgroundDetectionObserver.register(this)
|
||||||
registerActiveCallsObserver()
|
registerActiveCallsObserver()
|
||||||
while (state != SyncState.Killing) {
|
while (state != SyncState.Killing) {
|
||||||
Timber.v("Entering loop, state: $state")
|
Timber.tag(loggerTag.value).d("Entering loop, state: $state")
|
||||||
if (!isStarted) {
|
if (!isStarted) {
|
||||||
Timber.v("Sync is Paused. Waiting...")
|
Timber.tag(loggerTag.value).d("Sync is Paused. Waiting...")
|
||||||
updateStateTo(SyncState.Paused)
|
updateStateTo(SyncState.Paused)
|
||||||
synchronized(lock) { lock.wait() }
|
synchronized(lock) { lock.wait() }
|
||||||
Timber.v("...unlocked")
|
Timber.tag(loggerTag.value).d("...unlocked")
|
||||||
} else if (!canReachServer) {
|
} else if (!canReachServer) {
|
||||||
Timber.v("No network. Waiting...")
|
Timber.tag(loggerTag.value).d("No network. Waiting...")
|
||||||
updateStateTo(SyncState.NoNetwork)
|
updateStateTo(SyncState.NoNetwork)
|
||||||
// We force retrying in RETRY_WAIT_TIME_MS maximum. Otherwise it will be unlocked by onConnectivityChanged() or restart()
|
// We force retrying in RETRY_WAIT_TIME_MS maximum. Otherwise it will be unlocked by onConnectivityChanged() or restart()
|
||||||
retryNoNetworkTask = Timer(SyncState.NoNetwork.toString(), false).schedule(RETRY_WAIT_TIME_MS) {
|
retryNoNetworkTask = Timer(SyncState.NoNetwork.toString(), false).schedule(RETRY_WAIT_TIME_MS) {
|
||||||
|
@ -148,19 +151,19 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
synchronized(lock) { lock.wait() }
|
synchronized(lock) { lock.wait() }
|
||||||
Timber.v("...retry")
|
Timber.tag(loggerTag.value).d("...retry")
|
||||||
} else if (!isTokenValid) {
|
} else if (!isTokenValid) {
|
||||||
Timber.v("Token is invalid. Waiting...")
|
Timber.tag(loggerTag.value).d("Token is invalid. Waiting...")
|
||||||
updateStateTo(SyncState.InvalidToken)
|
updateStateTo(SyncState.InvalidToken)
|
||||||
synchronized(lock) { lock.wait() }
|
synchronized(lock) { lock.wait() }
|
||||||
Timber.v("...unlocked")
|
Timber.tag(loggerTag.value).d("...unlocked")
|
||||||
} else {
|
} else {
|
||||||
if (state !is SyncState.Running) {
|
if (state !is SyncState.Running) {
|
||||||
updateStateTo(SyncState.Running(afterPause = true))
|
updateStateTo(SyncState.Running(afterPause = true))
|
||||||
}
|
}
|
||||||
// No timeout after a pause
|
// No timeout after a pause
|
||||||
val timeout = state.let { if (it is SyncState.Running && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT }
|
val timeout = state.let { if (it is SyncState.Running && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT }
|
||||||
Timber.v("Execute sync request with timeout $timeout")
|
Timber.tag(loggerTag.value).d("Execute sync request with timeout $timeout")
|
||||||
val params = SyncTask.Params(timeout, SyncPresence.Online)
|
val params = SyncTask.Params(timeout, SyncPresence.Online)
|
||||||
val sync = syncScope.launch {
|
val sync = syncScope.launch {
|
||||||
doSync(params)
|
doSync(params)
|
||||||
|
@ -168,10 +171,10 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
runBlocking {
|
runBlocking {
|
||||||
sync.join()
|
sync.join()
|
||||||
}
|
}
|
||||||
Timber.v("...Continue")
|
Timber.tag(loggerTag.value).d("...Continue")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timber.v("Sync killed")
|
Timber.tag(loggerTag.value).d("Sync killed")
|
||||||
updateStateTo(SyncState.Killed)
|
updateStateTo(SyncState.Killed)
|
||||||
backgroundDetectionObserver.unregister(this)
|
backgroundDetectionObserver.unregister(this)
|
||||||
networkConnectivityChecker.unregister(this)
|
networkConnectivityChecker.unregister(this)
|
||||||
|
@ -199,19 +202,19 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
}
|
}
|
||||||
if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) {
|
if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) {
|
||||||
// Timeout are not critical
|
// Timeout are not critical
|
||||||
Timber.v("Timeout")
|
Timber.tag(loggerTag.value).d("Timeout")
|
||||||
} else if (failure is CancellationException) {
|
} else if (failure is CancellationException) {
|
||||||
Timber.v("Cancelled")
|
Timber.tag(loggerTag.value).d("Cancelled")
|
||||||
} else if (failure.isTokenError()) {
|
} else if (failure.isTokenError()) {
|
||||||
// No token or invalid token, stop the thread
|
// No token or invalid token, stop the thread
|
||||||
Timber.w(failure, "Token error")
|
Timber.tag(loggerTag.value).w(failure, "Token error")
|
||||||
isStarted = false
|
isStarted = false
|
||||||
isTokenValid = false
|
isTokenValid = false
|
||||||
} else {
|
} else {
|
||||||
Timber.e(failure)
|
Timber.tag(loggerTag.value).e(failure)
|
||||||
if (failure !is Failure.NetworkConnection || failure.cause is JsonEncodingException) {
|
if (failure !is Failure.NetworkConnection || failure.cause is JsonEncodingException) {
|
||||||
// Wait 10s before retrying
|
// Wait 10s before retrying
|
||||||
Timber.v("Wait 10s")
|
Timber.tag(loggerTag.value).d("Wait 10s")
|
||||||
delay(RETRY_WAIT_TIME_MS)
|
delay(RETRY_WAIT_TIME_MS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,7 +228,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateStateTo(newState: SyncState) {
|
private fun updateStateTo(newState: SyncState) {
|
||||||
Timber.v("Update state from $state to $newState")
|
Timber.tag(loggerTag.value).d("Update state from $state to $newState")
|
||||||
if (newState == state) {
|
if (newState == state) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.matrix.android.sdk.internal.util
|
package org.matrix.android.sdk.internal.util
|
||||||
|
|
||||||
import org.matrix.android.sdk.BuildConfig
|
import org.matrix.android.sdk.BuildConfig
|
||||||
|
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
|
internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
|
||||||
|
@ -32,14 +33,15 @@ internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal suspend fun <T> logDuration(message: String,
|
internal suspend fun <T> logDuration(message: String,
|
||||||
|
loggerTag: LoggerTag,
|
||||||
block: suspend () -> T): T {
|
block: suspend () -> T): T {
|
||||||
Timber.d("$message -- BEGIN")
|
Timber.tag(loggerTag.value).d("$message -- BEGIN")
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
val result = logRamUsage(message) {
|
val result = logRamUsage(message) {
|
||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
val duration = System.currentTimeMillis() - start
|
val duration = System.currentTimeMillis() - start
|
||||||
Timber.d("$message -- END duration: $duration ms")
|
Timber.tag(loggerTag.value).d("$message -- END duration: $duration ms")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,11 @@ apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-parcelize'
|
apply plugin: 'kotlin-parcelize'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdk versions.compileSdk
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 19
|
minSdk versions.minSdk
|
||||||
targetSdkVersion 30
|
targetSdk versions.targetSdk
|
||||||
versionCode 1
|
|
||||||
versionName "1.0"
|
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles 'consumer-rules.pro'
|
consumerProguardFiles 'consumer-rules.pro'
|
||||||
}
|
}
|
||||||
|
@ -41,11 +38,11 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation libs.jetbrains.kotlinStdlibJdk7
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
implementation libs.androidx.appCompat
|
||||||
implementation "androidx.fragment:fragment-ktx:1.3.6"
|
implementation libs.androidx.fragmentKtx
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.3.3'
|
implementation libs.androidx.exifinterface
|
||||||
|
|
||||||
// Log
|
// Log
|
||||||
implementation 'com.jakewharton.timber:timber:5.0.1'
|
implementation libs.jakewharton.timber
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
|
@ -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
|
|
@ -162,7 +162,7 @@ Formatter\.formatShortFileSize===1
|
||||||
# android\.text\.TextUtils
|
# android\.text\.TextUtils
|
||||||
|
|
||||||
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
||||||
enum class===105
|
enum class===106
|
||||||
|
|
||||||
### Do not import temporary legacy classes
|
### Do not import temporary legacy classes
|
||||||
import org.matrix.android.sdk.internal.legacy.riot===3
|
import org.matrix.android.sdk.internal.legacy.riot===3
|
||||||
|
|
|
@ -13,8 +13,8 @@ kapt {
|
||||||
|
|
||||||
// Note: 2 digits max for each value
|
// Note: 2 digits max for each value
|
||||||
ext.versionMajor = 1
|
ext.versionMajor = 1
|
||||||
ext.versionMinor = 2
|
ext.versionMinor = 3
|
||||||
ext.versionPatch = 2
|
ext.versionPatch = 0
|
||||||
|
|
||||||
ext.scVersion = 44
|
ext.scVersion = 44
|
||||||
|
|
||||||
|
@ -104,17 +104,20 @@ ext.abiVersionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2, "x86": 3, "x86_64": 4].
|
||||||
def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0
|
def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
|
||||||
|
|
||||||
|
|
||||||
// Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use
|
// Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use
|
||||||
// Ref: https://issuetracker.google.com/issues/144111441
|
// Ref: https://issuetracker.google.com/issues/144111441
|
||||||
ndkVersion "21.3.6528147"
|
ndkVersion "21.3.6528147"
|
||||||
|
|
||||||
|
compileSdk versions.compileSdk
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.spiritcroc.riotx"
|
applicationId "de.spiritcroc.riotx"
|
||||||
// Set to API 21: see #405
|
// Set to API 21: see #405
|
||||||
minSdkVersion 21
|
minSdk versions.minSdk
|
||||||
targetSdkVersion 30
|
targetSdk versions.targetSdk
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
renderscriptTargetApi 24
|
renderscriptTargetApi 24
|
||||||
|
@ -294,8 +297,8 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_11
|
sourceCompatibility versions.sourceCompat
|
||||||
targetCompatibility JavaVersion.VERSION_11
|
targetCompatibility versions.targetCompat
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
|
@ -322,26 +325,6 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
def epoxy_version = '4.6.2'
|
|
||||||
def fragment_version = '1.3.6'
|
|
||||||
def arrow_version = "0.8.2"
|
|
||||||
def markwon_version = '4.1.2'
|
|
||||||
def big_image_viewer_version = '1.8.1'
|
|
||||||
def glide_version = '4.12.0'
|
|
||||||
def moshi_version = '1.12.0'
|
|
||||||
def daggerVersion = '2.38.1'
|
|
||||||
def autofill_version = "1.1.0"
|
|
||||||
def work_version = '2.5.0'
|
|
||||||
def arch_version = '2.1.0'
|
|
||||||
def lifecycle_version = '2.2.0'
|
|
||||||
def rxbinding_version = '3.1.0'
|
|
||||||
def jjwt_version = '0.11.2'
|
|
||||||
|
|
||||||
// Tests
|
|
||||||
def kluent_version = '1.68'
|
|
||||||
def androidxTest_version = '1.4.0'
|
|
||||||
def espresso_version = '3.4.0'
|
|
||||||
|
|
||||||
implementation project(":matrix-sdk-android")
|
implementation project(":matrix-sdk-android")
|
||||||
implementation project(":matrix-sdk-android-rx")
|
implementation project(":matrix-sdk-android-rx")
|
||||||
implementation project(":diff-match-patch")
|
implementation project(":diff-match-patch")
|
||||||
|
@ -350,75 +333,79 @@ dependencies {
|
||||||
implementation project(":library:ui-styles")
|
implementation project(":library:ui-styles")
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation libs.jetbrains.kotlinStdlibJdk7
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
implementation libs.jetbrains.kotlinReflect
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
implementation libs.jetbrains.coroutinesCore
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
implementation libs.jetbrains.coroutinesAndroid
|
||||||
|
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
implementation libs.androidx.recyclerview
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
implementation libs.androidx.appCompat
|
||||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
implementation libs.androidx.fragmentKtx
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
|
implementation libs.androidx.constraintLayout
|
||||||
implementation "androidx.sharetarget:sharetarget:1.1.0"
|
implementation "androidx.sharetarget:sharetarget:1.1.0"
|
||||||
implementation 'androidx.core:core-ktx:1.6.0'
|
implementation libs.androidx.core
|
||||||
implementation "androidx.media:media:1.4.1"
|
implementation "androidx.media:media:1.4.2"
|
||||||
implementation "androidx.transition:transition:1.4.1"
|
implementation "androidx.transition:transition:1.4.1"
|
||||||
|
|
||||||
implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
|
implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
|
||||||
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0"
|
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0"
|
||||||
|
|
||||||
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
implementation libs.squareup.moshi
|
||||||
implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"
|
implementation libs.squareup.moshiKt
|
||||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
kapt libs.squareup.moshiKotlin
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
|
implementation libs.androidx.lifecycleExtensions
|
||||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
implementation libs.androidx.lifecycleLivedata
|
||||||
|
|
||||||
|
implementation libs.androidx.datastore
|
||||||
|
implementation libs.androidx.datastorepreferences
|
||||||
|
|
||||||
|
|
||||||
// Log
|
// Log
|
||||||
implementation 'com.jakewharton.timber:timber:5.0.1'
|
implementation libs.jakewharton.timber
|
||||||
|
|
||||||
// Debug
|
// Debug
|
||||||
implementation 'com.facebook.stetho:stetho:1.6.0'
|
implementation 'com.facebook.stetho:stetho:1.6.0'
|
||||||
|
|
||||||
// Phone number https://github.com/google/libphonenumber
|
// Phone number https://github.com/google/libphonenumber
|
||||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.31'
|
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.33'
|
||||||
|
|
||||||
// rx
|
// rx
|
||||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
|
implementation libs.rx.rxKotlin
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
implementation libs.rx.rxAndroid
|
||||||
implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.1'
|
implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.1'
|
||||||
// RXBinding
|
// RXBinding
|
||||||
implementation "com.jakewharton.rxbinding3:rxbinding:$rxbinding_version"
|
implementation libs.jakewharton.rxbinding
|
||||||
implementation "com.jakewharton.rxbinding3:rxbinding-appcompat:$rxbinding_version"
|
implementation libs.jakewharton.rxbindingAppcompat
|
||||||
implementation "com.jakewharton.rxbinding3:rxbinding-material:$rxbinding_version"
|
implementation libs.jakewharton.rxbindingMaterial
|
||||||
|
|
||||||
implementation("com.airbnb.android:epoxy:$epoxy_version")
|
implementation libs.airbnb.epoxy
|
||||||
implementation "com.airbnb.android:epoxy-glide-preloading:$epoxy_version"
|
implementation libs.airbnb.epoxyGlide
|
||||||
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
|
kapt libs.airbnb.epoxyProcessor
|
||||||
implementation "com.airbnb.android:epoxy-paging:$epoxy_version"
|
implementation libs.airbnb.epoxyPaging
|
||||||
implementation 'com.airbnb.android:mvrx:1.5.1'
|
implementation libs.airbnb.mvrx
|
||||||
|
|
||||||
// Work
|
// Work
|
||||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
implementation libs.androidx.work
|
||||||
|
|
||||||
// Paging
|
// Paging
|
||||||
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
|
implementation libs.androidx.pagingRuntimeKtx
|
||||||
|
|
||||||
// Functional Programming
|
// Functional Programming
|
||||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
implementation libs.arrow.core
|
||||||
|
|
||||||
// Pref
|
// Pref
|
||||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
implementation libs.androidx.preferenceKtx
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||||
implementation 'com.google.android.material:material:1.4.0'
|
implementation libs.google.material
|
||||||
implementation 'me.gujun.android:span:1.7'
|
implementation 'me.gujun.android:span:1.7'
|
||||||
implementation "io.noties.markwon:core:$markwon_version"
|
implementation libs.markwon.core
|
||||||
implementation "io.noties.markwon:html:$markwon_version"
|
implementation libs.markwon.html
|
||||||
implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.5.2'
|
implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.5.2'
|
||||||
implementation 'me.saket:better-link-movement-method:2.2.0'
|
implementation 'me.saket:better-link-movement-method:2.2.0'
|
||||||
implementation 'com.google.android:flexbox:2.0.1'
|
implementation 'com.google.android:flexbox:2.0.1'
|
||||||
implementation "androidx.autofill:autofill:$autofill_version"
|
implementation libs.androidx.autoFill
|
||||||
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
||||||
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
|
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
|
||||||
implementation 'com.github.hyuwah:DraggableView:1.0.0'
|
implementation 'com.github.hyuwah:DraggableView:1.0.0'
|
||||||
|
@ -442,16 +429,16 @@ dependencies {
|
||||||
implementation 'com.squareup:seismic:1.0.2'
|
implementation 'com.squareup:seismic:1.0.2'
|
||||||
|
|
||||||
// Image Loading
|
// Image Loading
|
||||||
implementation "com.github.piasy:BigImageViewer:$big_image_viewer_version"
|
implementation libs.github.bigImageViewer
|
||||||
implementation "com.github.piasy:GlideImageLoader:$big_image_viewer_version"
|
implementation libs.github.glideImageLoader
|
||||||
implementation "com.github.piasy:ProgressPieIndicator:$big_image_viewer_version"
|
implementation libs.github.progressPieIndicator
|
||||||
implementation "com.github.piasy:GlideImageViewFactory:$big_image_viewer_version"
|
implementation libs.github.glideImageViewFactory
|
||||||
|
|
||||||
// implementation 'com.github.MikeOrtiz:TouchImageView:3.0.2'
|
// implementation 'com.github.MikeOrtiz:TouchImageView:3.0.2'
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
|
|
||||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
implementation libs.github.glide
|
||||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
kapt libs.github.glideCompiler
|
||||||
implementation 'com.danikula:videocache:2.7.1'
|
implementation 'com.danikula:videocache:2.7.1'
|
||||||
implementation 'com.github.yalantis:ucrop:2.2.7'
|
implementation 'com.github.yalantis:ucrop:2.2.7'
|
||||||
|
|
||||||
|
@ -462,8 +449,8 @@ dependencies {
|
||||||
implementation 'nl.dionsegijn:konfetti:1.3.2'
|
implementation 'nl.dionsegijn:konfetti:1.3.2'
|
||||||
implementation 'com.github.jetradarmobile:android-snowfall:1.2.1'
|
implementation 'com.github.jetradarmobile:android-snowfall:1.2.1'
|
||||||
// DI
|
// DI
|
||||||
implementation "com.google.dagger:dagger:$daggerVersion"
|
implementation libs.dagger.dagger
|
||||||
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
|
kapt libs.dagger.daggerCompiler
|
||||||
|
|
||||||
// UnifiedPush
|
// UnifiedPush
|
||||||
implementation 'com.github.UnifiedPush:android-connector:1.2.0'
|
implementation 'com.github.UnifiedPush:android-connector:1.2.0'
|
||||||
|
@ -505,36 +492,37 @@ dependencies {
|
||||||
implementation 'im.dlg:android-dialer:1.2.5'
|
implementation 'im.dlg:android-dialer:1.2.5'
|
||||||
|
|
||||||
// JWT
|
// JWT
|
||||||
api "io.jsonwebtoken:jjwt-api:$jjwt_version"
|
api libs.jsonwebtoken.jjwtApi
|
||||||
runtimeOnly "io.jsonwebtoken:jjwt-impl:$jjwt_version"
|
runtimeOnly libs.jsonwebtoken.jjwtImpl
|
||||||
runtimeOnly("io.jsonwebtoken:jjwt-orgjson:$jjwt_version") {
|
runtimeOnly(libs.jsonwebtoken.jjwtOrgjson) {
|
||||||
exclude group: 'org.json', module: 'json' //provided by Android natively
|
exclude group: 'org.json', module: 'json' //provided by Android natively
|
||||||
}
|
}
|
||||||
implementation 'commons-codec:commons-codec:1.15'
|
implementation 'commons-codec:commons-codec:1.15'
|
||||||
|
|
||||||
|
|
||||||
// TESTS
|
// TESTS
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation libs.tests.junit
|
||||||
testImplementation "org.amshove.kluent:kluent-android:$kluent_version"
|
testImplementation libs.tests.kluent
|
||||||
// Plant Timber tree for test
|
// Plant Timber tree for test
|
||||||
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
testImplementation libs.tests.timberJunitRule
|
||||||
|
|
||||||
// Activate when you want to check for leaks, from time to time.
|
// Activate when you want to check for leaks, from time to time.
|
||||||
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
|
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
|
||||||
|
|
||||||
androidTestImplementation "androidx.test:core:$androidxTest_version"
|
androidTestImplementation libs.androidx.testCore
|
||||||
androidTestImplementation "androidx.test:runner:$androidxTest_version"
|
androidTestImplementation libs.androidx.testRunner
|
||||||
androidTestImplementation "androidx.test:rules:$androidxTest_version"
|
androidTestImplementation libs.androidx.testRules
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation libs.androidx.junit
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
|
androidTestImplementation libs.androidx.espressoCore
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version"
|
androidTestImplementation libs.androidx.espressoContrib
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version"
|
androidTestImplementation libs.androidx.espressoIntents
|
||||||
androidTestImplementation "org.amshove.kluent:kluent-android:$kluent_version"
|
androidTestImplementation libs.tests.kluent
|
||||||
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
|
androidTestImplementation libs.androidx.coreTesting
|
||||||
// Plant Timber tree for test
|
// Plant Timber tree for test
|
||||||
androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
androidTestImplementation libs.tests.timberJunitRule
|
||||||
// "The one who serves a great Espresso"
|
// "The one who serves a great Espresso"
|
||||||
androidTestImplementation('com.adevinta.android:barista:4.1.0') {
|
androidTestImplementation('com.adevinta.android:barista:4.2.0') {
|
||||||
exclude group: 'org.jetbrains.kotlin'
|
exclude group: 'org.jetbrains.kotlin'
|
||||||
}
|
}
|
||||||
|
androidTestUtil libs.androidx.orchestrator
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
<issue id="Recycle" severity="error" />
|
<issue id="Recycle" severity="error" />
|
||||||
<issue id="KotlinPropertyAccess" severity="error" />
|
<issue id="KotlinPropertyAccess" severity="error" />
|
||||||
<issue id="DefaultLocale" severity="error" />
|
<issue id="DefaultLocale" severity="error" />
|
||||||
|
<issue id="CheckResult" severity="error" />
|
||||||
|
|
||||||
<issue id="InvalidPackage">
|
<issue id="InvalidPackage">
|
||||||
<!-- Ignore error from HtmlCompressor lib -->
|
<!-- Ignore error from HtmlCompressor lib -->
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
package im.vector.app.features.reactions.data
|
package im.vector.app.features.reactions.data
|
||||||
|
|
||||||
import im.vector.app.InstrumentedTest
|
import im.vector.app.InstrumentedTest
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
|
@ -30,64 +34,80 @@ import kotlin.system.measureTimeMillis
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
class EmojiDataSourceTest : InstrumentedTest {
|
class EmojiDataSourceTest : InstrumentedTest {
|
||||||
|
|
||||||
|
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun checkParsingTime() {
|
fun checkParsingTime() {
|
||||||
val time = measureTimeMillis {
|
val time = measureTimeMillis {
|
||||||
EmojiDataSource(context().resources)
|
createEmojiDataSource()
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue("Too long to parse", time < 100)
|
assertTrue("Too long to parse", time < 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun checkNumberOfResult() {
|
fun checkNumberOfResult() {
|
||||||
val emojiDataSource = EmojiDataSource(context().resources)
|
val emojiDataSource = createEmojiDataSource()
|
||||||
assertTrue("Wrong number of emojis", emojiDataSource.rawData.emojis.size >= 500)
|
val rawData = runBlocking {
|
||||||
assertTrue("Wrong number of categories", emojiDataSource.rawData.categories.size >= 8)
|
emojiDataSource.rawData.await()
|
||||||
|
}
|
||||||
|
assertTrue("Wrong number of emojis", rawData.emojis.size >= 500)
|
||||||
|
assertTrue("Wrong number of categories", rawData.categories.size >= 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun searchTestEmptySearch() {
|
fun searchTestEmptySearch() {
|
||||||
val emojiDataSource = EmojiDataSource(context().resources)
|
val emojiDataSource = createEmojiDataSource()
|
||||||
|
val result = runBlocking {
|
||||||
assertTrue("Empty search should return at least 500 results", emojiDataSource.filterWith("").size >= 500)
|
emojiDataSource.filterWith("")
|
||||||
|
}
|
||||||
|
assertTrue("Empty search should return at least 500 results", result.size >= 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun searchTestNoResult() {
|
fun searchTestNoResult() {
|
||||||
val emojiDataSource = EmojiDataSource(context().resources)
|
val emojiDataSource = createEmojiDataSource()
|
||||||
|
val result = runBlocking {
|
||||||
assertTrue("Should not have result", emojiDataSource.filterWith("noresult").isEmpty())
|
emojiDataSource.filterWith("noresult")
|
||||||
|
}
|
||||||
|
assertTrue("Should not have result", result.isEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun searchTestOneResult() {
|
fun searchTestOneResult() {
|
||||||
val emojiDataSource = EmojiDataSource(context().resources)
|
val emojiDataSource = createEmojiDataSource()
|
||||||
|
val result = runBlocking {
|
||||||
assertEquals("Should have 1 result", 1, emojiDataSource.filterWith("france").size)
|
emojiDataSource.filterWith("france")
|
||||||
|
}
|
||||||
|
assertEquals("Should have 1 result", 1, result.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun searchTestManyResult() {
|
fun searchTestManyResult() {
|
||||||
val emojiDataSource = EmojiDataSource(context().resources)
|
val emojiDataSource = createEmojiDataSource()
|
||||||
|
val result = runBlocking {
|
||||||
assertTrue("Should have many result", emojiDataSource.filterWith("fra").size > 1)
|
emojiDataSource.filterWith("fra")
|
||||||
|
}
|
||||||
|
assertTrue("Should have many result", result.size > 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTada() {
|
fun testTada() {
|
||||||
val emojiDataSource = EmojiDataSource(context().resources)
|
val emojiDataSource = createEmojiDataSource()
|
||||||
|
val result = runBlocking {
|
||||||
val result = emojiDataSource.filterWith("tada")
|
emojiDataSource.filterWith("tada")
|
||||||
|
}
|
||||||
assertEquals("Should find tada emoji", 1, result.size)
|
assertEquals("Should find tada emoji", 1, result.size)
|
||||||
assertEquals("Should find tada emoji", "🎉", result[0].emoji)
|
assertEquals("Should find tada emoji", "🎉", result[0].emoji)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testQuickReactions() {
|
fun testQuickReactions() {
|
||||||
val emojiDataSource = EmojiDataSource(context().resources)
|
val emojiDataSource = createEmojiDataSource()
|
||||||
|
val result = runBlocking {
|
||||||
|
emojiDataSource.getQuickReactions()
|
||||||
|
}
|
||||||
|
assertEquals("Should have 8 quick reactions", 8, result.size)
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals("Should have 8 quick reactions", 8, emojiDataSource.getQuickReactions().size)
|
private fun createEmojiDataSource() = EmojiDataSource(coroutineScope, context().resources)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,6 +225,8 @@ class UiAllScreensSanityTest {
|
||||||
clickOn(R.string.message_add_reaction)
|
clickOn(R.string.message_add_reaction)
|
||||||
// Filter
|
// Filter
|
||||||
// TODO clickMenu(R.id.search)
|
// TODO clickMenu(R.id.search)
|
||||||
|
// Wait for emoji to load, it's async now
|
||||||
|
sleep(1_000)
|
||||||
clickListItem(R.id.emojiRecyclerView, 4)
|
clickListItem(R.id.emojiRecyclerView, 4)
|
||||||
|
|
||||||
// Test Edit mode
|
// Test Edit mode
|
||||||
|
@ -283,6 +285,7 @@ class UiAllScreensSanityTest {
|
||||||
clickListItem(R.id.matrixProfileRecyclerView, 9)
|
clickListItem(R.id.matrixProfileRecyclerView, 9)
|
||||||
// File tab
|
// File tab
|
||||||
clickOn(R.string.uploads_files_title)
|
clickOn(R.string.uploads_files_title)
|
||||||
|
sleep(1000)
|
||||||
pressBack()
|
pressBack()
|
||||||
|
|
||||||
assertDisplayed(R.id.roomProfileAvatarView)
|
assertDisplayed(R.id.roomProfileAvatarView)
|
||||||
|
@ -334,6 +337,7 @@ class UiAllScreensSanityTest {
|
||||||
private fun navigateToRoomPeople() {
|
private fun navigateToRoomPeople() {
|
||||||
// Open first user
|
// Open first user
|
||||||
clickListItem(R.id.roomSettingsRecyclerView, 1)
|
clickListItem(R.id.roomSettingsRecyclerView, 1)
|
||||||
|
sleep(1000)
|
||||||
assertDisplayed(R.id.memberProfilePowerLevelView)
|
assertDisplayed(R.id.memberProfilePowerLevelView)
|
||||||
|
|
||||||
// Verification
|
// Verification
|
||||||
|
@ -342,8 +346,9 @@ class UiAllScreensSanityTest {
|
||||||
|
|
||||||
// Role
|
// Role
|
||||||
clickListItem(R.id.matrixProfileRecyclerView, 3)
|
clickListItem(R.id.matrixProfileRecyclerView, 3)
|
||||||
|
sleep(1000)
|
||||||
clickDialogNegativeButton()
|
clickDialogNegativeButton()
|
||||||
|
sleep(1000)
|
||||||
clickBack()
|
clickBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -330,6 +330,7 @@
|
||||||
<activity android:name=".features.spaces.SpaceCreationActivity" />
|
<activity android:name=".features.spaces.SpaceCreationActivity" />
|
||||||
<activity android:name=".features.spaces.manage.SpaceManageActivity" />
|
<activity android:name=".features.spaces.manage.SpaceManageActivity" />
|
||||||
<activity android:name=".features.spaces.people.SpacePeopleActivity" />
|
<activity android:name=".features.spaces.people.SpacePeopleActivity" />
|
||||||
|
<activity android:name=".features.spaces.leave.SpaceLeaveAdvancedActivity" />
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -61,7 +61,20 @@ class AppStateHandler @Inject constructor(
|
||||||
|
|
||||||
var onSwitchSpaceListener: OnSwitchSpaceListener? = null
|
var onSwitchSpaceListener: OnSwitchSpaceListener? = null
|
||||||
|
|
||||||
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? = selectedSpaceDataSource.currentValue?.orNull()
|
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? {
|
||||||
|
// XXX we should somehow make it live :/ just a work around
|
||||||
|
// For example just after creating a space and switching to it the
|
||||||
|
// name in the app Bar could show Empty Room, and it will not update unless you
|
||||||
|
// switch space
|
||||||
|
return selectedSpaceDataSource.currentValue?.orNull()?.let {
|
||||||
|
if (it is RoomGroupingMethod.BySpace) {
|
||||||
|
// try to refresh sum?
|
||||||
|
it.spaceSummary?.roomId?.let { activeSessionHolder.getSafeActiveSession()?.getRoomSummary(it) }?.let {
|
||||||
|
RoomGroupingMethod.BySpace(it)
|
||||||
|
} ?: it
|
||||||
|
} else it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setCurrentSpace(spaceId: String?, session: Session? = null) {
|
fun setCurrentSpace(spaceId: String?, session: Session? = null) {
|
||||||
val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
|
val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
|
||||||
|
|
|
@ -143,9 +143,11 @@ import im.vector.app.features.signout.soft.SoftLogoutFragment
|
||||||
import im.vector.app.features.spaces.SpaceListFragment
|
import im.vector.app.features.spaces.SpaceListFragment
|
||||||
import im.vector.app.features.spaces.create.ChoosePrivateSpaceTypeFragment
|
import im.vector.app.features.spaces.create.ChoosePrivateSpaceTypeFragment
|
||||||
import im.vector.app.features.spaces.create.ChooseSpaceTypeFragment
|
import im.vector.app.features.spaces.create.ChooseSpaceTypeFragment
|
||||||
|
import im.vector.app.features.spaces.create.CreateSpaceAdd3pidInvitesFragment
|
||||||
import im.vector.app.features.spaces.create.CreateSpaceDefaultRoomsFragment
|
import im.vector.app.features.spaces.create.CreateSpaceDefaultRoomsFragment
|
||||||
import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment
|
import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment
|
||||||
import im.vector.app.features.spaces.explore.SpaceDirectoryFragment
|
import im.vector.app.features.spaces.explore.SpaceDirectoryFragment
|
||||||
|
import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedFragment
|
||||||
import im.vector.app.features.spaces.manage.SpaceAddRoomFragment
|
import im.vector.app.features.spaces.manage.SpaceAddRoomFragment
|
||||||
import im.vector.app.features.spaces.manage.SpaceManageRoomsFragment
|
import im.vector.app.features.spaces.manage.SpaceManageRoomsFragment
|
||||||
import im.vector.app.features.spaces.manage.SpaceSettingsFragment
|
import im.vector.app.features.spaces.manage.SpaceSettingsFragment
|
||||||
|
@ -799,6 +801,11 @@ interface FragmentModule {
|
||||||
@FragmentKey(ChoosePrivateSpaceTypeFragment::class)
|
@FragmentKey(ChoosePrivateSpaceTypeFragment::class)
|
||||||
fun bindChoosePrivateSpaceTypeFragment(fragment: ChoosePrivateSpaceTypeFragment): Fragment
|
fun bindChoosePrivateSpaceTypeFragment(fragment: ChoosePrivateSpaceTypeFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(CreateSpaceAdd3pidInvitesFragment::class)
|
||||||
|
fun bindCreateSpaceAdd3pidInvitesFragment(fragment: CreateSpaceAdd3pidInvitesFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(SpaceAddRoomFragment::class)
|
@FragmentKey(SpaceAddRoomFragment::class)
|
||||||
|
@ -828,4 +835,9 @@ interface FragmentModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(RoomJoinRuleChooseRestrictedFragment::class)
|
@FragmentKey(RoomJoinRuleChooseRestrictedFragment::class)
|
||||||
fun bindRoomJoinRuleChooseRestrictedFragment(fragment: RoomJoinRuleChooseRestrictedFragment): Fragment
|
fun bindRoomJoinRuleChooseRestrictedFragment(fragment: RoomJoinRuleChooseRestrictedFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(SpaceLeaveAdvancedFragment::class)
|
||||||
|
fun bindSpaceLeaveAdvancedFragment(fragment: SpaceLeaveAdvancedFragment): Fragment
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,10 +84,12 @@ import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet
|
||||||
import im.vector.app.features.share.IncomingShareActivity
|
import im.vector.app.features.share.IncomingShareActivity
|
||||||
import im.vector.app.features.signout.soft.SoftLogoutActivity
|
import im.vector.app.features.signout.soft.SoftLogoutActivity
|
||||||
import im.vector.app.features.spaces.InviteRoomSpaceChooserBottomSheet
|
import im.vector.app.features.spaces.InviteRoomSpaceChooserBottomSheet
|
||||||
|
import im.vector.app.features.spaces.LeaveSpaceBottomSheet
|
||||||
import im.vector.app.features.spaces.SpaceCreationActivity
|
import im.vector.app.features.spaces.SpaceCreationActivity
|
||||||
import im.vector.app.features.spaces.SpaceExploreActivity
|
import im.vector.app.features.spaces.SpaceExploreActivity
|
||||||
import im.vector.app.features.spaces.SpaceSettingsMenuBottomSheet
|
import im.vector.app.features.spaces.SpaceSettingsMenuBottomSheet
|
||||||
import im.vector.app.features.spaces.invite.SpaceInviteBottomSheet
|
import im.vector.app.features.spaces.invite.SpaceInviteBottomSheet
|
||||||
|
import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedActivity
|
||||||
import im.vector.app.features.spaces.manage.SpaceManageActivity
|
import im.vector.app.features.spaces.manage.SpaceManageActivity
|
||||||
import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
|
import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
|
||||||
import im.vector.app.features.terms.ReviewTermsActivity
|
import im.vector.app.features.terms.ReviewTermsActivity
|
||||||
|
@ -96,6 +98,7 @@ import im.vector.app.features.usercode.UserCodeActivity
|
||||||
import im.vector.app.features.widgets.WidgetActivity
|
import im.vector.app.features.widgets.WidgetActivity
|
||||||
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
|
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
|
||||||
import im.vector.app.features.workers.signout.SignOutBottomSheetDialogFragment
|
import im.vector.app.features.workers.signout.SignOutBottomSheetDialogFragment
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
@Component(
|
@Component(
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
@ -127,6 +130,7 @@ interface ScreenComponent {
|
||||||
fun uiStateRepository(): UiStateRepository
|
fun uiStateRepository(): UiStateRepository
|
||||||
fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog
|
fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog
|
||||||
fun autoAcceptInvites(): AutoAcceptInvites
|
fun autoAcceptInvites(): AutoAcceptInvites
|
||||||
|
fun appCoroutineScope(): CoroutineScope
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* Activities
|
* Activities
|
||||||
|
@ -171,6 +175,7 @@ interface ScreenComponent {
|
||||||
fun inject(activity: SpaceExploreActivity)
|
fun inject(activity: SpaceExploreActivity)
|
||||||
fun inject(activity: SpaceManageActivity)
|
fun inject(activity: SpaceManageActivity)
|
||||||
fun inject(activity: RoomJoinRuleActivity)
|
fun inject(activity: RoomJoinRuleActivity)
|
||||||
|
fun inject(activity: SpaceLeaveAdvancedActivity)
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* BottomSheets
|
* BottomSheets
|
||||||
|
@ -199,6 +204,7 @@ interface ScreenComponent {
|
||||||
fun inject(bottomSheet: SpaceInviteBottomSheet)
|
fun inject(bottomSheet: SpaceInviteBottomSheet)
|
||||||
fun inject(bottomSheet: JoinReplacementRoomBottomSheet)
|
fun inject(bottomSheet: JoinReplacementRoomBottomSheet)
|
||||||
fun inject(bottomSheet: MigrateRoomBottomSheet)
|
fun inject(bottomSheet: MigrateRoomBottomSheet)
|
||||||
|
fun inject(bottomSheet: LeaveSpaceBottomSheet)
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* Others
|
* Others
|
||||||
|
|
|
@ -58,8 +58,10 @@ import im.vector.app.features.rageshake.VectorFileLogger
|
||||||
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
|
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
|
||||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||||
import im.vector.app.features.session.SessionListener
|
import im.vector.app.features.session.SessionListener
|
||||||
|
import im.vector.app.features.settings.VectorDataStore
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.app.features.ui.UiStateRepository
|
import im.vector.app.features.ui.UiStateRepository
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import org.matrix.android.sdk.api.Matrix
|
import org.matrix.android.sdk.api.Matrix
|
||||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||||
|
@ -145,6 +147,8 @@ interface VectorComponent {
|
||||||
|
|
||||||
fun vectorPreferences(): VectorPreferences
|
fun vectorPreferences(): VectorPreferences
|
||||||
|
|
||||||
|
fun vectorDataStore(): VectorDataStore
|
||||||
|
|
||||||
fun wifiDetector(): WifiDetector
|
fun wifiDetector(): WifiDetector
|
||||||
|
|
||||||
fun vectorFileLogger(): VectorFileLogger
|
fun vectorFileLogger(): VectorFileLogger
|
||||||
|
@ -165,6 +169,8 @@ interface VectorComponent {
|
||||||
|
|
||||||
fun webRtcCallManager(): WebRtcCallManager
|
fun webRtcCallManager(): WebRtcCallManager
|
||||||
|
|
||||||
|
fun appCoroutineScope(): CoroutineScope
|
||||||
|
|
||||||
fun jitsiActiveConferenceHolder(): JitsiActiveConferenceHolder
|
fun jitsiActiveConferenceHolder(): JitsiActiveConferenceHolder
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
|
|
|
@ -33,12 +33,16 @@ import im.vector.app.features.pin.PinCodeStore
|
||||||
import im.vector.app.features.pin.SharedPrefPinCodeStore
|
import im.vector.app.features.pin.SharedPrefPinCodeStore
|
||||||
import im.vector.app.features.ui.SharedPreferencesUiStateRepository
|
import im.vector.app.features.ui.SharedPreferencesUiStateRepository
|
||||||
import im.vector.app.features.ui.UiStateRepository
|
import im.vector.app.features.ui.UiStateRepository
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import org.matrix.android.sdk.api.Matrix
|
import org.matrix.android.sdk.api.Matrix
|
||||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
abstract class VectorModule {
|
abstract class VectorModule {
|
||||||
|
@ -94,6 +98,13 @@ abstract class VectorModule {
|
||||||
fun providesHomeServerHistoryService(matrix: Matrix): HomeServerHistoryService {
|
fun providesHomeServerHistoryService(matrix: Matrix): HomeServerHistoryService {
|
||||||
return matrix.homeServerHistoryService()
|
return matrix.homeServerHistoryService()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@JvmStatic
|
||||||
|
@Singleton
|
||||||
|
fun providesApplicationCoroutineScope(): CoroutineScope {
|
||||||
|
return CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
|
|
|
@ -39,13 +39,15 @@ import im.vector.app.features.themes.ThemeUtils
|
||||||
/**
|
/**
|
||||||
* Set a text in the TextView, or set visibility to GONE if the text is null
|
* Set a text in the TextView, or set visibility to GONE if the text is null
|
||||||
*/
|
*/
|
||||||
fun TextView.setTextOrHide(newText: CharSequence?, hideWhenBlank: Boolean = true) {
|
fun TextView.setTextOrHide(newText: CharSequence?, hideWhenBlank: Boolean = true, vararg relatedViews: View = emptyArray()) {
|
||||||
if (newText == null
|
if (newText == null
|
||||||
|| (newText.isBlank() && hideWhenBlank)) {
|
|| (newText.isBlank() && hideWhenBlank)) {
|
||||||
isVisible = false
|
isVisible = false
|
||||||
|
relatedViews.forEach { it.isVisible = false }
|
||||||
} else {
|
} else {
|
||||||
this.text = newText
|
this.text = newText
|
||||||
isVisible = true
|
isVisible = true
|
||||||
|
relatedViews.forEach { it.isVisible = true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,11 +16,14 @@
|
||||||
|
|
||||||
package im.vector.app.core.extensions
|
package im.vector.app.core.extensions
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
|
import android.widget.ImageView
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,3 +53,8 @@ fun View.getMeasurements(): Pair<Int, Int> {
|
||||||
val height = measuredHeight
|
val height = measuredHeight
|
||||||
return width to height
|
return width to height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ImageView.setDrawableOrHide(drawableRes: Drawable?) {
|
||||||
|
setImageDrawable(drawableRes)
|
||||||
|
isVisible = drawableRes != null
|
||||||
|
}
|
||||||
|
|
|
@ -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."
|
||||||
|
}
|
|
@ -62,6 +62,23 @@ class PushersManager @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun registerEmailForPush(email: String) {
|
||||||
|
val currentSession = activeSessionHolder.getActiveSession()
|
||||||
|
val appName = appNameProvider.getAppName()
|
||||||
|
currentSession.addEmailPusher(
|
||||||
|
email = email,
|
||||||
|
lang = localeProvider.current().language,
|
||||||
|
emailBranding = appName,
|
||||||
|
appDisplayName = appName,
|
||||||
|
deviceDisplayName = currentSession.sessionParams.deviceId ?: "MOBILE"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun unregisterEmailPusher(email: String) {
|
||||||
|
val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
|
||||||
|
currentSession.removeEmailPusher(email)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun unregisterPusher(context: Context, pushKey: String) {
|
suspend fun unregisterPusher(context: Context, pushKey: String) {
|
||||||
val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
|
val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
|
||||||
currentSession.removeHttpPusher(pushKey, getPusherAppId(context))
|
currentSession.removeHttpPusher(pushKey, getPusherAppId(context))
|
||||||
|
|
|
@ -40,12 +40,14 @@ import im.vector.app.features.notifications.NotifiableEventResolver
|
||||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||||
import im.vector.app.features.notifications.NotificationUtils
|
import im.vector.app.features.notifications.NotificationUtils
|
||||||
import im.vector.app.features.settings.BackgroundSyncMode
|
import im.vector.app.features.settings.BackgroundSyncMode
|
||||||
|
import im.vector.app.features.settings.VectorDataStore
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.unifiedpush.android.connector.MessagingReceiver
|
import org.unifiedpush.android.connector.MessagingReceiver
|
||||||
import org.unifiedpush.android.connector.MessagingReceiverHandler
|
import org.unifiedpush.android.connector.MessagingReceiverHandler
|
||||||
|
@ -69,6 +71,8 @@ data class Counts(
|
||||||
val unread: Int = 0
|
val unread: Int = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UnifiedPush handler.
|
* UnifiedPush handler.
|
||||||
*/
|
*/
|
||||||
|
@ -79,6 +83,7 @@ val upHandler = object: MessagingReceiverHandler {
|
||||||
private lateinit var pusherManager: PushersManager
|
private lateinit var pusherManager: PushersManager
|
||||||
private lateinit var activeSessionHolder: ActiveSessionHolder
|
private lateinit var activeSessionHolder: ActiveSessionHolder
|
||||||
private lateinit var vectorPreferences: VectorPreferences
|
private lateinit var vectorPreferences: VectorPreferences
|
||||||
|
private lateinit var vectorDataStore: VectorDataStore
|
||||||
private lateinit var wifiDetector: WifiDetector
|
private lateinit var wifiDetector: WifiDetector
|
||||||
|
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
private val coroutineScope = CoroutineScope(SupervisorJob())
|
||||||
|
@ -95,6 +100,7 @@ val upHandler = object: MessagingReceiverHandler {
|
||||||
pusherManager = pusherManager()
|
pusherManager = pusherManager()
|
||||||
activeSessionHolder = activeSessionHolder()
|
activeSessionHolder = activeSessionHolder()
|
||||||
vectorPreferences = vectorPreferences()
|
vectorPreferences = vectorPreferences()
|
||||||
|
vectorDataStore = vectorDataStore()
|
||||||
wifiDetector = wifiDetector()
|
wifiDetector = wifiDetector()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,9 +114,14 @@ val upHandler = object: MessagingReceiverHandler {
|
||||||
override fun onMessage(context: Context?, message: String, instance: String) {
|
override fun onMessage(context: Context?, message: String, instance: String) {
|
||||||
initVar(context!!)
|
initVar(context!!)
|
||||||
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
||||||
Timber.d("## onMessageReceived() %s", message)
|
Timber.tag(loggerTag.value).d("## onMessageReceived() %s", message)
|
||||||
|
} else {
|
||||||
|
Timber.tag(loggerTag.value).d("## onMessageReceived()")
|
||||||
|
}
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
vectorDataStore.incrementPushCounter()
|
||||||
}
|
}
|
||||||
Timber.d("## onMessage() received")
|
|
||||||
|
|
||||||
val moshi: Moshi = Moshi.Builder()
|
val moshi: Moshi = Moshi.Builder()
|
||||||
.add(KotlinJsonAdapterFactory())
|
.add(KotlinJsonAdapterFactory())
|
||||||
|
@ -127,6 +138,7 @@ val upHandler = object: MessagingReceiverHandler {
|
||||||
notification.unread = notification.counts.unread
|
notification.unread = notification.counts.unread
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Diagnostic Push
|
// Diagnostic Push
|
||||||
if (notification.eventId == PushersManager.TEST_EVENT_ID) {
|
if (notification.eventId == PushersManager.TEST_EVENT_ID) {
|
||||||
val intent = Intent(NotificationUtils.PUSH_ACTION)
|
val intent = Intent(NotificationUtils.PUSH_ACTION)
|
||||||
|
@ -135,14 +147,14 @@ val upHandler = object: MessagingReceiverHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!vectorPreferences.areNotificationEnabledForDevice()) {
|
if (!vectorPreferences.areNotificationEnabledForDevice()) {
|
||||||
Timber.i("Notification are disabled for this device")
|
Timber.tag(loggerTag.value).i("Notification are disabled for this device")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mUIHandler.post {
|
mUIHandler.post {
|
||||||
if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||||
// we are in foreground, let the sync do the things?
|
// we are in foreground, let the sync do the things?
|
||||||
Timber.d("PUSH received in a foreground state, ignore")
|
Timber.tag(loggerTag.value).d("PUSH received in a foreground state, ignore")
|
||||||
} else {
|
} else {
|
||||||
onMessageReceivedInternal(context, notification)
|
onMessageReceivedInternal(context, notification)
|
||||||
}
|
}
|
||||||
|
@ -157,7 +169,7 @@ val upHandler = object: MessagingReceiverHandler {
|
||||||
*/
|
*/
|
||||||
override fun onNewEndpoint(context: Context?, endpoint: String, instance: String) {
|
override fun onNewEndpoint(context: Context?, endpoint: String, instance: String) {
|
||||||
initVar(context!!)
|
initVar(context!!)
|
||||||
Timber.i("onNewEndpoint: adding $endpoint")
|
Timber.tag(loggerTag.value).i("onNewEndpoint: adding $endpoint")
|
||||||
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
|
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
|
||||||
val gateway = UPHelper.customOrDefaultGateway(context, endpoint)
|
val gateway = UPHelper.customOrDefaultGateway(context, endpoint)
|
||||||
if (UPHelper.getUpEndpoint(context) != endpoint
|
if (UPHelper.getUpEndpoint(context) != endpoint
|
||||||
|
@ -166,7 +178,7 @@ val upHandler = object: MessagingReceiverHandler {
|
||||||
UPHelper.storeUpEndpoint(context, endpoint)
|
UPHelper.storeUpEndpoint(context, endpoint)
|
||||||
pusherManager.registerPusher(context, endpoint, gateway)
|
pusherManager.registerPusher(context, endpoint, gateway)
|
||||||
} else {
|
} else {
|
||||||
Timber.i("onNewEndpoint: skipped")
|
Timber.tag(loggerTag.value).i("onNewEndpoint: skipped")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!UPHelper.allowBackgroundSync(context)) {
|
if (!UPHelper.allowBackgroundSync(context)) {
|
||||||
|
@ -184,7 +196,7 @@ val upHandler = object: MessagingReceiverHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUnregistered(context: Context?, instance: String) {
|
override fun onUnregistered(context: Context?, instance: String) {
|
||||||
Timber.d("Unifiedpush: Unregistered")
|
Timber.tag(loggerTag.value).d("Unifiedpush: Unregistered")
|
||||||
initVar(context!!)
|
initVar(context!!)
|
||||||
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY
|
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY
|
||||||
vectorPreferences.setFdroidSyncBackgroundMode(mode)
|
vectorPreferences.setFdroidSyncBackgroundMode(mode)
|
||||||
|
@ -192,7 +204,7 @@ val upHandler = object: MessagingReceiverHandler {
|
||||||
try {
|
try {
|
||||||
pusherManager.unregisterPusher(context, UPHelper.getUpEndpoint(context)!!)
|
pusherManager.unregisterPusher(context, UPHelper.getUpEndpoint(context)!!)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.d("Probably unregistering a non existant pusher")
|
Timber.tag(loggerTag.value).d("Probably unregistering a non existant pusher")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,7 +217,9 @@ val upHandler = object: MessagingReceiverHandler {
|
||||||
private fun onMessageReceivedInternal(context: Context, notification: Notification) {
|
private fun onMessageReceivedInternal(context: Context, notification: Notification) {
|
||||||
try {
|
try {
|
||||||
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
||||||
Timber.d("## onMessageReceivedInternal() : $notification")
|
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $notification")
|
||||||
|
} else {
|
||||||
|
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the badge counter
|
// update the badge counter
|
||||||
|
@ -214,21 +228,21 @@ val upHandler = object: MessagingReceiverHandler {
|
||||||
val session = activeSessionHolder.getSafeActiveSession()
|
val session = activeSessionHolder.getSafeActiveSession()
|
||||||
|
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
Timber.w("## Can't sync from push, no current session")
|
Timber.tag(loggerTag.value).w("## Can't sync from push, no current session")
|
||||||
} else {
|
} else {
|
||||||
if (isEventAlreadyKnown(notification.eventId, notification.roomId)) {
|
if (isEventAlreadyKnown(notification.eventId, notification.roomId)) {
|
||||||
Timber.d("Ignoring push, event already known")
|
Timber.tag(loggerTag.value).d("Ignoring push, event already known")
|
||||||
} else {
|
} else {
|
||||||
// Try to get the Event content faster
|
// Try to get the Event content faster
|
||||||
Timber.d("Requesting event in fast lane")
|
Timber.tag(loggerTag.value).d("Requesting event in fast lane")
|
||||||
getEventFastLane(session, notification.roomId, notification.eventId)
|
getEventFastLane(session, notification.roomId, notification.eventId)
|
||||||
|
|
||||||
Timber.d("Requesting background sync")
|
Timber.tag(loggerTag.value).d("Requesting background sync")
|
||||||
session.requireBackgroundSync()
|
session.requireBackgroundSync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "## onMessageReceivedInternal() failed")
|
Timber.tag(loggerTag.value).e(e, "## onMessageReceivedInternal() failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,18 +256,18 @@ val upHandler = object: MessagingReceiverHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wifiDetector.isConnectedToWifi().not()) {
|
if (wifiDetector.isConnectedToWifi().not()) {
|
||||||
Timber.d("No WiFi network, do not get Event")
|
Timber.tag(loggerTag.value).d("No WiFi network, do not get Event")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
Timber.d("Fast lane: start request")
|
Timber.tag(loggerTag.value).d("Fast lane: start request")
|
||||||
val event = tryOrNull { session.getEvent(roomId, eventId) } ?: return@launch
|
val event = tryOrNull { session.getEvent(roomId, eventId) } ?: return@launch
|
||||||
|
|
||||||
val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event)
|
val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event)
|
||||||
|
|
||||||
resolvedEvent
|
resolvedEvent
|
||||||
?.also { Timber.d("Fast lane: notify drawer") }
|
?.also { Timber.tag(loggerTag.value).d("Fast lane: notify drawer") }
|
||||||
?.let {
|
?.let {
|
||||||
it.isPushGatewayEvent = true
|
it.isPushGatewayEvent = true
|
||||||
notificationDrawerManager.onNotifiableEventReceived(it)
|
notificationDrawerManager.onNotifiableEventReceived(it)
|
||||||
|
@ -271,7 +285,7 @@ val upHandler = object: MessagingReceiverHandler {
|
||||||
val room = session.getRoom(roomId) ?: return false
|
val room = session.getRoom(roomId) ?: return false
|
||||||
return room.getTimeLineEvent(eventId) != null
|
return room.getTimeLineEvent(eventId) != null
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined")
|
Timber.tag(loggerTag.value).e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -50,7 +50,7 @@ private val loggerTag = LoggerTag("CallService", LoggerTag.VOIP)
|
||||||
class CallService : VectorService() {
|
class CallService : VectorService() {
|
||||||
|
|
||||||
private val connections = mutableMapOf<String, CallConnection>()
|
private val connections = mutableMapOf<String, CallConnection>()
|
||||||
private val knownCalls = mutableSetOf<CallInformation>()
|
private val knownCalls = mutableMapOf<String, CallInformation>()
|
||||||
private val connectedCallIds = mutableSetOf<String>()
|
private val connectedCallIds = mutableSetOf<String>()
|
||||||
|
|
||||||
private lateinit var notificationManager: NotificationManagerCompat
|
private lateinit var notificationManager: NotificationManagerCompat
|
||||||
|
@ -190,7 +190,7 @@ class CallService : VectorService() {
|
||||||
} else {
|
} else {
|
||||||
notificationManager.notify(callId.hashCode(), notification)
|
notificationManager.notify(callId.hashCode(), notification)
|
||||||
}
|
}
|
||||||
knownCalls.add(callInformation)
|
knownCalls[callId] = callInformation
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleCallTerminated(intent: Intent) {
|
private fun handleCallTerminated(intent: Intent) {
|
||||||
|
@ -198,20 +198,22 @@ class CallService : VectorService() {
|
||||||
val endCallReason = intent.getSerializableExtra(EXTRA_END_CALL_REASON) as EndCallReason
|
val endCallReason = intent.getSerializableExtra(EXTRA_END_CALL_REASON) as EndCallReason
|
||||||
val rejected = intent.getBooleanExtra(EXTRA_END_CALL_REJECTED, false)
|
val rejected = intent.getBooleanExtra(EXTRA_END_CALL_REJECTED, false)
|
||||||
alertManager.cancelAlert(callId)
|
alertManager.cancelAlert(callId)
|
||||||
val terminatedCall = knownCalls.firstOrNull { it.callId == callId }
|
val terminatedCall = knownCalls.remove(callId)
|
||||||
if (terminatedCall == null) {
|
if (terminatedCall == null) {
|
||||||
Timber.tag(loggerTag.value).v("Call terminated for unknown call $callId$")
|
Timber.tag(loggerTag.value).v("Call terminated for unknown call $callId")
|
||||||
handleUnexpectedState(callId)
|
handleUnexpectedState(callId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
knownCalls.remove(terminatedCall)
|
val notification = notificationUtils.buildCallEndedNotification(false)
|
||||||
|
val notificationId = callId.hashCode()
|
||||||
|
startForeground(notificationId, notification)
|
||||||
if (knownCalls.isEmpty()) {
|
if (knownCalls.isEmpty()) {
|
||||||
|
Timber.tag(loggerTag.value).v("No more call, stop the service")
|
||||||
|
stopForeground(true)
|
||||||
mediaSession?.isActive = false
|
mediaSession?.isActive = false
|
||||||
myStopSelf()
|
myStopSelf()
|
||||||
}
|
}
|
||||||
val wasConnected = connectedCallIds.remove(callId)
|
val wasConnected = connectedCallIds.remove(callId)
|
||||||
val notification = notificationUtils.buildCallEndedNotification(terminatedCall.isVideoCall)
|
|
||||||
notificationManager.notify(callId.hashCode(), notification)
|
|
||||||
if (!wasConnected && !terminatedCall.isOutgoing && !rejected && endCallReason != EndCallReason.ANSWERED_ELSEWHERE) {
|
if (!wasConnected && !terminatedCall.isOutgoing && !rejected && endCallReason != EndCallReason.ANSWERED_ELSEWHERE) {
|
||||||
val missedCallNotification = notificationUtils.buildCallMissedNotification(terminatedCall)
|
val missedCallNotification = notificationUtils.buildCallMissedNotification(terminatedCall)
|
||||||
notificationManager.notify(MISSED_CALL_TAG, terminatedCall.nativeRoomId.hashCode(), missedCallNotification)
|
notificationManager.notify(MISSED_CALL_TAG, terminatedCall.nativeRoomId.hashCode(), missedCallNotification)
|
||||||
|
@ -243,7 +245,7 @@ class CallService : VectorService() {
|
||||||
} else {
|
} else {
|
||||||
notificationManager.notify(callId.hashCode(), notification)
|
notificationManager.notify(callId.hashCode(), notification)
|
||||||
}
|
}
|
||||||
knownCalls.add(callInformation)
|
knownCalls[callId] = callInformation
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -267,18 +269,19 @@ class CallService : VectorService() {
|
||||||
} else {
|
} else {
|
||||||
notificationManager.notify(callId.hashCode(), notification)
|
notificationManager.notify(callId.hashCode(), notification)
|
||||||
}
|
}
|
||||||
knownCalls.add(callInformation)
|
knownCalls[callId] = callInformation
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUnexpectedState(callId: String?) {
|
private fun handleUnexpectedState(callId: String?) {
|
||||||
Timber.tag(loggerTag.value).v("Fallback to clear everything")
|
Timber.tag(loggerTag.value).v("Fallback to clear everything")
|
||||||
callRingPlayerIncoming?.stop()
|
callRingPlayerIncoming?.stop()
|
||||||
callRingPlayerOutgoing?.stop()
|
callRingPlayerOutgoing?.stop()
|
||||||
if (callId != null) {
|
|
||||||
notificationManager.cancel(callId.hashCode())
|
|
||||||
}
|
|
||||||
val notification = notificationUtils.buildCallEndedNotification(false)
|
val notification = notificationUtils.buildCallEndedNotification(false)
|
||||||
|
if (callId != null) {
|
||||||
|
startForeground(callId.hashCode(), notification)
|
||||||
|
} else {
|
||||||
startForeground(DEFAULT_NOTIFICATION_ID, notification)
|
startForeground(DEFAULT_NOTIFICATION_ID, notification)
|
||||||
|
}
|
||||||
if (knownCalls.isEmpty()) {
|
if (knownCalls.isEmpty()) {
|
||||||
mediaSession?.isActive = false
|
mediaSession?.isActive = false
|
||||||
myStopSelf()
|
myStopSelf()
|
||||||
|
@ -371,7 +374,7 @@ class CallService : VectorService() {
|
||||||
putExtra(EXTRA_END_CALL_REASON, endCallReason)
|
putExtra(EXTRA_END_CALL_REASON, endCallReason)
|
||||||
putExtra(EXTRA_END_CALL_REJECTED, rejected)
|
putExtra(EXTRA_END_CALL_REJECTED, rejected)
|
||||||
}
|
}
|
||||||
ContextCompat.startForegroundService(context, intent)
|
context.startService(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import androidx.core.view.isGone
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.setDrawableOrHide
|
||||||
import im.vector.app.core.extensions.setTextOrHide
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.databinding.ViewBottomSheetActionButtonBinding
|
import im.vector.app.databinding.ViewBottomSheetActionButtonBinding
|
||||||
|
@ -81,7 +82,7 @@ class BottomSheetActionButton @JvmOverloads constructor(
|
||||||
var rightIcon: Drawable? = null
|
var rightIcon: Drawable? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
views.bottomSheetActionIcon.setImageDrawable(value)
|
views.bottomSheetActionIcon.setDrawableOrHide(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tint: Int? = null
|
var tint: Int? = null
|
||||||
|
@ -96,6 +97,12 @@ class BottomSheetActionButton @JvmOverloads constructor(
|
||||||
value?.let { views.bottomSheetActionTitle.setTextColor(it) }
|
value?.let { views.bottomSheetActionTitle.setTextColor(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isBetaAction: Boolean? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
views.bottomSheetActionBeta.isVisible = field ?: false
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
inflate(context, R.layout.view_bottom_sheet_action_button, this)
|
inflate(context, R.layout.view_bottom_sheet_action_button, this)
|
||||||
views = ViewBottomSheetActionButtonBinding.bind(this)
|
views = ViewBottomSheetActionButtonBinding.bind(this)
|
||||||
|
@ -110,6 +117,8 @@ class BottomSheetActionButton @JvmOverloads constructor(
|
||||||
|
|
||||||
tint = getColor(R.styleable.BottomSheetActionButton_tint, ThemeUtils.getColor(context, android.R.attr.textColor))
|
tint = getColor(R.styleable.BottomSheetActionButton_tint, ThemeUtils.getColor(context, android.R.attr.textColor))
|
||||||
titleTextColor = getColor(R.styleable.BottomSheetActionButton_titleTextColor, ThemeUtils.getColor(context, R.attr.colorPrimary))
|
titleTextColor = getColor(R.styleable.BottomSheetActionButton_titleTextColor, ThemeUtils.getColor(context, R.attr.colorPrimary))
|
||||||
|
|
||||||
|
isBetaAction = getBoolean(R.styleable.BottomSheetActionButton_betaAction, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ class CurrentCallsViewPresenter {
|
||||||
this.currentCall = currentCall
|
this.currentCall = currentCall
|
||||||
this.currentCall?.addListener(tickListener)
|
this.currentCall?.addListener(tickListener)
|
||||||
this.calls = calls
|
this.calls = calls
|
||||||
val hasActiveCall = currentCall != null
|
val hasActiveCall = calls.isNotEmpty()
|
||||||
currentCallsView?.isVisible = hasActiveCall
|
currentCallsView?.isVisible = hasActiveCall
|
||||||
currentCallsView?.render(calls, currentCall?.formattedDuration() ?: "")
|
currentCallsView?.render(calls, currentCall?.formattedDuration() ?: "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.app.core.ui.views
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.databinding.ViewFailedMessagesWarningBinding
|
import im.vector.app.databinding.ViewFailedMessagesWarningBinding
|
||||||
|
|
||||||
|
@ -49,8 +48,4 @@ class FailedMessagesWarningView @JvmOverloads constructor(
|
||||||
views.failedMessagesDeleteAllButton.setOnClickListener { callback?.onDeleteAllClicked() }
|
views.failedMessagesDeleteAllButton.setOnClickListener { callback?.onDeleteAllClicked() }
|
||||||
views.failedMessagesRetryButton.setOnClickListener { callback?.onRetryClicked() }
|
views.failedMessagesRetryButton.setOnClickListener { callback?.onRetryClicked() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun render(hasFailedMessages: Boolean) {
|
|
||||||
isVisible = hasFailedMessages
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.content.Context
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import im.vector.app.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a web view above the current activity.
|
* Open a web view above the current activity.
|
||||||
|
@ -38,3 +39,14 @@ fun Context.displayInWebView(url: String) {
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Context.showIdentityServerConsentDialog(configuredIdentityServer: String?, consentCallBack: (() -> Unit)) {
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.identity_server_consent_dialog_title)
|
||||||
|
.setMessage(getString(R.string.identity_server_consent_dialog_content, configuredIdentityServer ?: ""))
|
||||||
|
.setPositiveButton(R.string.yes) { _, _ ->
|
||||||
|
consentCallBack.invoke()
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.no, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,11 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import im.vector.app.features.autocomplete.AutocompleteClickListener
|
import im.vector.app.features.autocomplete.AutocompleteClickListener
|
||||||
import im.vector.app.features.autocomplete.RecyclerViewPresenter
|
import im.vector.app.features.autocomplete.RecyclerViewPresenter
|
||||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AutocompleteEmojiPresenter @Inject constructor(context: Context,
|
class AutocompleteEmojiPresenter @Inject constructor(context: Context,
|
||||||
|
@ -28,11 +33,14 @@ class AutocompleteEmojiPresenter @Inject constructor(context: Context,
|
||||||
private val controller: AutocompleteEmojiController) :
|
private val controller: AutocompleteEmojiController) :
|
||||||
RecyclerViewPresenter<String>(context), AutocompleteClickListener<String> {
|
RecyclerViewPresenter<String>(context), AutocompleteClickListener<String> {
|
||||||
|
|
||||||
|
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
controller.listener = this
|
controller.listener = this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
|
coroutineScope.coroutineContext.cancelChildren()
|
||||||
controller.listener = null
|
controller.listener = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +53,7 @@ class AutocompleteEmojiPresenter @Inject constructor(context: Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQuery(query: CharSequence?) {
|
override fun onQuery(query: CharSequence?) {
|
||||||
|
coroutineScope.launch {
|
||||||
val data = if (query.isNullOrBlank()) {
|
val data = if (query.isNullOrBlank()) {
|
||||||
// Return common emojis
|
// Return common emojis
|
||||||
emojiDataSource.getQuickReactions()
|
emojiDataSource.getQuickReactions()
|
||||||
|
@ -54,3 +63,4 @@ class AutocompleteEmojiPresenter @Inject constructor(context: Context,
|
||||||
controller.setData(data)
|
controller.setData(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,8 +19,8 @@ package im.vector.app.features.autocomplete.member
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.features.autocomplete.AutocompleteClickListener
|
import im.vector.app.features.autocomplete.AutocompleteClickListener
|
||||||
import im.vector.app.features.autocomplete.RecyclerViewPresenter
|
import im.vector.app.features.autocomplete.RecyclerViewPresenter
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
|
@ -35,7 +35,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
|
||||||
private val controller: AutocompleteMemberController
|
private val controller: AutocompleteMemberController
|
||||||
) : RecyclerViewPresenter<RoomMemberSummary>(context), AutocompleteClickListener<RoomMemberSummary> {
|
) : RecyclerViewPresenter<RoomMemberSummary>(context), AutocompleteClickListener<RoomMemberSummary> {
|
||||||
|
|
||||||
private val room = session.getRoom(roomId)!!
|
private val room by lazy { session.getRoom(roomId)!! }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
controller.listener = this
|
controller.listener = this
|
||||||
|
|
|
@ -41,7 +41,7 @@ class SharedKnownCallsViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
|
private val callManagerListener = object : WebRtcCallManager.Listener {
|
||||||
override fun onCurrentCallChange(call: WebRtcCall?) {
|
override fun onCurrentCallChange(call: WebRtcCall?) {
|
||||||
val knownCalls = callManager.getCalls()
|
val knownCalls = callManager.getCalls()
|
||||||
liveKnownCalls.postValue(knownCalls)
|
liveKnownCalls.postValue(knownCalls)
|
||||||
|
@ -50,12 +50,17 @@ class SharedKnownCallsViewModel @Inject constructor(
|
||||||
it.addListener(callListener)
|
it.addListener(callListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCallEnded(callId: String) {
|
||||||
|
val knownCalls = callManager.getCalls()
|
||||||
|
liveKnownCalls.postValue(knownCalls)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val knownCalls = callManager.getCalls()
|
val knownCalls = callManager.getCalls()
|
||||||
liveKnownCalls.postValue(knownCalls)
|
liveKnownCalls.postValue(knownCalls)
|
||||||
callManager.addCurrentCallListener(currentCallListener)
|
callManager.addListener(callManagerListener)
|
||||||
knownCalls.forEach {
|
knownCalls.forEach {
|
||||||
it.addListener(callListener)
|
it.addListener(callListener)
|
||||||
}
|
}
|
||||||
|
@ -65,7 +70,7 @@ class SharedKnownCallsViewModel @Inject constructor(
|
||||||
callManager.getCalls().forEach {
|
callManager.getCalls().forEach {
|
||||||
it.removeListener(callListener)
|
it.removeListener(callListener)
|
||||||
}
|
}
|
||||||
callManager.removeCurrentCallListener(currentCallListener)
|
callManager.removeListener(callManagerListener)
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,15 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||||
} ?: VectorCallViewState.TransfereeState.UnknownTransferee
|
} ?: VectorCallViewState.TransfereeState.UnknownTransferee
|
||||||
}
|
}
|
||||||
|
|
||||||
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
|
private val callManagerListener = object : WebRtcCallManager.Listener {
|
||||||
|
|
||||||
|
override fun onCallEnded(callId: String) {
|
||||||
|
withState { state ->
|
||||||
|
if (state.otherKnownCallInfo?.callId == callId) {
|
||||||
|
setState { copy(otherKnownCallInfo = null) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCurrentCallChange(call: WebRtcCall?) {
|
override fun onCurrentCallChange(call: WebRtcCall?) {
|
||||||
if (call != null) {
|
if (call != null) {
|
||||||
|
@ -159,9 +167,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateOtherKnownCall(currentCall: WebRtcCall) {
|
private fun updateOtherKnownCall(currentCall: WebRtcCall) {
|
||||||
val otherCall = callManager.getCalls().firstOrNull {
|
val otherCall = getOtherKnownCall(currentCall)
|
||||||
it.callId != currentCall.callId && it.mxCall.state is CallState.Connected
|
|
||||||
}
|
|
||||||
setState {
|
setState {
|
||||||
if (otherCall == null) {
|
if (otherCall == null) {
|
||||||
copy(otherKnownCallInfo = null)
|
copy(otherKnownCallInfo = null)
|
||||||
|
@ -171,6 +177,12 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getOtherKnownCall(currentCall: WebRtcCall): WebRtcCall? {
|
||||||
|
return callManager.getCalls().firstOrNull {
|
||||||
|
it.callId != currentCall.callId && it.mxCall.state is CallState.Connected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setupCallWithCurrentState()
|
setupCallWithCurrentState()
|
||||||
}
|
}
|
||||||
|
@ -184,7 +196,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
call = webRtcCall
|
call = webRtcCall
|
||||||
callManager.addCurrentCallListener(currentCallListener)
|
callManager.addListener(callManagerListener)
|
||||||
webRtcCall.addListener(callListener)
|
webRtcCall.addListener(callListener)
|
||||||
val currentSoundDevice = callManager.audioManager.selectedDevice
|
val currentSoundDevice = callManager.audioManager.selectedDevice
|
||||||
if (currentSoundDevice == CallAudioManager.Device.Phone) {
|
if (currentSoundDevice == CallAudioManager.Device.Phone) {
|
||||||
|
@ -230,7 +242,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
callManager.removeCurrentCallListener(currentCallListener)
|
callManager.removeListener(callManagerListener)
|
||||||
call?.removeListener(callListener)
|
call?.removeListener(callListener)
|
||||||
call = null
|
call = null
|
||||||
proximityManager.stop()
|
proximityManager.stop()
|
||||||
|
|
|
@ -28,20 +28,21 @@ import com.facebook.react.bridge.JavaOnlyMap
|
||||||
import org.jitsi.meet.sdk.BroadcastEmitter
|
import org.jitsi.meet.sdk.BroadcastEmitter
|
||||||
import org.jitsi.meet.sdk.BroadcastEvent
|
import org.jitsi.meet.sdk.BroadcastEvent
|
||||||
import org.jitsi.meet.sdk.JitsiMeet
|
import org.jitsi.meet.sdk.JitsiMeet
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import timber.log.Timber
|
||||||
|
|
||||||
private const val CONFERENCE_URL_DATA_KEY = "url"
|
private const val CONFERENCE_URL_DATA_KEY = "url"
|
||||||
|
|
||||||
fun BroadcastEvent.extractConferenceUrl(): String? {
|
sealed class ConferenceEvent(open val data: Map<String, Any>) {
|
||||||
return when (type) {
|
data class Terminated(override val data: Map<String, Any>) : ConferenceEvent(data)
|
||||||
BroadcastEvent.Type.CONFERENCE_TERMINATED,
|
data class WillJoin(override val data: Map<String, Any>) : ConferenceEvent(data)
|
||||||
BroadcastEvent.Type.CONFERENCE_WILL_JOIN,
|
data class Joined(override val data: Map<String, Any>) : ConferenceEvent(data)
|
||||||
BroadcastEvent.Type.CONFERENCE_JOINED -> data[CONFERENCE_URL_DATA_KEY] as? String
|
|
||||||
else -> null
|
fun extractConferenceUrl(): String? {
|
||||||
|
return data[CONFERENCE_URL_DATA_KEY] as? String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class JitsiBroadcastEmitter(private val context: Context) {
|
class ConferenceEventEmitter(private val context: Context) {
|
||||||
|
|
||||||
fun emitConferenceEnded() {
|
fun emitConferenceEnded() {
|
||||||
val broadcastEventData = JavaOnlyMap.of(CONFERENCE_URL_DATA_KEY, JitsiMeet.getCurrentConference())
|
val broadcastEventData = JavaOnlyMap.of(CONFERENCE_URL_DATA_KEY, JitsiMeet.getCurrentConference())
|
||||||
|
@ -49,8 +50,9 @@ class JitsiBroadcastEmitter(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class JitsiBroadcastEventObserver(private val context: Context,
|
class ConferenceEventObserver(private val context: Context,
|
||||||
private val onBroadcastEvent: (BroadcastEvent) -> Unit) : LifecycleObserver {
|
private val onBroadcastEvent: (ConferenceEvent) -> Unit)
|
||||||
|
: LifecycleObserver {
|
||||||
|
|
||||||
// See https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-android-sdk#listening-for-broadcasted-events
|
// See https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-android-sdk#listening-for-broadcasted-events
|
||||||
private val broadcastReceiver = object : BroadcastReceiver() {
|
private val broadcastReceiver = object : BroadcastReceiver() {
|
||||||
|
@ -61,8 +63,10 @@ class JitsiBroadcastEventObserver(private val context: Context,
|
||||||
|
|
||||||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||||
fun unregisterForBroadcastMessages() {
|
fun unregisterForBroadcastMessages() {
|
||||||
tryOrNull("Unable to unregister receiver") {
|
try {
|
||||||
LocalBroadcastManager.getInstance(context).unregisterReceiver(broadcastReceiver)
|
LocalBroadcastManager.getInstance(context).unregisterReceiver(broadcastReceiver)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
Timber.v("Unable to unregister receiver")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,13 +76,23 @@ class JitsiBroadcastEventObserver(private val context: Context,
|
||||||
for (type in BroadcastEvent.Type.values()) {
|
for (type in BroadcastEvent.Type.values()) {
|
||||||
intentFilter.addAction(type.action)
|
intentFilter.addAction(type.action)
|
||||||
}
|
}
|
||||||
tryOrNull("Unable to register receiver") {
|
try {
|
||||||
LocalBroadcastManager.getInstance(context).registerReceiver(broadcastReceiver, intentFilter)
|
LocalBroadcastManager.getInstance(context).registerReceiver(broadcastReceiver, intentFilter)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
Timber.v("Unable to register receiver")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onBroadcastReceived(intent: Intent) {
|
private fun onBroadcastReceived(intent: Intent) {
|
||||||
val event = BroadcastEvent(intent)
|
val event = BroadcastEvent(intent)
|
||||||
onBroadcastEvent(event)
|
val conferenceEvent = when (event.type) {
|
||||||
|
BroadcastEvent.Type.CONFERENCE_JOINED -> ConferenceEvent.Joined(event.data)
|
||||||
|
BroadcastEvent.Type.CONFERENCE_TERMINATED -> ConferenceEvent.Terminated(event.data)
|
||||||
|
BroadcastEvent.Type.CONFERENCE_WILL_JOIN -> ConferenceEvent.WillJoin(event.data)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (conferenceEvent != null) {
|
||||||
|
onBroadcastEvent(conferenceEvent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue