Merge branch 'develop' into feature/ons/voice_message

This commit is contained in:
Benoit Marty 2021-07-09 22:19:50 +02:00
commit e391a1371c
179 changed files with 2297 additions and 533 deletions

84
.github/ISSUE_TEMPLATE/release.md vendored Normal file
View file

@ -0,0 +1,84 @@
---
name: Release
about: Checklist for each release. To be used by the core team only.
title: "[Release] Element Android v"
labels: "\U0001F680 Release"
assignees: bmarty
---
For the example, we are releasing the version 1.1.10
### Before the release
- [ ] Weblate sync, fix lint issue if any (in a dedicated PR)
- [ ] Check the update of the store descriptions (using Google Translate if necessary) to ensure that the changes are acceptable to be published to the stores.
### Do the release
- [ ] Create release with gitflow, branch name `release/1.1.10`
- [ ] Run the script `./tools/release/pushPlayStoreMetaData.sh`. You can check in the GooglePlay console the Activity log to check the effect.
- [ ] Run `./tools/import_emojis.py` and commit the change if any.
- [ ] Run `./tools/import_sas_strings.py` and commit the change if any. If there is no change since a while, ping Travis
- [ ] Check the crashes from the PlayStore
- [ ] Check the rageshake with the current dev version. For instance https://github.com/matrix-org/element-android-rageshakes/labels/1.1.10-dev
- [ ] Run the integration test, and especially `UiAllScreensSanityTest.allScreensTest()`
- [ ] Create an account on matrix.org
- [ ] Run towncrier: `./towncrier --version v1.1.10` (add `--draft` for a preview)
- [ ] Add file for fastlane under ./fastlane/metadata/android/en-US/changelogs
- [ ] Push the branch and start a draft PR (will not be merged), to check that the CI is happy with all the changes.
- [ ] Finish release with gitflow, delete the draft PR
- [ ] Push `main` and the new tag `v1.1.10` to origin
- [ ] Checkout `develop`
- [ ] Increase version in `./vector/build.gradle`
- [ ] Commit and push `develop`
- [ ] Wait for [Buildkite](https://buildkite.com/matrix-dot-org/element-android/builds?branch=main) to build the `main` branch.
- [ ] Run the script `~/scripts/releaseElement.sh`. It will download the APKs from Buildkite check them and sign them.
- [ ] Install the APK on your phone to check that the upgrade went well (no init sync, etc.)
- [ ] Create a new beta release on the GooglePlay console and upload the 4 signed Apks.
- [ ] Check that the version codes are correct
- [ ] Copy the fastlane change to the GooglePlay console in the section en-GB.
- [ ] Push to beta release to 100% of the users
- [ ] Create the release on gitHub [from the tag](https://github.com/vector-im/element-android/tags), copy paste the block from the file CHANGES.md
- [ ] Add the 4 signed APKs to the GitHub release
- [ ] Ping the Android Internal room
- [ ] Add an entry in the internal diary
### Once Live on PlayStore
- [ ] Ping the Android public room and update its topic
### After at least 2 days
- [ ] Check the [rageshakes](https://github.com/matrix-org/element-android-rageshakes/issues)
- [ ] Check the crash reports on the GooglePlay console
- [ ] Check the Android Element room for any reported issues on the new version
- [ ] If all is OK, push to production and notify Markus (Bubu) to release the F-Droid version
- [ ] Ping the Android public room and update its topic with the new available version
### Android SDK2
- [ ] Checkout the `main` branch on Element Android project
#### On the SDK2 project
https://github.com/matrix-org/matrix-android-sdk2
- [ ] Create a release with GitFlow
- [ ] Update the files `./build.gradle` and `./gradle/gradle-wrapper.properties` manually, to use the latest version for the dependency. You can get inspired by the same files on Element Android project.
- [ ] Run the script `./tools/import_from_element.sh`
- [ ] Update the version in `./matrix-sdk-android/build.gradle` and let the script finish to build the library
- [ ] Update the file `CHANGES.md`
- [ ] Finish the release using GitFlow
- [ ] Create the release on GitHub from [the tag](https://github.com/matrix-org/matrix-android-sdk2/tags)
- [ ] Upload the AAR on the GitHub release
### 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
- [ ] Build and run the sample, you may have to fix some API break
- [ ] Commit and push directly on `main`
<!-- Note: some scripts are not public because they contain some private keys -->

View file

@ -38,6 +38,7 @@
<w>unpublish</w> <w>unpublish</w>
<w>unwedging</w> <w>unwedging</w>
<w>vctr</w> <w>vctr</w>
<w>wellknown</w>
</words> </words>
</dictionary> </dictionary>
</component> </component>

View file

@ -1,3 +1,20 @@
Changes in Element 1.1.12 (2021-07-05)
======================================
Features ✨
----------
- Reveal password: use facility from com.google.android.material.textfield.TextInputLayout instead of manual handling. ([#3545](https://github.com/vector-im/element-android/issues/3545))
- Implements new design for Jump to unread and quick fix visibility issues. ([#3547](https://github.com/vector-im/element-android/issues/3547))
Bugfixes 🐛
----------
- Fix some issues with timeline cache invalidation and visibility. ([#3542](https://github.com/vector-im/element-android/issues/3542))
- Fix call invite processed after call is ended because of fastlane mode. ([#3564](https://github.com/vector-im/element-android/issues/3564))
- Fix crash after video call. ([#3577](https://github.com/vector-im/element-android/issues/3577))
- Fix crash out of memory ([#3583](https://github.com/vector-im/element-android/issues/3583))
- CryptoStore migration has to be object to avoid crash ([#3605](https://github.com/vector-im/element-android/issues/3605))
Changes in Element v1.1.11 (2021-06-22) Changes in Element v1.1.11 (2021-06-22)
======================================= =======================================

View file

@ -55,9 +55,9 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.5.0' implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.appcompat:appcompat:1.3.0'
implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.4.0'
} }

View file

@ -12,7 +12,7 @@ buildscript {
} }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.1' classpath 'com.android.tools.build:gradle:4.2.2'
classpath 'com.google.gms:google-services:4.3.8' classpath 'com.google.gms:google-services:4.3.8'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 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'

1
changelog.d/2843.bugfix Normal file
View file

@ -0,0 +1 @@
Perform .well-known request first, even if the entered URL is a valid homeserver base url

1
changelog.d/3273.feature Normal file
View file

@ -0,0 +1 @@
Remove redundant mimetype (vector-im/element-web#2547)

View file

@ -1 +0,0 @@
Reveal password: use facility from com.google.android.material.textfield.TextInputLayout instead of manual handling.

View file

@ -1 +0,0 @@
Implements new design for Jump to unread and quick fix visibility issues.

1
changelog.d/3551.feature Normal file
View file

@ -0,0 +1 @@
Room version capabilities and room upgrade support, better error feedback

View file

@ -1 +0,0 @@
Fix call invite processed after call is ended because of fastlane mode.

1
changelog.d/3572.misc Normal file
View file

@ -0,0 +1 @@
RawService.getWellknown() now takes a domain instead of a matrixId as parameter

View file

@ -1 +0,0 @@
Fix crash after video call.

1
changelog.d/3624.bugfix Normal file
View file

@ -0,0 +1 @@
Use different copy for self verification.

1
changelog.d/3634.bugfix Normal file
View file

@ -0,0 +1 @@
Crash when opening room addresses screen with no internet connection

1
changelog.d/3635.feature Normal file
View file

@ -0,0 +1 @@
Add retry support in room addresses screen

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: beta podpora pro Spaces. Komprimace videa před odesláním.
Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.7

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: vylepšení pro Spaces
Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.8

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: doplněna podpora pro síť gitter.im
Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.9

View file

@ -0,0 +1,2 @@
Main changes in this version: theme and style update and fix a crash after video call
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.12

View file

@ -0,0 +1,2 @@
Principaux changements pour cette version : prise en charge des espaces en bêta. Compression des vidéos avant envoi.
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.7

View file

@ -0,0 +1,2 @@
Principaux changements pour cette version : amélioration des espaces.
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.8

View file

@ -0,0 +1,2 @@
Principaux changements pour cette version : ajout de la prise en charge de gitter.im
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.9

View file

@ -1,30 +1,39 @@
Element est une nouvelle application de messagerie et de collaboration qui : Element est à la fois une messagerie sécurisée et une application de collaboration en équipe, idéale pour les conversations de groupe en télétravail. Cette application utilise le chiffrement de bout en bout. Elle permet de mettre en place des téléconférences vidéo, du partage de fichier et des appels vocaux.
1. Vous permet de préserver votre vie privée <b>Les fonctionnalités dElement incluent :</b>
2. Vous permet de communiquer avec nimporte qui sur réseau Matrix, et plus encore grâce aux intégrations dautres applications telles que Slack ou Discord - Outils de communication en ligne avancés
3. Vous protège de la publicité et de la collecte de données - Communication dentreprise sécurisée par le chiffrement de bout en bout des messages, même pour les travailleurs à distance
4. Vous protège grâce au chiffrement de bout-à-bout et à la signature croisée pour authentifier les autres utilisateurs - Messagerie décentralisée basée sur le framework open source Matrix
- Partage sécurisé de fichiers avec chiffrement des données lors de la gestion de projet
- Conversations vidéo par voix sur IP et partage décran
- Intégration facile avec vos outils de collaboration, de gestion de projet, services de VoIP et autres applications de messagerie
Element est complètement différente des autres applications de messagerie et de collaboration puisque lapplication est décentralisée et open-source. Element est complètement différente des autres applications de messagerie et de collaboration. Elle sappuie sur Matrix, un réseau ouvert de communication décentralisée. Elle permet lauto-hébergement pour que ses utilisateurs restent le plus en contrôle possible de leurs données et leurs messages.
Element vous permet dhéberger vous-même ou de choisir un hôte vous permettant dassurer votre vie privée, la propriété et le contrôle de vos données et de vos conversations. Cela vous donne accès à un réseau ouvert. Vous nêtes donc pas condamné à parler à dautres utilisateurs dElement seulement. Et c'est très sécurisé. <b>Confidentialité et messagerie chiffrée</b>
Element vous protège des publicités non désirées, du minage de données et des prisons dorées. Elle protègé vos données et vos communications vocales grâce au chiffrement de bout en bout et à la vérification de signature croisée entre appareils.
Element peut faire tout ça car elle est basée sur Matrix, le protocole standard pour la communication ouverte et décentralisée. Element vous donne la main sur votre confidentialité en vous permettant de communiquer de manière sécurisée avec tout le réseau Matrix ou dautres applications de communication dentreprise au travers dintégrations dapplications comme Slack.
Element vous donne le contrôle en vous laissant choisir qui héberge vos conversations. Depuis l'application Element, vous pouvez choisir votre hôte de différentes manières : <b>Element peut être auto-hébergé</b>
Pour une meilleure souveraineté sur vos données et conversations, Element peut être auto-hébergé ou vous pouvez choisir votre hôte Matrix - la norme open source pour les communications décentralisées. Element garantit votre confidentialité, conformité aux normes de sécurité, tout en proposant une intégration souple.
1. Créer un compte gratuit sur le serveur public matrix.org hébergé par les développeurs de Matrix, ou choisir parmi les milliers de serveurs public hébergés par des bénévoles <b>Vos données vous appartiennent</b>
2. Héberger vous-même votre compte en installant un serveur sur votre propre machine Vous décidez où stocker vos données et messages. Aucun risque de minage de données où daccès par des tierce parties.
3. Créer un compte sur un serveur personnalisé en souscrivant sur la plateforme d'hébergement « Element Matrix Services » (EMS)
<b>Pourquoi choisir Element ?</b> Element vous place aux commandes de différente manières :
1. Inscrivez vous sur le serveur public matrix.org hébergé par les développeurs de Matrix ou choisissez parmi des milliers de serveurs publics hébergés par des bénévoles
2. Auto-hébergez votre compte sur un serveur de votre proper infrastructure informatique
3. Inscrivez vous à la plateforme dhébergement Element Matrix Services
<b>VOS DONNÉES VOUS APPARTIENNENT</b> : vous décidez où stocker vos données et messages. Ils vous appartiennent et vous les maîtrisez. Aucune multinationale ne viendra extraire vos données pour les envoyer au plus offrant. <b>Messagerie et collaboration ouvertes</b>
Vous pouvez discuter avec tout le réseau Matrix, que vos interlocuteurs utilisent Element, une autre application Matrix, ou même sils utilisent une application complètement différente.
<b>MESSAGERIE ET COLLABORATION OUVERTES</b> : vous pouvez discuter avec tout le réseau Matrix, quils utilisent Element ou une autre application Matrix, même sils utilisent une autre plateforme de messagerie telle que Slack, IRC ou XMPP. <b>Ultra sécurisé</b>
Chiffrement de bout en bout (seules les personnes dans la conversation peuvent déchiffrer les messages) et vérification de signature croisée entre appareils.
<b>ULTRA SÉCURISÉ</b> : chiffrement de bout en bout (seuls les membres dune conversation peuvent déchiffrer les messages), et signature croisée pour vérifier les appareils de vos interlocuteurs. <b>Communication et intégration parfaites</b>
Messagerie instantannée, appels audio et vidéo, partage de fichier, partage décran et bien dautres intégrations, bots et widgets. Lancez des salons, des communautés, restez en contact et menez vos projets à bien.
<b>TOUTES VOS COMMUNICATIONS</b> : messagerie, appels audio et vidéo, partage de fichier, partage décran et un grand nombre dintégrations, robots et widgets. Participez à des salons, des communautés, restez en contact et faites avancer vos projets. <b>Reprenez où vous vous êtes arrêté</b>
Restez en contact où que vous soyez grâce à lhistorique des messages synchronisé entre tous vos appareils et sur le web sur https://app.element.io
<b>PARTOUT AVEC VOUS</b> : votre historique reste synchronisé entre tous vos appareils et sur le web sur https://element.io/app.

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=a9e356a21595348b6f04b024ed0b08ac8aea6b2ac37e6c0ef58e51549cd7b9cb distributionSha256Sum=9bb8bc05f562f2d42bdf1ba8db62f6b6fa1c3bf6c392228802cc7cb0578fe7e0
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View file

@ -53,7 +53,7 @@ android {
dependencies { dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.4.0'
// Pref theme // Pref theme
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
// PFLockScreen attrs // PFLockScreen attrs

View file

@ -9,7 +9,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath "io.realm:realm-gradle-plugin:10.6.0" classpath "io.realm:realm-gradle-plugin:10.6.1"
} }
} }
@ -121,7 +121,7 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.appcompat:appcompat:1.3.0" implementation "androidx.appcompat:appcompat:1.3.0"
implementation "androidx.core:core-ktx:1.5.0" implementation "androidx.core:core-ktx:1.6.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
@ -169,30 +169,30 @@ dependencies {
implementation 'com.otaliastudios:transcoder:0.10.3' implementation 'com.otaliastudios:transcoder:0.10.3'
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.26' implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.27'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.5.1' testImplementation 'org.robolectric:robolectric:4.5.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.11.0' testImplementation 'io.mockk:mockk:1.12.0'
testImplementation 'org.amshove.kluent:kluent-android:1.67' testImplementation 'org.amshove.kluent:kluent-android:1.67'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// 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 "com.google.dagger:dagger-compiler:$daggerVersion"
androidTestImplementation 'androidx.test:core:1.3.0' androidTestImplementation 'androidx.test:core:1.4.0'
androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'org.amshove.kluent:kluent-android:1.65' androidTestImplementation 'org.amshove.kluent:kluent-android:1.65'
androidTestImplementation 'io.mockk:mockk-android:1.11.0' androidTestImplementation 'io.mockk:mockk-android:1.12.0'
androidTestImplementation "androidx.arch.core:core-testing:$arch_version" androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// Plant Timber tree for test // Plant Timber tree for test
androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
androidTestUtil 'androidx.test:orchestrator:1.3.0' androidTestUtil 'androidx.test:orchestrator:1.4.0'
} }

View file

@ -16,6 +16,8 @@
package org.matrix.android.sdk.api package org.matrix.android.sdk.api
import org.matrix.android.sdk.BuildConfig
/** /**
* This class contains pattern to match the different Matrix ids * This class contains pattern to match the different Matrix ids
*/ */
@ -154,7 +156,7 @@ object MatrixPatterns {
* Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7E (~), * Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7E (~),
* or consist of more than 50 characters, are forbidden and the field should be ignored if received. * or consist of more than 50 characters, are forbidden and the field should be ignored if received.
*/ */
fun isValidOrderString(order: String?) : Boolean { fun isValidOrderString(order: String?): Boolean {
return order != null && order.length < 50 && order matches ORDER_STRING_REGEX return order != null && order.length < 50 && order matches ORDER_STRING_REGEX
} }
@ -163,4 +165,17 @@ object MatrixPatterns {
"[^a-z0-9._%#@=+-]".toRegex().replace(it, "") "[^a-z0-9._%#@=+-]".toRegex().replace(it, "")
} }
} }
/**
* Return the domain form a userId
* Examples:
* - "@alice:domain.org".getDomain() will return "domain.org"
* - "@bob:domain.org:3455".getDomain() will return "domain.org:3455"
*/
fun String.getDomain(): String {
if (BuildConfig.DEBUG) {
assert(isUserId(this))
}
return substringAfter(":")
}
} }

View file

@ -18,11 +18,11 @@ package org.matrix.android.sdk.api.auth.data
import android.net.Uri import android.net.Uri
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import okhttp3.CipherSuite
import okhttp3.TlsVersion
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig.Builder import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig.Builder
import org.matrix.android.sdk.internal.network.ssl.Fingerprint import org.matrix.android.sdk.internal.network.ssl.Fingerprint
import org.matrix.android.sdk.internal.util.ensureTrailingSlash import org.matrix.android.sdk.internal.util.ensureTrailingSlash
import okhttp3.CipherSuite
import okhttp3.TlsVersion
/** /**
* This data class holds how to connect to a specific Homeserver. * This data class holds how to connect to a specific Homeserver.
@ -31,7 +31,12 @@ import okhttp3.TlsVersion
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class HomeServerConnectionConfig( data class HomeServerConnectionConfig(
// This is the homeserver URL entered by the user
val homeServerUri: Uri, val homeServerUri: Uri,
// This is the homeserver base URL for the client-server API. Default to homeServerUri,
// but can be updated with data from .Well-Known before login, and/or with the data
// included in the login response
val homeServerUriBase: Uri = homeServerUri,
val identityServerUri: Uri? = null, val identityServerUri: Uri? = null,
val antiVirusServerUri: Uri? = null, val antiVirusServerUri: Uri? = null,
val allowedFingerprints: List<Fingerprint> = emptyList(), val allowedFingerprints: List<Fingerprint> = emptyList(),
@ -47,7 +52,6 @@ data class HomeServerConnectionConfig(
* This builder should be use to create a [HomeServerConnectionConfig] instance. * This builder should be use to create a [HomeServerConnectionConfig] instance.
*/ */
class Builder { class Builder {
private lateinit var homeServerUri: Uri private lateinit var homeServerUri: Uri
private var identityServerUri: Uri? = null private var identityServerUri: Uri? = null
private var antiVirusServerUri: Uri? = null private var antiVirusServerUri: Uri? = null
@ -234,16 +238,16 @@ data class HomeServerConnectionConfig(
*/ */
fun build(): HomeServerConnectionConfig { fun build(): HomeServerConnectionConfig {
return HomeServerConnectionConfig( return HomeServerConnectionConfig(
homeServerUri, homeServerUri = homeServerUri,
identityServerUri, identityServerUri = identityServerUri,
antiVirusServerUri, antiVirusServerUri = antiVirusServerUri,
allowedFingerprints, allowedFingerprints = allowedFingerprints,
shouldPin, shouldPin = shouldPin,
tlsVersions, tlsVersions = tlsVersions,
tlsCipherSuites, tlsCipherSuites = tlsCipherSuites,
shouldAcceptTlsExtensions, shouldAcceptTlsExtensions = shouldAcceptTlsExtensions,
allowHttpExtension, allowHttpExtension = allowHttpExtension,
forceUsageTlsVersions forceUsageTlsVersions = forceUsageTlsVersions
) )
} }
} }

View file

@ -51,13 +51,18 @@ data class SessionParams(
val deviceId = credentials.deviceId val deviceId = credentials.deviceId
/** /**
* The current homeserver Url. It can be different that the homeserver url entered * The homeserver Url entered by the user during the login phase.
* during login phase, because a redirection may have occurred
*/ */
val homeServerUrl = homeServerConnectionConfig.homeServerUri.toString() val homeServerUrl = homeServerConnectionConfig.homeServerUri.toString()
/** /**
* The current homeserver host * The current homeserver Url for client-server API. It can be different that the homeserver url entered
* during login phase, because a redirection may have occurred
*/
val homeServerUrlBase = homeServerConnectionConfig.homeServerUriBase.toString()
/**
* The current homeserver host, using what has been entered by the user during login phase
*/ */
val homeServerHost = homeServerConnectionConfig.homeServerUri.host val homeServerHost = homeServerConnectionConfig.homeServerUri.host

View file

@ -22,11 +22,6 @@ import org.matrix.android.sdk.api.auth.data.WellKnown
* Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri * Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri
*/ */
sealed class WellknownResult { sealed class WellknownResult {
/**
* The provided matrixId is no valid. Unable to extract a domain name.
*/
object InvalidMatrixId : WellknownResult()
/** /**
* Retrieve the specific piece of information from the user in a way which fits within the existing client user experience, * Retrieve the specific piece of information from the user in a way which fits within the existing client user experience,
* if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point. * if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point.

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 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.failure
sealed class MatrixIdFailure : Failure.FeatureFailure() {
object InvalidMatrixId : MatrixIdFailure()
}

View file

@ -29,8 +29,10 @@ interface RawService {
/** /**
* Specific case for the well-known file. Cache validity is 8 hours * Specific case for the well-known file. Cache validity is 8 hours
* @param domain the domain to get the .well-known file, for instance "matrix.org".
* The URL will be "https://{domain}/.well-known/matrix/client"
*/ */
suspend fun getWellknown(userId: String): String suspend fun getWellknown(domain: String): String
/** /**
* Clear all the cache data * Clear all the cache data

View file

@ -32,7 +32,13 @@ data class HomeServerCapabilities(
/** /**
* Default identity server url, provided in Wellknown * Default identity server url, provided in Wellknown
*/ */
val defaultIdentityServerUrl: String? = null val defaultIdentityServerUrl: String? = null,
/**
* Room versions supported by the server
* This capability describes the default and available room versions a server supports, and at what level of stability.
* Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
*/
val roomVersions: RoomVersionCapabilities? = null
) { ) {
companion object { companion object {
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L

View file

@ -0,0 +1,32 @@
/*
* 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.homeserver
data class RoomVersionCapabilities(
val defaultRoomVersion: String,
val supportedVersion: List<RoomVersionInfo>
)
data class RoomVersionInfo(
val version: String,
val status: RoomVersionStatus
)
enum class RoomVersionStatus {
STABLE,
UNSTABLE
}

View file

@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.tags.TagsService
import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.timeline.TimelineService
import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.typing.TypingService
import org.matrix.android.sdk.api.session.room.uploads.UploadsService import org.matrix.android.sdk.api.session.room.uploads.UploadsService
import org.matrix.android.sdk.api.session.room.version.RoomVersionService
import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.session.search.SearchResult
import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.session.space.Space
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
@ -57,7 +58,8 @@ interface Room :
RelationService, RelationService,
RoomCryptoService, RoomCryptoService,
RoomPushRuleService, RoomPushRuleService,
RoomAccountDataService { RoomAccountDataService,
RoomVersionService {
/** /**
* The roomId of this room * The roomId of this room

View file

@ -65,5 +65,5 @@ data class MessageAudioContent(
) : MessageWithAttachmentContent { ) : MessageWithAttachmentContent {
override val mimeType: String? override val mimeType: String?
get() = encryptedFileInfo?.mimetype ?: audioInfo?.mimeType get() = audioInfo?.mimeType
} }

View file

@ -60,8 +60,7 @@ data class MessageFileContent(
) : MessageWithAttachmentContent { ) : MessageWithAttachmentContent {
override val mimeType: String? override val mimeType: String?
get() = encryptedFileInfo?.mimetype get() = info?.mimeType
?: info?.mimeType
?: MimeTypeMap.getFileExtensionFromUrl(filename ?: body)?.let { extension -> ?: MimeTypeMap.getFileExtensionFromUrl(filename ?: body)?.let { extension ->
MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
} }

View file

@ -20,7 +20,6 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
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.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.api.util.MimeTypes
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@ -55,5 +54,5 @@ data class MessageImageContent(
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageImageInfoContent { ) : MessageImageInfoContent {
override val mimeType: String? override val mimeType: String?
get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: MimeTypes.Images get() = info?.mimeType
} }

View file

@ -55,5 +55,5 @@ data class MessageStickerContent(
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageImageInfoContent { ) : MessageImageInfoContent {
override val mimeType: String? override val mimeType: String?
get() = encryptedFileInfo?.mimetype ?: info?.mimeType get() = info?.mimeType
} }

View file

@ -53,5 +53,5 @@ data class MessageVideoContent(
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageWithAttachmentContent { ) : MessageWithAttachmentContent {
override val mimeType: String? override val mimeType: String?
get() = encryptedFileInfo?.mimetype ?: videoInfo?.mimeType get() = videoInfo?.mimeType
} }

View file

@ -0,0 +1,45 @@
/*
* 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.version
interface RoomVersionService {
/**
* Return the room version of this room
*/
fun getRoomVersion(): String
/**
* Upgrade to the given room version
* @return the replacement room id
*/
suspend fun upgradeToVersion(version: String): String
/**
* Get the recommended room version for the current homeserver
*/
fun getRecommendedVersion() : String
/**
* Ask if the user has enough power level to upgrade the room
*/
fun userMayUpgradeRoom(userId: String): Boolean
/**
* Return true if the current room version is declared unstable by the homeserver
*/
fun isUsingUnstableRoomVersion(): Boolean
}

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.space
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
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.space.model.SpaceChildContent
interface Space { interface Space {
@ -38,6 +39,8 @@ interface Space {
autoJoin: Boolean = false, autoJoin: Boolean = false,
suggested: Boolean? = false) suggested: Boolean? = false)
fun getChildInfo(roomId: String): SpaceChildContent?
suspend fun removeChildren(roomId: String) suspend fun removeChildren(roomId: String)
@Throws @Throws

View file

@ -21,8 +21,8 @@ import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.auth.data.Availability import org.matrix.android.sdk.internal.auth.data.Availability
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams
import org.matrix.android.sdk.internal.auth.data.RiotConfig
import org.matrix.android.sdk.internal.auth.data.TokenLoginParams import org.matrix.android.sdk.internal.auth.data.TokenLoginParams
import org.matrix.android.sdk.internal.auth.data.WebClientConfig
import org.matrix.android.sdk.internal.auth.login.ResetPasswordMailConfirmed import org.matrix.android.sdk.internal.auth.login.ResetPasswordMailConfirmed
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse
@ -44,16 +44,16 @@ import retrofit2.http.Url
*/ */
internal interface AuthAPI { internal interface AuthAPI {
/** /**
* Get a Riot config file, using the name including the domain * Get a Web client config file, using the name including the domain
*/ */
@GET("config.{domain}.json") @GET("config.{domain}.json")
suspend fun getRiotConfigDomain(@Path("domain") domain: String): RiotConfig suspend fun getWebClientConfigDomain(@Path("domain") domain: String): WebClientConfig
/** /**
* Get a Riot config file * Get a Web client default config file
*/ */
@GET("config.json") @GET("config.json")
suspend fun getRiotConfig(): RiotConfig suspend fun getWebClientConfig(): WebClientConfig
/** /**
* Get the version information of the homeserver * Get the version information of the homeserver

View file

@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.auth
import android.net.Uri import android.net.Uri
import dagger.Lazy import dagger.Lazy
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@ -28,10 +30,11 @@ import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixIdFailure
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.appendParamToUrl import org.matrix.android.sdk.api.util.appendParamToUrl
import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.auth.data.RiotConfig import org.matrix.android.sdk.internal.auth.data.WebClientConfig
import org.matrix.android.sdk.internal.auth.db.PendingSessionData import org.matrix.android.sdk.internal.auth.db.PendingSessionData
import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard
import org.matrix.android.sdk.internal.auth.login.DirectLoginTask import org.matrix.android.sdk.internal.auth.login.DirectLoginTask
@ -122,7 +125,7 @@ internal class DefaultAuthenticationService @Inject constructor(
private fun getHomeServerUrlBase(): String? { private fun getHomeServerUrlBase(): String? {
return pendingSessionData return pendingSessionData
?.homeServerConnectionConfig ?.homeServerConnectionConfig
?.homeServerUri ?.homeServerUriBase
?.toString() ?.toString()
?.trim { it == '/' } ?.trim { it == '/' }
} }
@ -143,9 +146,9 @@ internal class DefaultAuthenticationService @Inject constructor(
return result.fold( return result.fold(
{ {
// The homeserver exists and up to date, keep the config // The homeserver exists and up to date, keep the config
// Homeserver url may have been changed, if it was a Riot url // Homeserver url may have been changed, if it was a Web client url
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy( val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = Uri.parse(it.homeServerUrl) homeServerUriBase = Uri.parse(it.homeServerUrl)
) )
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig) pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
@ -154,7 +157,7 @@ internal class DefaultAuthenticationService @Inject constructor(
}, },
{ {
if (it is UnrecognizedCertificateException) { if (it is UnrecognizedCertificateException) {
throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint) throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUriBase.toString(), it.fingerprint)
} else { } else {
throw it throw it
} }
@ -165,46 +168,57 @@ internal class DefaultAuthenticationService @Inject constructor(
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
val authAPI = buildAuthAPI(homeServerConnectionConfig) val authAPI = buildAuthAPI(homeServerConnectionConfig)
// First check the homeserver version // First check if there is a well-known file
return runCatching { return try {
executeRequest(null) { getWellknownLoginFlowInternal(homeServerConnectionConfig)
authAPI.versions() } catch (failure: Throwable) {
if (failure is Failure.OtherServerError
&& failure.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// 404, no well-known data, try direct access to the API
// First check the homeserver version
return runCatching {
executeRequest(null) {
authAPI.versions()
}
}
.map { versions ->
// Ok, it seems that the homeserver url is valid
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUriBase.toString())
}
.fold(
{
it
},
{
if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// It's maybe a Web client url?
getWebClientDomainLoginFlowInternal(homeServerConnectionConfig)
} else {
throw it
}
}
)
} else {
throw failure
} }
} }
.map { versions ->
// Ok, it seems that the homeserver url is valid
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
}
.fold(
{
it
},
{
if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// It's maybe a Riot url?
getRiotDomainLoginFlowInternal(homeServerConnectionConfig)
} else {
throw it
}
}
)
} }
private suspend fun getRiotDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { private suspend fun getWebClientDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
val authAPI = buildAuthAPI(homeServerConnectionConfig) val authAPI = buildAuthAPI(homeServerConnectionConfig)
val domain = homeServerConnectionConfig.homeServerUri.host val domain = homeServerConnectionConfig.homeServerUri.host
?: return getRiotLoginFlowInternal(homeServerConnectionConfig) ?: return getWebClientLoginFlowInternal(homeServerConnectionConfig)
// Ok, try to get the config.domain.json file of a RiotWeb client // Ok, try to get the config.domain.json file of a Web client
return runCatching { return runCatching {
executeRequest(null) { executeRequest(null) {
authAPI.getRiotConfigDomain(domain) authAPI.getWebClientConfigDomain(domain)
} }
} }
.map { riotConfig -> .map { webClientConfig ->
onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig) onWebClientConfigRetrieved(homeServerConnectionConfig, webClientConfig)
} }
.fold( .fold(
{ {
@ -214,7 +228,7 @@ internal class DefaultAuthenticationService @Inject constructor(
if (it is Failure.OtherServerError if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// Try with config.json // Try with config.json
getRiotLoginFlowInternal(homeServerConnectionConfig) getWebClientLoginFlowInternal(homeServerConnectionConfig)
} else { } else {
throw it throw it
} }
@ -222,40 +236,24 @@ internal class DefaultAuthenticationService @Inject constructor(
) )
} }
private suspend fun getRiotLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { private suspend fun getWebClientLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
val authAPI = buildAuthAPI(homeServerConnectionConfig) val authAPI = buildAuthAPI(homeServerConnectionConfig)
// Ok, try to get the config.json file of a RiotWeb client // Ok, try to get the config.json file of a Web client
return runCatching { return executeRequest(null) {
executeRequest(null) { authAPI.getWebClientConfig()
authAPI.getRiotConfig()
}
} }
.map { riotConfig -> .let { webClientConfig ->
onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig) onWebClientConfigRetrieved(homeServerConnectionConfig, webClientConfig)
} }
.fold(
{
it
},
{
if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// Try with wellknown
getWellknownLoginFlowInternal(homeServerConnectionConfig)
} else {
throw it
}
}
)
} }
private suspend fun onRiotConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, riotConfig: RiotConfig): LoginFlowResult { private suspend fun onWebClientConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, webClientConfig: WebClientConfig): LoginFlowResult {
val defaultHomeServerUrl = riotConfig.getPreferredHomeServerUrl() val defaultHomeServerUrl = webClientConfig.getPreferredHomeServerUrl()
if (defaultHomeServerUrl?.isNotEmpty() == true) { if (defaultHomeServerUrl?.isNotEmpty() == true) {
// Ok, good sign, we got a default hs url // Ok, good sign, we got a default hs url
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = Uri.parse(defaultHomeServerUrl) homeServerUriBase = Uri.parse(defaultHomeServerUrl)
) )
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
@ -275,15 +273,13 @@ internal class DefaultAuthenticationService @Inject constructor(
val domain = homeServerConnectionConfig.homeServerUri.host val domain = homeServerConnectionConfig.homeServerUri.host
?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) ?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
// Create a fake userId, for the getWellknown task val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(domain, homeServerConnectionConfig))
val fakeUserId = "@alice:$domain"
val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(fakeUserId, homeServerConnectionConfig))
return when (wellknownResult) { return when (wellknownResult) {
is WellknownResult.Prompt -> { is WellknownResult.Prompt -> {
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = Uri.parse(wellknownResult.homeServerUrl), homeServerUriBase = Uri.parse(wellknownResult.homeServerUrl),
identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) } identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) } ?: homeServerConnectionConfig.identityServerUri
) )
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
@ -379,7 +375,14 @@ internal class DefaultAuthenticationService @Inject constructor(
override suspend fun getWellKnownData(matrixId: String, override suspend fun getWellKnownData(matrixId: String,
homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult { homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult {
return getWellknownTask.execute(GetWellknownTask.Params(matrixId, homeServerConnectionConfig)) if (!MatrixPatterns.isUserId(matrixId)) {
throw MatrixIdFailure.InvalidMatrixId
}
return getWellknownTask.execute(GetWellknownTask.Params(
domain = matrixId.getDomain(),
homeServerConnectionConfig = homeServerConnectionConfig)
)
} }
override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig, override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
@ -390,7 +393,7 @@ internal class DefaultAuthenticationService @Inject constructor(
} }
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUri.toString()) val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUriBase.toString())
return retrofit.create(AuthAPI::class.java) return retrofit.create(AuthAPI::class.java)
} }

View file

@ -42,7 +42,7 @@ internal class DefaultIsValidClientServerApiTask @Inject constructor(
override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean { override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean {
val client = buildClient(params.homeServerConnectionConfig) val client = buildClient(params.homeServerConnectionConfig)
val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString() val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString()
val authAPI = retrofitFactory.create(client, homeServerUrl) val authAPI = retrofitFactory.create(client, homeServerUrl)
.create(AuthAPI::class.java) .create(AuthAPI::class.java)

View file

@ -56,7 +56,7 @@ internal class DefaultSessionCreator @Inject constructor(
tryOrNull { tryOrNull {
isValidClientServerApiTask.execute( isValidClientServerApiTask.execute(
IsValidClientServerApiTask.Params( IsValidClientServerApiTask.Params(
homeServerConnectionConfig.copy(homeServerUri = it) homeServerConnectionConfig.copy(homeServerUriBase = it)
) )
) )
.also { Timber.d("Overriding homeserver url: $it") } .also { Timber.d("Overriding homeserver url: $it") }
@ -66,7 +66,7 @@ internal class DefaultSessionCreator @Inject constructor(
val sessionParams = SessionParams( val sessionParams = SessionParams(
credentials = credentials, credentials = credentials,
homeServerConnectionConfig = homeServerConnectionConfig.copy( homeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = overriddenUrl ?: homeServerConnectionConfig.homeServerUri, homeServerUriBase = overriddenUrl ?: homeServerConnectionConfig.homeServerUriBase,
identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL
// remove trailing "/" // remove trailing "/"
?.trim { it == '/' } ?.trim { it == '/' }

View file

@ -20,7 +20,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class RiotConfig( internal data class WebClientConfig(
/** /**
* This is now deprecated, but still used first, rather than value from "default_server_config" * This is now deprecated, but still used first, rather than value from "default_server_config"
*/ */
@ -28,7 +28,7 @@ internal data class RiotConfig(
val defaultHomeServerUrl: String?, val defaultHomeServerUrl: String?,
@Json(name = "default_server_config") @Json(name = "default_server_config")
val defaultServerConfig: RiotConfigDefaultServerConfig? val defaultServerConfig: WebClientConfigDefaultServerConfig?
) { ) {
fun getPreferredHomeServerUrl(): String? { fun getPreferredHomeServerUrl(): String? {
return defaultHomeServerUrl return defaultHomeServerUrl
@ -38,13 +38,13 @@ internal data class RiotConfig(
} }
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class RiotConfigDefaultServerConfig( internal data class WebClientConfigDefaultServerConfig(
@Json(name = "m.homeserver") @Json(name = "m.homeserver")
val homeServer: RiotConfigBaseConfig? = null val homeServer: WebClientConfigBaseConfig? = null
) )
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class RiotConfigBaseConfig( internal data class WebClientConfigBaseConfig(
@Json(name = "base_url") @Json(name = "base_url")
val baseURL: String? = null val baseURL: String? = null
) )

View file

@ -16,17 +16,19 @@
package org.matrix.android.sdk.internal.auth.db package org.matrix.android.sdk.internal.auth.db
import org.matrix.android.sdk.api.auth.data.Credentials import android.net.Uri
import org.matrix.android.sdk.api.auth.data.sessionId
import org.matrix.android.sdk.internal.di.MoshiProvider
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.RealmMigration import io.realm.RealmMigration
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.sessionId
import org.matrix.android.sdk.internal.di.MoshiProvider
import timber.log.Timber import timber.log.Timber
internal object AuthRealmMigration : RealmMigration { internal object AuthRealmMigration : RealmMigration {
// Current schema version // Current schema version
const val SCHEMA_VERSION = 3L const val SCHEMA_VERSION = 4L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion") Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
@ -34,6 +36,7 @@ internal object AuthRealmMigration : RealmMigration {
if (oldVersion <= 0) migrateTo1(realm) if (oldVersion <= 0) migrateTo1(realm)
if (oldVersion <= 1) migrateTo2(realm) if (oldVersion <= 1) migrateTo2(realm)
if (oldVersion <= 2) migrateTo3(realm) if (oldVersion <= 2) migrateTo3(realm)
if (oldVersion <= 3) migrateTo4(realm)
} }
private fun migrateTo1(realm: DynamicRealm) { private fun migrateTo1(realm: DynamicRealm) {
@ -81,4 +84,34 @@ internal object AuthRealmMigration : RealmMigration {
} }
?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID) ?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID)
} }
private fun migrateTo4(realm: DynamicRealm) {
Timber.d("Step 3 -> 4")
Timber.d("Update SessionParamsEntity to add HomeServerConnectionConfig.homeServerUriBase value")
val adapter = MoshiProvider.providesMoshi()
.adapter(HomeServerConnectionConfig::class.java)
realm.schema.get("SessionParamsEntity")
?.transform {
val homeserverConnectionConfigJson = it.getString(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON)
val homeserverConnectionConfig = adapter
.fromJson(homeserverConnectionConfigJson)
val homeserverUrl = homeserverConnectionConfig?.homeServerUri?.toString()
// Special case for matrix.org. Old session may use "https://matrix.org", newer one may use
// "https://matrix-client.matrix.org". So fix that here
val alteredHomeserverConnectionConfig =
if (homeserverUrl == "https://matrix.org" || homeserverUrl == "https://matrix-client.matrix.org") {
homeserverConnectionConfig.copy(
homeServerUri = Uri.parse("https://matrix.org"),
homeServerUriBase = Uri.parse("https://matrix-client.matrix.org")
)
} else {
homeserverConnectionConfig
}
it.set(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, adapter.toJson(alteredHomeserverConnectionConfig))
}
}
} }

View file

@ -50,7 +50,7 @@ internal class DefaultDirectLoginTask @Inject constructor(
override suspend fun execute(params: DirectLoginTask.Params): Session { override suspend fun execute(params: DirectLoginTask.Params): Session {
val client = buildClient(params.homeServerConnectionConfig) val client = buildClient(params.homeServerConnectionConfig)
val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString() val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString()
val authAPI = retrofitFactory.create(client, homeServerUrl) val authAPI = retrofitFactory.create(client, homeServerUrl)
.create(AuthAPI::class.java) .create(AuthAPI::class.java)

View file

@ -112,7 +112,6 @@ internal abstract class CryptoModule {
@SessionScope @SessionScope
fun providesRealmConfiguration(@SessionFilesDirectory directory: File, fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
@UserMd5 userMd5: String, @UserMd5 userMd5: String,
realmCryptoStoreMigration: RealmCryptoStoreMigration,
realmKeysUtils: RealmKeysUtils): RealmConfiguration { realmKeysUtils: RealmKeysUtils): RealmConfiguration {
return RealmConfiguration.Builder() return RealmConfiguration.Builder()
.directory(directory) .directory(directory)
@ -123,7 +122,7 @@ internal abstract class CryptoModule {
.modules(RealmCryptoStoreModule()) .modules(RealmCryptoStoreModule())
.allowWritesOnUiThread(true) .allowWritesOnUiThread(true)
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
.migration(realmCryptoStoreMigration) .migration(RealmCryptoStoreMigration)
.build() .build()
} }

View file

@ -39,7 +39,9 @@ internal object MXEncryptedAttachments {
private const val SECRET_KEY_SPEC_ALGORITHM = "AES" private const val SECRET_KEY_SPEC_ALGORITHM = "AES"
private const val MESSAGE_DIGEST_ALGORITHM = "SHA-256" private const val MESSAGE_DIGEST_ALGORITHM = "SHA-256"
fun encrypt(clearStream: InputStream, mimetype: String?, outputFile: File, progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo { fun encrypt(clearStream: InputStream,
outputFile: File,
progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo {
val t0 = System.currentTimeMillis() val t0 = System.currentTimeMillis()
val secureRandom = SecureRandom() val secureRandom = SecureRandom()
val initVectorBytes = ByteArray(16) { 0.toByte() } val initVectorBytes = ByteArray(16) { 0.toByte() }
@ -86,7 +88,6 @@ internal object MXEncryptedAttachments {
return EncryptedFileInfo( return EncryptedFileInfo(
url = null, url = null,
mimetype = mimetype,
key = EncryptedFileKey( key = EncryptedFileKey(
alg = "A256CTR", alg = "A256CTR",
ext = true, ext = true,
@ -155,10 +156,9 @@ internal object MXEncryptedAttachments {
* Encrypt an attachment stream. * Encrypt an attachment stream.
* DO NOT USE for big files, it will load all in memory * DO NOT USE for big files, it will load all in memory
* @param attachmentStream the attachment stream. Will be closed after this method call. * @param attachmentStream the attachment stream. Will be closed after this method call.
* @param mimetype the mime type
* @return the encryption file info * @return the encryption file info
*/ */
fun encryptAttachment(attachmentStream: InputStream, mimetype: String?): EncryptionResult { fun encryptAttachment(attachmentStream: InputStream): EncryptionResult {
val t0 = System.currentTimeMillis() val t0 = System.currentTimeMillis()
val secureRandom = SecureRandom() val secureRandom = SecureRandom()
@ -207,7 +207,6 @@ internal object MXEncryptedAttachments {
return EncryptionResult( return EncryptionResult(
encryptedFileInfo = EncryptedFileInfo( encryptedFileInfo = EncryptedFileInfo(
url = null, url = null,
mimetype = mimetype,
key = EncryptedFileKey( key = EncryptedFileKey(
alg = "A256CTR", alg = "A256CTR",
ext = true, ext = true,
@ -232,7 +231,9 @@ internal object MXEncryptedAttachments {
* @param outputStream the outputStream where the decrypted attachment will be write. * @param outputStream the outputStream where the decrypted attachment will be write.
* @return true in case of success, false in case of error * @return true in case of success, false in case of error
*/ */
fun decryptAttachment(attachmentStream: InputStream?, elementToDecrypt: ElementToDecrypt?, outputStream: OutputStream): Boolean { fun decryptAttachment(attachmentStream: InputStream?,
elementToDecrypt: ElementToDecrypt?,
outputStream: OutputStream): Boolean {
// sanity checks // sanity checks
if (null == attachmentStream || elementToDecrypt == null) { if (null == attachmentStream || elementToDecrypt == null) {
Timber.e("## decryptAttachment() : null stream") Timber.e("## decryptAttachment() : null stream")

View file

@ -29,12 +29,6 @@ data class EncryptedFileInfo(
@Json(name = "url") @Json(name = "url")
val url: String? = null, val url: String? = null,
/**
* Not documented
*/
@Json(name = "mimetype")
val mimetype: String? = null,
/** /**
* Required. A JSON Web Key object. * Required. A JSON Web Key object.
*/ */

View file

@ -18,6 +18,9 @@ package org.matrix.android.sdk.internal.crypto.store.db
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import com.squareup.moshi.Types import com.squareup.moshi.Types
import io.realm.DynamicRealm
import io.realm.RealmMigration
import io.realm.RealmObjectSchema
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
@ -35,29 +38,24 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntit
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.di.SerializeNulls import org.matrix.android.sdk.internal.di.SerializeNulls
import io.realm.DynamicRealm
import io.realm.RealmMigration
import io.realm.RealmObjectSchema
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2 import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
import org.matrix.androidsdk.crypto.data.MXDeviceInfo as LegacyMXDeviceInfo import org.matrix.androidsdk.crypto.data.MXDeviceInfo as LegacyMXDeviceInfo
internal class RealmCryptoStoreMigration @Inject constructor(private val crossSigningKeysMapper: CrossSigningKeysMapper) : RealmMigration { internal object RealmCryptoStoreMigration : RealmMigration {
companion object { // 0, 1, 2: legacy Riot-Android
// 0, 1, 2: legacy Riot-Android // 3: migrate to RiotX schema
// 3: migrate to RiotX schema // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
// 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6) const val CRYPTO_STORE_SCHEMA_VERSION = 12L
const val CRYPTO_STORE_SCHEMA_VERSION = 12L
}
private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema { private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
if (!hasField(fieldName)) { if (!hasField(fieldName)) {
@ -384,6 +382,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
private fun migrateTo7(realm: DynamicRealm) { private fun migrateTo7(realm: DynamicRealm) {
Timber.d("Step 6 -> 7") Timber.d("Step 6 -> 7")
Timber.d("Updating KeyInfoEntity table") Timber.d("Updating KeyInfoEntity table")
val crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi())
val keyInfoEntities = realm.where("KeyInfoEntity").findAll() val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
try { try {
keyInfoEntities.forEach { keyInfoEntities.forEach {

View file

@ -19,9 +19,10 @@ package org.matrix.android.sdk.internal.database
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.FieldAttribute import io.realm.FieldAttribute
import io.realm.RealmMigration import io.realm.RealmMigration
import org.matrix.android.sdk.api.session.room.model.VersioningState
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
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.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
@ -40,14 +41,12 @@ import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFie
import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.query.process
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
class RealmSessionStoreMigration @Inject constructor() : RealmMigration { internal object RealmSessionStoreMigration : RealmMigration {
companion object { const val SESSION_STORE_SCHEMA_VERSION = 16L
const val SESSION_STORE_SCHEMA_VERSION = 14L
}
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.v("Migrating Realm Session from $oldVersion to $newVersion") Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
@ -66,6 +65,8 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
if (oldVersion <= 11) migrateTo12(realm) if (oldVersion <= 11) migrateTo12(realm)
if (oldVersion <= 12) migrateTo13(realm) if (oldVersion <= 12) migrateTo13(realm)
if (oldVersion <= 13) migrateTo14(realm) if (oldVersion <= 13) migrateTo14(realm)
if (oldVersion <= 14) migrateTo15(realm)
if (oldVersion <= 15) migrateTo16(realm)
} }
private fun migrateTo1(realm: DynamicRealm) { private fun migrateTo1(realm: DynamicRealm) {
@ -292,7 +293,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
Timber.d("Step 13 -> 14") Timber.d("Step 13 -> 14")
val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity") val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity")
.addField(RoomAccountDataEntityFields.CONTENT_STR, String::class.java) .addField(RoomAccountDataEntityFields.CONTENT_STR, String::class.java)
.addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED) .addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED)
realm.schema.get("RoomEntity") realm.schema.get("RoomEntity")
?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema) ?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema)
@ -306,4 +307,27 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
roomAccountDataSchema.isEmbedded = true roomAccountDataSchema.isEmbedded = true
} }
private fun migrateTo15(realm: DynamicRealm) {
Timber.d("Step 14 -> 15")
// fix issue with flattenParentIds on DM that kept growing with duplicate
// so we reset it, will be updated next sync
realm.where("RoomSummaryEntity")
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
.findAll()
.onEach {
it.setString(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, null)
}
}
private fun migrateTo16(realm: DynamicRealm) {
Timber.d("Step 15 -> 16")
realm.schema.get("HomeServerCapabilitiesEntity")
?.addField(HomeServerCapabilitiesEntityFields.ROOM_VERSIONS_JSON, String::class.java)
?.transform { obj ->
// Schedule a refresh of the capabilities
obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
}
}
} }

View file

@ -43,7 +43,6 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
@SessionFilesDirectory val directory: File, @SessionFilesDirectory val directory: File,
@SessionId val sessionId: String, @SessionId val sessionId: String,
@UserMd5 val userMd5: String, @UserMd5 val userMd5: String,
val migration: RealmSessionStoreMigration,
context: Context) { context: Context) {
// Keep legacy preferences name for compatibility reason // Keep legacy preferences name for compatibility reason
@ -72,7 +71,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
.allowWritesOnUiThread(true) .allowWritesOnUiThread(true)
.modules(SessionRealmModule()) .modules(SessionRealmModule())
.schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
.migration(migration) .migration(RealmSessionStoreMigration)
.build() .build()
// Try creating a realm instance and if it succeeds we can clear the flag // Try creating a realm instance and if it succeeds we can clear the flag

View file

@ -16,8 +16,15 @@
package org.matrix.android.sdk.internal.database.mapper package org.matrix.android.sdk.internal.database.mapper
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.homeserver.RoomVersionCapabilities
import org.matrix.android.sdk.api.session.homeserver.RoomVersionInfo
import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.session.homeserver.RoomVersions
import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService
/** /**
* HomeServerCapabilitiesEntity -> HomeSeverCapabilities * HomeServerCapabilitiesEntity -> HomeSeverCapabilities
@ -29,7 +36,30 @@ internal object HomeServerCapabilitiesMapper {
canChangePassword = entity.canChangePassword, canChangePassword = entity.canChangePassword,
maxUploadFileSize = entity.maxUploadFileSize, maxUploadFileSize = entity.maxUploadFileSize,
lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported, lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
defaultIdentityServerUrl = entity.defaultIdentityServerUrl defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
roomVersions = mapRoomVersion(entity.roomVersionsJson)
) )
} }
private fun mapRoomVersion(roomVersionsJson: String?): RoomVersionCapabilities? {
roomVersionsJson ?: return null
return tryOrNull {
MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let {
RoomVersionCapabilities(
defaultRoomVersion = it.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION,
supportedVersion = it.available.entries.map { entry ->
RoomVersionInfo(
version = entry.key,
status = if (entry.value == "stable") {
RoomVersionStatus.STABLE
} else {
RoomVersionStatus.UNSTABLE
}
)
}
)
}
}
}
} }

View file

@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
internal open class HomeServerCapabilitiesEntity( internal open class HomeServerCapabilitiesEntity(
var canChangePassword: Boolean = true, var canChangePassword: Boolean = true,
var roomVersionsJson: String? = null,
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN, var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
var lastVersionIdentityServerSupported: Boolean = false, var lastVersionIdentityServerSupported: Boolean = false,
var defaultIdentityServerUrl: String? = null, var defaultIdentityServerUrl: String? = null,

View file

@ -37,7 +37,8 @@ internal abstract class FederationModule {
fun providesFederationAPI(@Unauthenticated okHttpClient: Lazy<OkHttpClient>, fun providesFederationAPI(@Unauthenticated okHttpClient: Lazy<OkHttpClient>,
sessionParams: SessionParams, sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): FederationAPI { retrofitFactory: RetrofitFactory): FederationAPI {
return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrl).create(FederationAPI::class.java) return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrlBase)
.create(FederationAPI::class.java)
} }
} }

View file

@ -42,7 +42,6 @@ import org.matrix.android.sdk.internal.legacy.riot.HomeServerConnectionConfig as
internal class DefaultLegacySessionImporter @Inject constructor( internal class DefaultLegacySessionImporter @Inject constructor(
private val context: Context, private val context: Context,
private val sessionParamsStore: SessionParamsStore, private val sessionParamsStore: SessionParamsStore,
private val realmCryptoStoreMigration: RealmCryptoStoreMigration,
private val realmKeysUtils: RealmKeysUtils private val realmKeysUtils: RealmKeysUtils
) : LegacySessionImporter { ) : LegacySessionImporter {
@ -172,7 +171,7 @@ internal class DefaultLegacySessionImporter @Inject constructor(
.name("crypto_store.realm") .name("crypto_store.realm")
.modules(RealmCryptoStoreModule()) .modules(RealmCryptoStoreModule())
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
.migration(realmCryptoStoreMigration) .migration(RealmCryptoStoreMigration)
.build() .build()
Timber.d("Migration: copy DB to encrypted DB") Timber.d("Migration: copy DB to encrypted DB")

View file

@ -253,7 +253,7 @@ internal object CertUtil {
val list = ArrayList<ConnectionSpec>() val list = ArrayList<ConnectionSpec>()
list.add(builder.build()) list.add(builder.build())
// TODO: we should display a warning if user enter an http url // TODO: we should display a warning if user enter an http url
if (hsConfig.allowHttpExtension || hsConfig.homeServerUri.toString().startsWith("http://")) { if (hsConfig.allowHttpExtension || hsConfig.homeServerUriBase.toString().startsWith("http://")) {
list.add(ConnectionSpec.CLEARTEXT) list.add(ConnectionSpec.CLEARTEXT)
} }
return list return list

View file

@ -29,10 +29,9 @@ internal class DefaultRawService @Inject constructor(
return getUrlTask.execute(GetUrlTask.Params(url, cacheStrategy)) return getUrlTask.execute(GetUrlTask.Params(url, cacheStrategy))
} }
override suspend fun getWellknown(userId: String): String { override suspend fun getWellknown(domain: String): String {
val homeServerDomain = userId.substringAfter(":")
return getUrl( return getUrl(
"https://$homeServerDomain/.well-known/matrix/client", "https://$domain/.well-known/matrix/client",
CacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false) CacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false)
) )
} }

View file

@ -313,7 +313,7 @@ internal class DefaultSession @Inject constructor(
override fun getUiaSsoFallbackUrl(authenticationSessionId: String): String { override fun getUiaSsoFallbackUrl(authenticationSessionId: String): String {
val hsBas = sessionParams.homeServerConnectionConfig val hsBas = sessionParams.homeServerConnectionConfig
.homeServerUri .homeServerUriBase
.toString() .toString()
.trim { it == '/' } .trim { it == '/' }
return buildString { return buildString {

View file

@ -261,7 +261,7 @@ internal abstract class SessionModule {
sessionParams: SessionParams, sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): Retrofit { retrofitFactory: RetrofitFactory): Retrofit {
return retrofitFactory return retrofitFactory
.create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString()) .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUriBase.toString())
} }
@JvmStatic @JvmStatic

View file

@ -26,7 +26,7 @@ private const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver { internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
private val baseUrl = homeServerConnectionConfig.homeServerUri.toString().ensureTrailingSlash() private val baseUrl = homeServerConnectionConfig.homeServerUriBase.toString().ensureTrailingSlash()
override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload" override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"

View file

@ -234,7 +234,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
.also { filesToDelete.add(it) } .also { filesToDelete.add(it) }
uploadedFileEncryptedFileInfo = uploadedFileEncryptedFileInfo =
MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), attachment.getSafeMimeType(), encryptedFile) { read, total -> MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), encryptedFile) { read, total ->
notifyTracker(params) { notifyTracker(params) {
contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong()) contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
} }
@ -315,7 +315,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
if (params.isEncrypted) { if (params.isEncrypted) {
Timber.v("Encrypt thumbnail") Timber.v("Encrypt thumbnail")
notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) } notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType) val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream())
val contentUploadResponse = fileUploader.uploadByteArray( val contentUploadResponse = fileUploader.uploadByteArray(
byteArray = encryptionResult.encryptedByteArray, byteArray = encryptionResult.encryptedByteArray,
filename = null, filename = null,

View file

@ -16,18 +16,12 @@
package org.matrix.android.sdk.internal.session.homeserver package org.matrix.android.sdk.internal.session.homeserver
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
import org.matrix.android.sdk.internal.database.query.get
import org.matrix.android.sdk.internal.di.SessionDatabase
import javax.inject.Inject import javax.inject.Inject
internal class DefaultHomeServerCapabilitiesService @Inject constructor( internal class DefaultHomeServerCapabilitiesService @Inject constructor(
@SessionDatabase private val monarchy: Monarchy, private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask
) : HomeServerCapabilitiesService { ) : HomeServerCapabilitiesService {
@ -36,11 +30,7 @@ internal class DefaultHomeServerCapabilitiesService @Inject constructor(
} }
override fun getHomeServerCapabilities(): HomeServerCapabilities { override fun getHomeServerCapabilities(): HomeServerCapabilities {
return Realm.getInstance(monarchy.realmConfiguration).use { realm -> return homeServerCapabilitiesDataSource.getHomeServerCapabilities()
HomeServerCapabilitiesEntity.get(realm)?.let {
HomeServerCapabilitiesMapper.map(it)
}
}
?: HomeServerCapabilities() ?: HomeServerCapabilities()
} }
} }

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.homeserver
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.extensions.orTrue
import org.matrix.android.sdk.api.util.JsonDict
/** /**
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-capabilities * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-capabilities
@ -38,9 +39,14 @@ internal data class Capabilities(
* Capability to indicate if the user can change their password. * Capability to indicate if the user can change their password.
*/ */
@Json(name = "m.change_password") @Json(name = "m.change_password")
val changePassword: ChangePassword? = null val changePassword: ChangePassword? = null,
// No need for m.room_versions for the moment /**
* This capability describes the default and available room versions a server supports, and at what level of stability.
* Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
*/
@Json(name = "m.room_versions")
val roomVersions: RoomVersions? = null
) )
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@ -52,6 +58,21 @@ internal data class ChangePassword(
val enabled: Boolean? val enabled: Boolean?
) )
@JsonClass(generateAdapter = true)
internal data class RoomVersions(
/**
* Required. The default room version the server is using for new rooms.
*/
@Json(name = "default")
val default: String?,
/**
* Required. A detailed description of the room versions the server supports.
*/
@Json(name = "available")
val available: JsonDict
)
// The spec says: If not present, the client should assume that password changes are possible via the API // The spec says: If not present, the client should assume that password changes are possible via the API
internal fun GetCapabilitiesResult.canChangePassword(): Boolean { internal fun GetCapabilitiesResult.canChangePassword(): Boolean {
return capabilities?.changePassword?.enabled.orTrue() return capabilities?.changePassword?.enabled.orTrue()

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.homeserver package org.matrix.android.sdk.internal.session.homeserver
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
@ -24,6 +25,7 @@ import org.matrix.android.sdk.internal.auth.version.Versions
import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
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
@ -89,7 +91,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
}.getOrNull() }.getOrNull()
val wellknownResult = runCatching { val wellknownResult = runCatching {
getWellknownTask.execute(GetWellknownTask.Params(userId, homeServerConnectionConfig)) getWellknownTask.execute(GetWellknownTask.Params(
domain = userId.getDomain(),
homeServerConnectionConfig = homeServerConnectionConfig
))
}.getOrNull() }.getOrNull()
insertInDb(capabilities, mediaConfig, versions, wellknownResult) insertInDb(capabilities, mediaConfig, versions, wellknownResult)
@ -104,6 +109,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
if (getCapabilitiesResult != null) { if (getCapabilitiesResult != null) {
homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword() homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword()
homeServerCapabilitiesEntity.roomVersionsJson = getCapabilitiesResult.capabilities?.roomVersions?.let {
MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it)
}
} }
if (getMediaConfigResult != null) { if (getMediaConfigResult != null) {

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 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.homeserver
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
import org.matrix.android.sdk.internal.database.query.get
import org.matrix.android.sdk.internal.di.SessionDatabase
import javax.inject.Inject
internal class HomeServerCapabilitiesDataSource @Inject constructor(
@SessionDatabase private val monarchy: Monarchy
) {
fun getHomeServerCapabilities(): HomeServerCapabilities? {
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
HomeServerCapabilitiesEntity.get(realm)?.let {
HomeServerCapabilitiesMapper.map(it)
}
}
}
}

View file

@ -60,7 +60,6 @@ internal abstract class IdentityModule {
@SessionScope @SessionScope
fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils, fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
@SessionFilesDirectory directory: File, @SessionFilesDirectory directory: File,
migration: RealmIdentityStoreMigration,
@UserMd5 userMd5: String): RealmConfiguration { @UserMd5 userMd5: String): RealmConfiguration {
return RealmConfiguration.Builder() return RealmConfiguration.Builder()
.directory(directory) .directory(directory)
@ -69,7 +68,7 @@ internal abstract class IdentityModule {
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
} }
.schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION) .schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION)
.migration(migration) .migration(RealmIdentityStoreMigration)
.allowWritesOnUiThread(true) .allowWritesOnUiThread(true)
.modules(IdentityRealmModule()) .modules(IdentityRealmModule())
.build() .build()

View file

@ -19,13 +19,10 @@ package org.matrix.android.sdk.internal.session.identity.db
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.RealmMigration import io.realm.RealmMigration
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration { internal object RealmIdentityStoreMigration : RealmMigration {
companion object { const val IDENTITY_STORE_SCHEMA_VERSION = 1L
const val IDENTITY_STORE_SCHEMA_VERSION = 1L
}
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.v("Migrating Realm Identity from $oldVersion to $newVersion") Timber.v("Migrating Realm Identity from $oldVersion to $newVersion")

View file

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.permalinks package org.matrix.android.sdk.internal.session.permalinks
import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
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.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
@ -47,9 +48,9 @@ internal class ViaParameterFinder @Inject constructor(
} }
fun computeViaParams(userId: String, roomId: String, max: Int): List<String> { fun computeViaParams(userId: String, roomId: String, max: Int): List<String> {
val userHomeserver = userId.substringAfter(":") val userHomeserver = userId.getDomain()
return getUserIdsOfJoinedMembers(roomId) return getUserIdsOfJoinedMembers(roomId)
.map { it.substringAfter(":") } .map { it.getDomain() }
.groupBy { it } .groupBy { it }
.mapValues { it.value.size } .mapValues { it.value.size }
.toMutableMap() .toMutableMap()

View file

@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.room.tags.TagsService
import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.timeline.TimelineService
import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.typing.TypingService
import org.matrix.android.sdk.api.session.room.uploads.UploadsService import org.matrix.android.sdk.api.session.room.uploads.UploadsService
import org.matrix.android.sdk.api.session.room.version.RoomVersionService
import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.session.search.SearchResult
import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.session.space.Space
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
@ -67,9 +68,11 @@ internal class DefaultRoom(override val roomId: String,
private val roomMembersService: MembershipService, private val roomMembersService: MembershipService,
private val roomPushRuleService: RoomPushRuleService, private val roomPushRuleService: RoomPushRuleService,
private val roomAccountDataService: RoomAccountDataService, private val roomAccountDataService: RoomAccountDataService,
private val roomVersionService: RoomVersionService,
private val sendStateTask: SendStateTask, private val sendStateTask: SendStateTask,
private val viaParameterFinder: ViaParameterFinder, private val viaParameterFinder: ViaParameterFinder,
private val searchTask: SearchTask) : private val searchTask: SearchTask
) :
Room, Room,
TimelineService by timelineService, TimelineService by timelineService,
SendService by sendService, SendService by sendService,
@ -85,7 +88,8 @@ internal class DefaultRoom(override val roomId: String,
RelationService by relationService, RelationService by relationService,
MembershipService by roomMembersService, MembershipService by roomMembersService,
RoomPushRuleService by roomPushRuleService, RoomPushRuleService by roomPushRuleService,
RoomAccountDataService by roomAccountDataService { RoomAccountDataService by roomAccountDataService,
RoomVersionService by roomVersionService {
override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> { override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> {
return roomSummaryDataSource.getRoomSummaryLive(roomId) return roomSummaryDataSource.getRoomSummaryLive(roomId)

View file

@ -369,4 +369,15 @@ internal interface RoomAPI {
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("type") type: String, @Path("type") type: String,
@Body content: JsonDict) @Body content: JsonDict)
/**
* Upgrades the given room to a particular room version.
* Errors:
* 400, The request was invalid. One way this can happen is if the room version requested is not supported by the homeserver
* (M_UNSUPPORTED_ROOM_VERSION)
* 403: The user is not permitted to upgrade the room.(M_FORBIDDEN)
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade")
suspend fun upgradeRoom(@Path("roomId") roomId: String,
@Body body: RoomUpgradeBody): RoomUpgradeResponse
} }

View file

@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.session.room.tags.DefaultTagsService
import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineService import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineService
import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService
import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService
import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService
import org.matrix.android.sdk.internal.session.search.SearchTask import org.matrix.android.sdk.internal.session.search.SearchTask
import javax.inject.Inject import javax.inject.Inject
@ -61,6 +62,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
private val relationServiceFactory: DefaultRelationService.Factory, private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory, private val membershipServiceFactory: DefaultMembershipService.Factory,
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory, private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
private val roomVersionServiceFactory: DefaultRoomVersionService.Factory,
private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory, private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
private val sendStateTask: SendStateTask, private val sendStateTask: SendStateTask,
private val viaParameterFinder: ViaParameterFinder, private val viaParameterFinder: ViaParameterFinder,
@ -87,6 +89,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
roomMembersService = membershipServiceFactory.create(roomId), roomMembersService = membershipServiceFactory.create(roomId),
roomPushRuleService = roomPushRuleServiceFactory.create(roomId), roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
roomAccountDataService = roomAccountDataServiceFactory.create(roomId), roomAccountDataService = roomAccountDataServiceFactory.create(roomId),
roomVersionService = roomVersionServiceFactory.create(roomId),
sendStateTask = sendStateTask, sendStateTask = sendStateTask,
searchTask = searchTask, searchTask = searchTask,
viaParameterFinder = viaParameterFinder viaParameterFinder = viaParameterFinder

View file

@ -92,6 +92,8 @@ import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask
import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask
import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask
import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask
import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionUpgradeTask
import org.matrix.android.sdk.internal.session.room.version.RoomVersionUpgradeTask
import org.matrix.android.sdk.internal.session.space.DefaultSpaceService import org.matrix.android.sdk.internal.session.space.DefaultSpaceService
import retrofit2.Retrofit import retrofit2.Retrofit
@ -243,4 +245,7 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask
@Binds
abstract fun bindRoomVersionUpgradeTask(task: DefaultRoomVersionUpgradeTask): RoomVersionUpgradeTask
} }

View file

@ -0,0 +1,26 @@
/*
* 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 com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class RoomUpgradeBody(
@Json(name = "new_version")
val newVersion: String
)

View file

@ -0,0 +1,26 @@
/*
* 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 com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class RoomUpgradeResponse(
@Json(name = "replacement_room")
val replacementRoomId: String
)

View file

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.room.alias package org.matrix.android.sdk.internal.session.room.alias
import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
@ -64,6 +65,6 @@ internal class RoomAliasAvailabilityChecker @Inject constructor(
} }
companion object { companion object {
internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.substringAfter(":") internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.getDomain()
} }
} }

View file

@ -142,7 +142,7 @@ internal class DefaultSendService @AssistedInject constructor(
// The image has not yet been sent // The image has not yet been sent
val attachmentData = ContentAttachmentData( val attachmentData = ContentAttachmentData(
size = messageContent.info!!.size, size = messageContent.info!!.size,
mimeType = messageContent.info.mimeType!!, mimeType = messageContent.mimeType,
width = messageContent.info.width.toLong(), width = messageContent.info.width.toLong(),
height = messageContent.info.height.toLong(), height = messageContent.info.height.toLong(),
name = messageContent.body, name = messageContent.body,
@ -169,7 +169,7 @@ internal class DefaultSendService @AssistedInject constructor(
is MessageFileContent -> { is MessageFileContent -> {
val attachmentData = ContentAttachmentData( val attachmentData = ContentAttachmentData(
size = messageContent.info!!.size, size = messageContent.info!!.size,
mimeType = messageContent.info.mimeType!!, mimeType = messageContent.mimeType,
name = messageContent.getFileName(), name = messageContent.getFileName(),
queryUri = Uri.parse(messageContent.url), queryUri = Uri.parse(messageContent.url),
type = ContentAttachmentData.Type.FILE type = ContentAttachmentData.Type.FILE
@ -181,7 +181,7 @@ internal class DefaultSendService @AssistedInject constructor(
val attachmentData = ContentAttachmentData( val attachmentData = ContentAttachmentData(
size = messageContent.audioInfo?.size ?: 0, size = messageContent.audioInfo?.size ?: 0,
duration = messageContent.audioInfo?.duration?.toLong() ?: 0L, duration = messageContent.audioInfo?.duration?.toLong() ?: 0L,
mimeType = messageContent.audioInfo?.mimeType, mimeType = messageContent.mimeType,
name = messageContent.body, name = messageContent.body,
queryUri = Uri.parse(messageContent.url), queryUri = Uri.parse(messageContent.url),
type = ContentAttachmentData.Type.AUDIO, type = ContentAttachmentData.Type.AUDIO,

View file

@ -26,8 +26,8 @@ import java.net.Socket
internal class HomeServerAvailabilityChecker(val sessionParams: SessionParams) { internal class HomeServerAvailabilityChecker(val sessionParams: SessionParams) {
fun check(): Boolean { fun check(): Boolean {
val host = sessionParams.homeServerConnectionConfig.homeServerUri.host ?: return false val host = sessionParams.homeServerConnectionConfig.homeServerUriBase.host ?: return false
val port = sessionParams.homeServerConnectionConfig.homeServerUri.port.takeIf { it != -1 } ?: 80 val port = sessionParams.homeServerConnectionConfig.homeServerUriBase.port.takeIf { it != -1 } ?: 80
val timeout = 30_000 val timeout = 30_000
try { try {
Socket().use { socket -> Socket().use { socket ->

View file

@ -199,7 +199,6 @@ internal class RoomSummaryUpdater @Inject constructor(
measureTimeMillis { measureTimeMillis {
val lookupMap = realm.where(RoomSummaryEntity::class.java) val lookupMap = realm.where(RoomSummaryEntity::class.java)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships()) .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
// we order by roomID to be consistent when breaking parent/child cycles // we order by roomID to be consistent when breaking parent/child cycles
.sort(RoomSummaryEntityFields.ROOM_ID) .sort(RoomSummaryEntityFields.ROOM_ID)
.findAll().map { .findAll().map {

View file

@ -0,0 +1,84 @@
/*
* 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.version
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import org.matrix.android.sdk.api.query.QueryStringValue
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.homeserver.RoomVersionStatus
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
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.version.RoomVersionService
import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
internal class DefaultRoomVersionService @AssistedInject constructor(
@Assisted private val roomId: String,
private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
private val stateEventDataSource: StateEventDataSource,
private val roomVersionUpgradeTask: RoomVersionUpgradeTask
) : RoomVersionService {
@AssistedFactory
interface Factory {
fun create(roomId: String): DefaultRoomVersionService
}
override fun getRoomVersion(): String {
return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty)
?.content
?.toModel<RoomCreateContent>()
?.roomVersion
// as per spec -> Defaults to "1" if the key does not exist.
?: DEFAULT_ROOM_VERSION
}
override suspend fun upgradeToVersion(version: String): String {
return roomVersionUpgradeTask.execute(
RoomVersionUpgradeTask.Params(
roomId = roomId,
newVersion = version
)
)
}
override fun getRecommendedVersion(): String {
return homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.roomVersions?.defaultRoomVersion ?: DEFAULT_ROOM_VERSION
}
override fun isUsingUnstableRoomVersion(): Boolean {
val versionCaps = homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.roomVersions
val currentVersion = getRoomVersion()
return versionCaps?.supportedVersion?.firstOrNull { it.version == currentVersion }?.status == RoomVersionStatus.UNSTABLE
}
override fun userMayUpgradeRoom(userId: String): Boolean {
val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
?.content?.toModel<PowerLevelsContent>()
?.let { PowerLevelsHelper(it) }
return powerLevelsHelper?.isUserAllowedToSend(userId, true, EventType.STATE_ROOM_TOMBSTONE) ?: false
}
companion object {
const val DEFAULT_ROOM_VERSION = "1"
}
}

View file

@ -0,0 +1,66 @@
/*
* 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.version
import io.realm.RealmConfiguration
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.session.room.RoomUpgradeBody
import org.matrix.android.sdk.internal.task.Task
import java.util.concurrent.TimeUnit
import javax.inject.Inject
internal interface RoomVersionUpgradeTask : Task<RoomVersionUpgradeTask.Params, String> {
data class Params(
val roomId: String,
val newVersion: String
)
}
internal class DefaultRoomVersionUpgradeTask @Inject constructor(
private val roomAPI: RoomAPI,
private val globalErrorReceiver: GlobalErrorReceiver,
@SessionDatabase
private val realmConfiguration: RealmConfiguration
) : RoomVersionUpgradeTask {
override suspend fun execute(params: RoomVersionUpgradeTask.Params): String {
val replacementRoomId = executeRequest(globalErrorReceiver) {
roomAPI.upgradeRoom(
roomId = params.roomId,
body = RoomUpgradeBody(params.newVersion)
)
}.replacementRoomId
// Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
tryOrNull {
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
realm.where(RoomSummaryEntity::class.java)
.equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId)
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
}
}
return replacementRoomId
}
}

View file

@ -86,6 +86,12 @@ internal class DefaultSpace(
) )
} }
override fun getChildInfo(roomId: String): SpaceChildContent? {
return room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
.firstOrNull()
?.content.toModel<SpaceChildContent>()
}
override suspend fun setChildrenOrder(roomId: String, order: String?) { override suspend fun setChildrenOrder(roomId: String, order: String?) {
val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
.firstOrNull() .firstOrNull()

View file

@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.wellknown
import android.util.MalformedJsonException import android.util.MalformedJsonException
import dagger.Lazy import dagger.Lazy
import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.WellKnown import org.matrix.android.sdk.api.auth.data.WellKnown
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
@ -39,7 +38,11 @@ import javax.net.ssl.HttpsURLConnection
internal interface GetWellknownTask : Task<GetWellknownTask.Params, WellknownResult> { internal interface GetWellknownTask : Task<GetWellknownTask.Params, WellknownResult> {
data class Params( data class Params(
val matrixId: String, /**
* domain, for instance "matrix.org"
* the URL will be https://{domain}/.well-known/matrix/client
*/
val domain: String,
val homeServerConnectionConfig: HomeServerConnectionConfig? val homeServerConnectionConfig: HomeServerConnectionConfig?
) )
} }
@ -54,14 +57,8 @@ internal class DefaultGetWellknownTask @Inject constructor(
) : GetWellknownTask { ) : GetWellknownTask {
override suspend fun execute(params: GetWellknownTask.Params): WellknownResult { override suspend fun execute(params: GetWellknownTask.Params): WellknownResult {
if (!MatrixPatterns.isUserId(params.matrixId)) {
return WellknownResult.InvalidMatrixId
}
val homeServerDomain = params.matrixId.substringAfter(":")
val client = buildClient(params.homeServerConnectionConfig) val client = buildClient(params.homeServerConnectionConfig)
return findClientConfig(homeServerDomain, client) return findClientConfig(params.domain, client)
} }
private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig?): OkHttpClient { private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig?): OkHttpClient {

View file

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

View file

@ -14,7 +14,7 @@ kapt {
// Note: 2 digits max for each value // Note: 2 digits max for each value
ext.versionMajor = 1 ext.versionMajor = 1
ext.versionMinor = 1 ext.versionMinor = 1
ext.versionPatch = 12 ext.versionPatch = 13
static def getGitTimestamp() { static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct' def cmd = 'git show -s --format=%ct'
@ -323,8 +323,8 @@ dependencies {
// Tests // Tests
def kluent_version = '1.67' def kluent_version = '1.67'
def androidxTest_version = '1.3.0' def androidxTest_version = '1.4.0'
def espresso_version = '3.3.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")
@ -343,7 +343,7 @@ dependencies {
implementation "androidx.fragment:fragment-ktx:$fragment_version" implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation 'androidx.constraintlayout:constraintlayout:2.1.0-beta02' implementation 'androidx.constraintlayout:constraintlayout:2.1.0-beta02'
implementation "androidx.sharetarget:sharetarget:1.1.0" implementation "androidx.sharetarget:sharetarget:1.1.0"
implementation 'androidx.core:core-ktx:1.5.0' implementation 'androidx.core:core-ktx:1.6.0'
implementation "androidx.media:media:1.3.1" implementation "androidx.media:media:1.3.1"
implementation "androidx.transition:transition:1.4.1" implementation "androidx.transition:transition:1.4.1"
@ -362,7 +362,7 @@ dependencies {
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.26' implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.27'
// rx // rx
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
@ -393,7 +393,7 @@ dependencies {
// 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.3.0' implementation 'com.google.android.material:material:1.4.0'
implementation 'me.gujun.android:span:1.7' implementation 'me.gujun.android:span:1.7'
implementation "io.noties.markwon:core:$markwon_version" implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:html:$markwon_version" implementation "io.noties.markwon:html:$markwon_version"
@ -501,7 +501,7 @@ dependencies {
androidTestImplementation "androidx.test:core:$androidxTest_version" androidTestImplementation "androidx.test:core:$androidxTest_version"
androidTestImplementation "androidx.test:runner:$androidxTest_version" androidTestImplementation "androidx.test:runner:$androidxTest_version"
androidTestImplementation "androidx.test:rules:$androidxTest_version" androidTestImplementation "androidx.test:rules:$androidxTest_version"
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version" androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version" androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version" androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version"

View file

@ -162,7 +162,6 @@ class VectorApplication :
// Do not display the name change popup // Do not display the name change popup
doNotShowDisclaimerDialog(this) doNotShowDisclaimerDialog(this)
} }
if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) {
val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!! val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!!
activeSessionHolder.setActiveSession(lastAuthenticatedSession) activeSessionHolder.setActiveSession(lastAuthenticatedSession)

View file

@ -40,12 +40,14 @@ import im.vector.app.features.debug.DebugMenuActivity
import im.vector.app.features.devtools.RoomDevToolActivity import im.vector.app.features.devtools.RoomDevToolActivity
import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.HomeActivity
import im.vector.app.features.home.HomeModule import im.vector.app.features.home.HomeModule
import im.vector.app.features.home.room.detail.JoinReplacementRoomBottomSheet
import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.app.features.home.room.detail.search.SearchActivity import im.vector.app.features.home.room.detail.search.SearchActivity
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.app.features.home.room.filtered.FilteredRoomsActivity import im.vector.app.features.home.room.filtered.FilteredRoomsActivity
import im.vector.app.features.home.room.list.RoomListModule import im.vector.app.features.home.room.list.RoomListModule
@ -193,6 +195,8 @@ interface ScreenComponent {
fun inject(bottomSheet: SpaceSettingsMenuBottomSheet) fun inject(bottomSheet: SpaceSettingsMenuBottomSheet)
fun inject(bottomSheet: InviteRoomSpaceChooserBottomSheet) fun inject(bottomSheet: InviteRoomSpaceChooserBottomSheet)
fun inject(bottomSheet: SpaceInviteBottomSheet) fun inject(bottomSheet: SpaceInviteBottomSheet)
fun inject(bottomSheet: JoinReplacementRoomBottomSheet)
fun inject(bottomSheet: MigrateRoomBottomSheet)
/* ========================================================================================== /* ==========================================================================================
* Others * Others

View file

@ -50,7 +50,15 @@ class UnrecognizedCertificateDialog @Inject constructor(
val userId = activeSessionHolder.getSafeActiveSession()?.myUserId val userId = activeSessionHolder.getSafeActiveSession()?.myUserId
val hsConfig = activeSessionHolder.getSafeActiveSession()?.sessionParams?.homeServerConnectionConfig ?: return val hsConfig = activeSessionHolder.getSafeActiveSession()?.sessionParams?.homeServerConnectionConfig ?: return
internalShow(activity, unrecognizedFingerprint, true, callback, userId, hsConfig.homeServerUri.toString(), hsConfig.allowedFingerprints.isNotEmpty()) internalShow(
activity = activity,
unrecognizedFingerprint = unrecognizedFingerprint,
existing = true,
callback = callback,
userId = userId,
homeServerUrl = hsConfig.homeServerUriBase.toString(),
homeServerConnectionConfigHasFingerprints = hsConfig.allowedFingerprints.isNotEmpty()
)
} }
/** /**
@ -60,7 +68,15 @@ class UnrecognizedCertificateDialog @Inject constructor(
unrecognizedFingerprint: Fingerprint, unrecognizedFingerprint: Fingerprint,
homeServerUrl: String, homeServerUrl: String,
callback: Callback) { callback: Callback) {
internalShow(activity, unrecognizedFingerprint, false, callback, null, homeServerUrl, false) internalShow(
activity = activity,
unrecognizedFingerprint = unrecognizedFingerprint,
existing = false,
callback = callback,
userId = null,
homeServerUrl = homeServerUrl,
homeServerConnectionConfigHasFingerprints = false
)
} }
/** /**

View file

@ -21,6 +21,7 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.features.call.dialpad.DialPadLookup import im.vector.app.features.call.dialpad.DialPadLookup
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.failure.MatrixIdFailure
import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import java.net.HttpURLConnection import java.net.HttpURLConnection
@ -39,9 +40,9 @@ class DefaultErrorFormatter @Inject constructor(
override fun toHumanReadable(throwable: Throwable?): String { override fun toHumanReadable(throwable: Throwable?): String {
return when (throwable) { return when (throwable) {
null -> null null -> null
is IdentityServiceError -> identityServerError(throwable) is IdentityServiceError -> identityServerError(throwable)
is Failure.NetworkConnection -> { is Failure.NetworkConnection -> {
when (throwable.ioException) { when (throwable.ioException) {
is SocketTimeoutException -> is SocketTimeoutException ->
stringProvider.getString(R.string.error_network_timeout) stringProvider.getString(R.string.error_network_timeout)
@ -54,7 +55,7 @@ class DefaultErrorFormatter @Inject constructor(
stringProvider.getString(R.string.error_no_network) stringProvider.getString(R.string.error_no_network)
} }
} }
is Failure.ServerError -> { is Failure.ServerError -> {
when { when {
throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> { throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> {
// Special case for terms and conditions // Special case for terms and conditions
@ -104,23 +105,25 @@ class DefaultErrorFormatter @Inject constructor(
} }
} }
} }
is Failure.OtherServerError -> { is Failure.OtherServerError -> {
when (throwable.httpCode) { when (throwable.httpCode) {
HttpURLConnection.HTTP_NOT_FOUND -> HttpURLConnection.HTTP_NOT_FOUND ->
// homeserver not found // homeserver not found
stringProvider.getString(R.string.login_error_no_homeserver_found) stringProvider.getString(R.string.login_error_no_homeserver_found)
HttpURLConnection.HTTP_UNAUTHORIZED -> HttpURLConnection.HTTP_UNAUTHORIZED ->
// uia errors? // uia errors?
stringProvider.getString(R.string.error_unauthorized) stringProvider.getString(R.string.error_unauthorized)
else -> else ->
throwable.localizedMessage throwable.localizedMessage
} }
} }
is DialPadLookup.Failure.NumberIsYours -> is DialPadLookup.Failure.NumberIsYours ->
stringProvider.getString(R.string.cannot_call_yourself) stringProvider.getString(R.string.cannot_call_yourself)
is DialPadLookup.Failure.NoResult -> is DialPadLookup.Failure.NoResult ->
stringProvider.getString(R.string.call_dial_pad_lookup_error) stringProvider.getString(R.string.call_dial_pad_lookup_error)
else -> throwable.localizedMessage is MatrixIdFailure.InvalidMatrixId ->
stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id)
else -> throwable.localizedMessage
} }
?: stringProvider.getString(R.string.unknown_error) ?: stringProvider.getString(R.string.unknown_error)
} }

View file

@ -41,7 +41,7 @@ fun SearchView.withoutLeftMargin() {
} }
fun EditText.hidePassword() { fun EditText.hidePassword() {
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
} }
fun View.getMeasurements(): Pair<Int, Int> { fun View.getMeasurements(): Pair<Int, Int> {

View file

@ -46,7 +46,7 @@ import java.util.concurrent.TimeUnit
/** /**
* Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment) * Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment)
*/ */
abstract class VectorBaseBottomSheetDialogFragment<VB: ViewBinding> : BottomSheetDialogFragment(), MvRxView { abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomSheetDialogFragment(), MvRxView {
private val mvrxViewIdProperty = MvRxViewId() private val mvrxViewIdProperty = MvRxViewId()
final override val mvrxViewId: String by mvrxViewIdProperty final override val mvrxViewId: String by mvrxViewIdProperty
@ -168,6 +168,10 @@ abstract class VectorBaseBottomSheetDialogFragment<VB: ViewBinding> : BottomShee
@CallSuper @CallSuper
override fun invalidate() { override fun invalidate() {
forceExpandState()
}
protected fun forceExpandState() {
if (showExpanded) { if (showExpanded) {
// Force the bottom sheet to be expanded // Force the bottom sheet to be expanded
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED bottomSheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED

View file

@ -0,0 +1,50 @@
/*
* 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.
*/
package im.vector.app.core.ui.list
import android.widget.ProgressBar
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
/**
* A generic progress bar item.
*/
@EpoxyModelClass(layout = R.layout.item_generic_progress)
abstract class GenericProgressBarItem : VectorEpoxyModel<GenericProgressBarItem.Holder>() {
@EpoxyAttribute
var progress: Int = 0
@EpoxyAttribute
var total: Int = 100
@EpoxyAttribute
var indeterminate: Boolean = false
override fun bind(holder: Holder) {
super.bind(holder)
holder.progressbar.progress = progress
holder.progressbar.max = total
holder.progressbar.isIndeterminate = indeterminate
}
class Holder : VectorEpoxyHolder() {
val progressbar by bind<ProgressBar>(R.id.genericProgressBar)
}
}

View file

@ -21,11 +21,12 @@ import android.graphics.Color
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.RelativeLayout import android.widget.LinearLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.text.italic import androidx.core.text.italic
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.error.ResourceLimitErrorFormatter import im.vector.app.core.error.ResourceLimitErrorFormatter
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import im.vector.app.databinding.ViewNotificationAreaBinding import im.vector.app.databinding.ViewNotificationAreaBinding
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
@ -44,7 +45,7 @@ class NotificationAreaView @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) { ) : LinearLayout(context, attrs, defStyleAttr) {
var delegate: Delegate? = null var delegate: Delegate? = null
private var state: State = State.Initial private var state: State = State.Initial
@ -69,12 +70,13 @@ class NotificationAreaView @JvmOverloads constructor(
cleanUp() cleanUp()
state = newState state = newState
when (newState) { when (newState) {
State.Initial -> Unit
is State.Default -> renderDefault() is State.Default -> renderDefault()
is State.Hidden -> renderHidden() is State.Hidden -> renderHidden()
is State.NoPermissionToPost -> renderNoPermissionToPost() is State.NoPermissionToPost -> renderNoPermissionToPost()
is State.Tombstone -> renderTombstone(newState) is State.Tombstone -> renderTombstone()
is State.ResourceLimitExceededError -> renderResourceLimitExceededError(newState) is State.ResourceLimitExceededError -> renderResourceLimitExceededError(newState)
} }.exhaustive
} }
// PRIVATE METHODS **************************************************************************************************************************************** // PRIVATE METHODS ****************************************************************************************************************************************
@ -125,15 +127,15 @@ class NotificationAreaView @JvmOverloads constructor(
setBackgroundColor(ContextCompat.getColor(context, backgroundColor)) setBackgroundColor(ContextCompat.getColor(context, backgroundColor))
} }
private fun renderTombstone(state: State.Tombstone) { private fun renderTombstone() {
visibility = View.VISIBLE visibility = View.VISIBLE
views.roomNotificationIcon.setImageResource(R.drawable.error) views.roomNotificationIcon.setImageResource(R.drawable.ic_warning_badge)
val message = span { val message = span {
+resources.getString(R.string.room_tombstone_versioned_description) +resources.getString(R.string.room_tombstone_versioned_description)
+"\n" +"\n"
span(resources.getString(R.string.room_tombstone_continuation_link)) { span(resources.getString(R.string.room_tombstone_continuation_link)) {
textDecorationLine = "underline" textDecorationLine = "underline"
onClick = { delegate?.onTombstoneEventClicked(state.tombstoneEvent) } onClick = { delegate?.onTombstoneEventClicked() }
} }
} }
views.roomNotificationMessage.movementMethod = BetterLinkMovementMethod.getInstance() views.roomNotificationMessage.movementMethod = BetterLinkMovementMethod.getInstance()
@ -177,6 +179,6 @@ class NotificationAreaView @JvmOverloads constructor(
* An interface to delegate some actions to another object * An interface to delegate some actions to another object
*/ */
interface Delegate { interface Delegate {
fun onTombstoneEventClicked(tombstoneEvent: Event) fun onTombstoneEventClicked()
} }
} }

View file

@ -56,7 +56,7 @@ class JitsiService @Inject constructor(
// Build data for a jitsi widget // Build data for a jitsi widget
val widgetId: String = WidgetType.Jitsi.preferred + "_" + session.myUserId + "_" + System.currentTimeMillis() val widgetId: String = WidgetType.Jitsi.preferred + "_" + session.myUserId + "_" + System.currentTimeMillis()
val preferredJitsiDomain = tryOrNull { val preferredJitsiDomain = tryOrNull {
rawService.getElementWellknown(session.myUserId) rawService.getElementWellknown(session.sessionParams)
?.jitsiServer ?.jitsiServer
?.preferredDomain ?.preferredDomain
} }

View file

@ -50,7 +50,8 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
CREATE_SPACE("/createspace", "<name> <invitee>*", R.string.command_description_create_space, true), CREATE_SPACE("/createspace", "<name> <invitee>*", R.string.command_description_create_space, true),
ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space, true), ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space, true),
JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space, true), JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space, true),
LEAVE_ROOM("/leave", "<roomId?>", R.string.command_description_leave_room, true); LEAVE_ROOM("/leave", "<roomId?>", R.string.command_description_leave_room, true),
UPGRADE_ROOM("/upgraderoom", "newVersion", R.string.command_description_upgrade_room, true);
val length val length
get() = command.length + 1 get() = command.length + 1

View file

@ -312,24 +312,32 @@ object CommandParser {
) )
} }
} }
Command.ADD_TO_SPACE.command -> { Command.ADD_TO_SPACE.command -> {
val rawCommand = textMessage.substring(Command.ADD_TO_SPACE.command.length).trim() val rawCommand = textMessage.substring(Command.ADD_TO_SPACE.command.length).trim()
ParsedCommand.AddToSpace( ParsedCommand.AddToSpace(
rawCommand rawCommand
) )
} }
Command.JOIN_SPACE.command -> { Command.JOIN_SPACE.command -> {
val spaceIdOrAlias = textMessage.substring(Command.JOIN_SPACE.command.length).trim() val spaceIdOrAlias = textMessage.substring(Command.JOIN_SPACE.command.length).trim()
ParsedCommand.JoinSpace( ParsedCommand.JoinSpace(
spaceIdOrAlias spaceIdOrAlias
) )
} }
Command.LEAVE_ROOM.command -> { Command.LEAVE_ROOM.command -> {
val spaceIdOrAlias = textMessage.substring(Command.LEAVE_ROOM.command.length).trim() val spaceIdOrAlias = textMessage.substring(Command.LEAVE_ROOM.command.length).trim()
ParsedCommand.LeaveRoom( ParsedCommand.LeaveRoom(
spaceIdOrAlias spaceIdOrAlias
) )
} }
Command.UPGRADE_ROOM.command -> {
val newVersion = textMessage.substring(Command.UPGRADE_ROOM.command.length).trim()
if (newVersion.isEmpty()) {
ParsedCommand.ErrorSyntax(Command.UPGRADE_ROOM)
} else {
ParsedCommand.UpgradeRoom(newVersion)
}
}
else -> { else -> {
// Unknown command // Unknown command
ParsedCommand.ErrorUnknownSlashCommand(slashCommand) ParsedCommand.ErrorUnknownSlashCommand(slashCommand)

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