diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 89a7c466fd..523496e317 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -7,15 +7,6 @@
-
diff --git a/CHANGES.md b/CHANGES.md
index a6221116b7..c1d3b8b733 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,53 @@
+Changes in Element 1.1.7 (2021-05-12)
+===================================================
+
+Features ✨:
+ - Spaces beta
+
+Improvements 🙌:
+ - Add ability to install APK from directly from Element (#2381)
+ - Delete and react to stickers (#3250)
+ - Compress video before sending (#442)
+ - Improve file too big error detection (#3245)
+ - User can now select video when selecting Gallery to send attachments to a room
+ - Add option to record a video from the camera
+ - Add the public icon on the rooms in the room list (#3292)
+
+Bugfix 🐛:
+ - Message states cosmetic changes (#3007)
+ - Fix exception in rxSingle (#3180)
+ - Do not invite the current user when creating a room (#3123)
+ - Fix color issues when the system theme is changed (#2738)
+ - Fix issues on Android 11 (#3067)
+ - Fix issue when opening encrypted files (#3186)
+ - Fix wording issue (#3242)
+ - Fix missing sender information after edits (#3184)
+ - Fix read marker not updating automatically (#3267)
+ - Sent video does not contains duration (#3272)
+ - Properly clean the back stack if the user cancel registration when waiting for email validation
+ - Fix read marker visibility/position when filtering some events
+ - Fix user invitation in case of restricted profile api (#3306)
+
+SDK API changes ⚠️:
+ - RegistrationWizard.createAccount() parameters are now all optional, following Matrix spec (#3205)
+
+Build 🧱:
+ - Upgrade to gradle 7
+ - https://github.com/Piasy/BigImageViewer is now hosted on mavenCentral()
+ - Upgrade Realm to version 10.4.0
+
+Other changes:
+ - New store descriptions
+ - `master` branch has been renamed to `main`. To apply change to your dev environment, run:
+```sh
+git branch -m master main
+git fetch origin
+git branch -u origin/main main
+# And optionally
+git remote prune origin
+```
+ - Allow cleartext (non-SSL) connections to Matrix servers on LAN hosts (#3166)
+
Changes in Element 1.1.6 (2021-04-16)
===================================================
diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle
index 5a8cce92e8..0ac8ac4a9a 100644
--- a/attachment-viewer/build.gradle
+++ b/attachment-viewer/build.gradle
@@ -17,20 +17,6 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
-buildscript {
- repositories {
- maven {
- url 'https://jitpack.io'
- content {
- // PhotoView
- includeGroupByRegex 'com\\.github\\.chrisbanes'
- }
- }
- jcenter()
- }
-
-}
-
android {
compileSdkVersion 30
@@ -69,7 +55,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
- implementation "androidx.recyclerview:recyclerview:1.2.0-rc01"
+ implementation "androidx.recyclerview:recyclerview:1.2.0"
implementation 'com.google.android.material:material:1.3.0'
}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index b8da6c3864..9c9e7a6c20 100644
--- a/build.gradle
+++ b/build.gradle
@@ -16,8 +16,8 @@ buildscript {
classpath 'com.google.gms:google-services:4.3.5'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1'
- classpath 'com.google.android.gms:oss-licenses-plugin:0.10.3'
- classpath "com.likethesalad.android:string-reference:1.2.1"
+ classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4'
+ classpath "com.likethesalad.android:string-reference:1.2.2"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -45,25 +45,20 @@ allprojects {
// PFLockScreen-Android
includeGroupByRegex 'com\\.github\\.vector-im'
- //Chat effects
+ // Chat effects
includeGroupByRegex 'com\\.github\\.jetradarmobile'
includeGroupByRegex 'nl\\.dionsegijn'
}
}
- maven {
- url "http://dl.bintray.com/piasy/maven"
- content {
- includeGroupByRegex "com\\.github\\.piasy"
- }
- }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
// Jitsi repo
maven {
- url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.1.0"
+ url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.1.0"
// Note: to test Jitsi release you can use a local file like this:
// url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.1.0"
}
google()
+ mavenCentral()
jcenter()
}
diff --git a/fastlane/metadata/android/ca/changelogs/40101020.txt b/fastlane/metadata/android/ca/changelogs/40101020.txt
new file mode 100644
index 0000000000..43c140214f
--- /dev/null
+++ b/fastlane/metadata/android/ca/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Canvis principals d'aquesta versió: millora de rendiment i correcció d'errors!
+Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/ca/changelogs/40101030.txt b/fastlane/metadata/android/ca/changelogs/40101030.txt
new file mode 100644
index 0000000000..9b2627e7f2
--- /dev/null
+++ b/fastlane/metadata/android/ca/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Canvis principals d'aquesta versió: millora de rendiment i correcció d'errors!
+Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/cs/changelogs/40101020.txt b/fastlane/metadata/android/cs/changelogs/40101020.txt
new file mode 100644
index 0000000000..5e90e49daa
--- /dev/null
+++ b/fastlane/metadata/android/cs/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: vylepšení výkonnosti a opravy chyb!
+Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/cs/changelogs/40101030.txt b/fastlane/metadata/android/cs/changelogs/40101030.txt
new file mode 100644
index 0000000000..0e624ba6d1
--- /dev/null
+++ b/fastlane/metadata/android/cs/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: vylepšení výkonnosti a opravy chyb!
+Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/cs/changelogs/40101040.txt b/fastlane/metadata/android/cs/changelogs/40101040.txt
new file mode 100644
index 0000000000..1818c92ba8
--- /dev/null
+++ b/fastlane/metadata/android/cs/changelogs/40101040.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: vylepšení výkonnosti a opravy chyb!
+Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.4
diff --git a/fastlane/metadata/android/cs/changelogs/40101050.txt b/fastlane/metadata/android/cs/changelogs/40101050.txt
new file mode 100644
index 0000000000..3e33cd8d07
--- /dev/null
+++ b/fastlane/metadata/android/cs/changelogs/40101050.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: nutné opravy pro 1.1.4
+Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.5
diff --git a/fastlane/metadata/android/cs/changelogs/40101060.txt b/fastlane/metadata/android/cs/changelogs/40101060.txt
new file mode 100644
index 0000000000..d148a9bc37
--- /dev/null
+++ b/fastlane/metadata/android/cs/changelogs/40101060.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: nutné opravy chyb pro 1.1.5!
+Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.6
diff --git a/fastlane/metadata/android/cs/short_description.txt b/fastlane/metadata/android/cs/short_description.txt
index a654a9ea6a..d14ac49efa 100644
--- a/fastlane/metadata/android/cs/short_description.txt
+++ b/fastlane/metadata/android/cs/short_description.txt
@@ -1 +1 @@
-Zabezpečený decentralizovaný chat a VoIP. Uchovejte svá data v bezpečí.
+Skupinový messenger - šifrovaná komunikace, skupinový chat a video hovory
diff --git a/fastlane/metadata/android/cs/title.txt b/fastlane/metadata/android/cs/title.txt
index 76943289ad..2fedfecb15 100644
--- a/fastlane/metadata/android/cs/title.txt
+++ b/fastlane/metadata/android/cs/title.txt
@@ -1 +1 @@
-Element (dříve Riot.im)
+Element - bezpečný messenger
diff --git a/fastlane/metadata/android/de/changelogs/40100110.txt b/fastlane/metadata/android/de/changelogs/40100110.txt
index 24bc6e518c..120f04a3f9 100644
--- a/fastlane/metadata/android/de/changelogs/40100110.txt
+++ b/fastlane/metadata/android/de/changelogs/40100110.txt
@@ -1,2 +1,2 @@
-Diese neue Version enthält hauptsächlich Verbesserungen der Benutzeroberfläche und der Handhabung. Du kannst jetzt ganz schnell Freund*innen einladen und DMs erstellen, indem du schlicht einen QR-Code scannst.
+Diese neue Version enthält hauptsächlich Verbesserungen der Benutzeroberfläche und der Handhabung. Du kannst jetzt ganz schnell Freunde einladen und DMs erstellen, indem du schlicht einen QR-Code scannst.
Vollständige Versionshinweise: https://github.com/vector-im/element-android/releases/tag/v1.0.11
diff --git a/fastlane/metadata/android/de/changelogs/40101020.txt b/fastlane/metadata/android/de/changelogs/40101020.txt
new file mode 100644
index 0000000000..32fabf7c2f
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Hauptänderungen in dieser Version: Leistungsverbesserung und Fehlerbehebungen!
+Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/de/changelogs/40101030.txt b/fastlane/metadata/android/de/changelogs/40101030.txt
new file mode 100644
index 0000000000..7e6dc25033
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Hauptänderungen in dieser Version: Leistungsverbesserung und Fehlerbehebungen!
+Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/de/changelogs/40101040.txt b/fastlane/metadata/android/de/changelogs/40101040.txt
new file mode 100644
index 0000000000..eaa8db1409
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/40101040.txt
@@ -0,0 +1,2 @@
+Hauptänderungen in dieser Version: Leistungsverbesserung und Fehlerbehebungen!
+Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.1.4
diff --git a/fastlane/metadata/android/de/changelogs/40101050.txt b/fastlane/metadata/android/de/changelogs/40101050.txt
new file mode 100644
index 0000000000..de6352961a
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/40101050.txt
@@ -0,0 +1,2 @@
+Hauptänderungen in dieser Version: Wichtige Fehlerbehebungen für 1.1.4!
+Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.1.5
diff --git a/fastlane/metadata/android/de/changelogs/40101060.txt b/fastlane/metadata/android/de/changelogs/40101060.txt
new file mode 100644
index 0000000000..775c016b3d
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/40101060.txt
@@ -0,0 +1,2 @@
+Hauptänderungen in dieser Version: Wichtige Fehlerbehebungen für 1.1.5!
+Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.1.6
diff --git a/fastlane/metadata/android/de/full_description.txt b/fastlane/metadata/android/de/full_description.txt
index 568ae61875..69343cf0e3 100644
--- a/fastlane/metadata/android/de/full_description.txt
+++ b/fastlane/metadata/android/de/full_description.txt
@@ -1,30 +1,39 @@
-Element ist eine neuartige Messaging- und Kollaborationsapp:
+Element ist mehr als ein sicherer Messenger. Es ist ein produktives Kolaborationsapp für das Team und eignet sich ideal für den Gruppenchat beim Arbeiten von zuhause aus. Mit eingebauter Ende-zu-Ende-Verschlüsselung ermöglicht Element umfangreiche und sichere Videokonferenzen, das Teilen von Dokumenten/Dateien und Videoanrufe.
-1. Volle Kontrolle über deine Privatssphäre
-2. Kommuniziere mit jedem aus dem Matrix-Netzwerk und mit der Integration von z.B. Slack sogar über Matrix hinaus
-3. Schutz vor Werbung, Datamining und geschlossenen Platformen
-4. Absicherung durch Ende-zu-Ende-Verschlüsselung, und Cross-Signing um andere zu verifizieren
+Element enthält folgende Funktionen:
+- Fortgeschrittene Werkzeuge für die Online-Kommunikation
+- Vollverschlüsselte Nachrichten um eine sichere Kommunikation innerhalb und außerhalb des Unternehmens zu ermöglichen
+- Dezentralisierte Chats basierend auf das quelloffene Matrix-Framework
+- Sichere und kontrollierte Dateienfreigabe durch verschlüsselte Daten beim verwalten von Projekten
+- Videochats über VoIP und Bildschirmübertragung
+- Einfache Einbindungen mit Ihren favorisierten Online-Kolaborationswerkzeugen, Projektverwaltungswerkzeugen, VoIP-Diensten und andere Kommunikationsapps für Ihren Team
-Element unterscheidet sich durch Dezentralität und Open Source deutlich von anderen Messaging- und Kollaborationsapps.
+Element unterscheidet sich deutlich von anderen Kommunikations- und Kollaborationsapps. Es läuft auf Matrix, ein offenes Netzwerk für eine sichere und dezentralisierte Kommunikation. Es erlaubt den Nutzern ihre eigenen Matrix-Dienste zu betreiben und gibt ihnen damit die vollständige Kontrolle und Besitz über ihre eigenen Daten und Nachrichten.
-Element ermöglicht es einen eigenen Server zu betreiben - oder einen beliebigen auszuwählen, sodass du nicht nur Privatssphäre gewinnst, sondern auch deine Daten und Konversationen in deiner Hand sind und du sie kontrollieren kannst. Du hast Zugriff auf ein offenes Netzwerk, und kannst daher nicht nur mit Element-Nutzern schreiben. Und es ist sehr sicher.
+Privatsphäre/Datenschutz und verschlüsselte Kommunikation
+Element schützt Ihnen vor unerwünschte Werbung, das Datenschürfen und geschlossene unentkommbare Dienste. Auch schützt es all Ihre Daten, Video und Sprachkommunikation unter vier Augen durch Ende-zu-Ende-Verschlüsselung und das Quersignieren von Geräten zur Verifizierung.
-Element ist zu all diesem in der Lage, weil es Matrix nutzt - einen Standard für offene, dezentrale Kommunikation.
+Element gibt Ihnen die Kontrolle über Ihre Privatsphäre, während es Ihnen ermöglicht mit jeden auf dem Matrix-Netzwerk oder andere geschäftliche Kollaborationswerkzeuge durch das Einbinden von Apps wie Slack sicher zu kommunizieren.
-Element gibt dir die Kontrolle, indem es dir die Wahl darüber lässt, wer deine Konversationen hostet. In der Element-App kannst du zwischen verschiedenen Möglichkeiten auswählen:
+Element kann man selber betreiben
+Um mehr Kontrolle über Ihre sensiblen Daten und Konversationen zu ermöglichen, kann man Element selbst betreiben oder Sie wählen irgendeinen Matrix-basierten Dienst - der standard für quelloffene, dezentralisierte Kommunikation. Element gibt Ihnen Privatsphäre, Sicherheitskonformität und die Flexibilität zum Integrieren.
+Besitzen Sie Ihre Daten
+Sie entscheiden wo Sie Ihre Daten und Nachrichten aufbewahren, ohne das Risiko des Datenschürfens oder des Zugriffes Dritter.
+
+Element gibt Ihnen die Kontrolle durch verschiedene Wege:
1. Kostenlos auf dem öffentlichen matrix.org Server registrieren, der von den Matrix-Entwicklern gehostet wird, oder wähle aus Tausenden von öffentlichen Servern, die von Freiwilligen gehostet werden
-2. Einen Konto auf einem eigenen Server auf eigener Hardware betreiben
+2. Einen Konto auf einem eigenen Server in der eigenen IT-Infrastruktur betreiben
3. Einen Konto auf einem benutzerdefinierten Server erstellen, zum Beispiel durch ein Abonnement bei Element Matrix Services (kurz EMS)
-Wieso Element nutzen?
+Offene Kommunikation und Zusammenarbeit
+Sie können mit jeden auf dem Matrix-Netzwerk chatten, egal ob sie Element, eine Matrix-App oder sogar eine andere Kommunikationsapp nutzen.
-BESITZE DEINE DATEN: Du entscheidest wo deine Daten und Nachrichten gespeichert werden. Du besitzt und kontrollierst sie, anstatt ein Großkonzern, der deine Daten analysiert und Dritten Zugriff gibt.
+Super sicher
+Reale Ende-zu-Ende-Verschlüsselung (nur die Personen in der Konversation können die Nachricht entschüsseln) und Quersignierung von Geräten zur Verifizierung.
-OFFENE KOMMUNIKATION UND KOLLABORATION: Du kannst mit jedem im Matrix-Netzwerk schreiben, ob sie nun Element oder eine andere Matrix-App nutzen, oder gar ein anderes Kommunikationssystem wie z.B. Slack, IRC oder XMPP.
+Vollständige Kommunikation und Integration
+Kurznachrichten, Sprach- und Videoanrufe, kontrollierte Dateifreigaben, Bildschirmübertragungen und eine ganze Reihe an Integrationen, Bots and Widgets. Schaffe Räume, Gemeinschaften, bleibe auf dem Laufenden und erledige Sachen.
-SUPER SICHER: Echte Ende-zu-Ende-Verschlüsselung (nur Personen in der Konversation können die Nachrichten entschlüsseln), und Cross-Signing um die Geräte der anderen Personen zu verifizieren.
-
-VOLLSTÄNDIGE KOMMUNIKATION: Nachrichten, Telefonate und Videoanrufe, Teilen von Dateien oder dem eigenen Bildschirm und viele andere Integrationen, Bots und Widgets. Erstelle Räume, Communities, bleib in Kontakt und sei produktiv.
-
-ÜBERALL WO DU BIST: Bleib in Kontakt wo auch immer du bist - mit einem vollständig synchronisierten Nachrichtenverlauf über alle Geräte und im Netz auf https://app.element.io.
+Das Stehengelassene später wieder aufgreifen
+Bleibe auf dem Laufenden, egal wo Sie sind, mit vollständig synchronisierter Nachrichtenverlauf quer über all Ihrer Geräte und im Netz auf https://app.element.io
diff --git a/fastlane/metadata/android/de/short_description.txt b/fastlane/metadata/android/de/short_description.txt
index d2c30d4167..d27bd3ef12 100644
--- a/fastlane/metadata/android/de/short_description.txt
+++ b/fastlane/metadata/android/de/short_description.txt
@@ -1 +1 @@
-Sicherer dezentraler Chat und Telefonie. Schütze deine Daten vor Dritten.
+Gruppen-Messenger - verschlüsselte Kommunikation, Gruppenchat und Videoanrufe
diff --git a/fastlane/metadata/android/de/title.txt b/fastlane/metadata/android/de/title.txt
index ec25e8a631..6304f37925 100644
--- a/fastlane/metadata/android/de/title.txt
+++ b/fastlane/metadata/android/de/title.txt
@@ -1 +1 @@
-Element (zuvor Riot.im)
+Element - Sicherer Messenger
diff --git a/fastlane/metadata/android/en-US/changelogs/40101070.txt b/fastlane/metadata/android/en-US/changelogs/40101070.txt
new file mode 100644
index 0000000000..254e4c0f62
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40101070.txt
@@ -0,0 +1,2 @@
+Main changes in this version: beta support for Spaces. Compress video before sending.
+Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.7
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
index e939b75bb7..853885944c 100644
--- a/fastlane/metadata/android/en-US/full_description.txt
+++ b/fastlane/metadata/android/en-US/full_description.txt
@@ -1,30 +1,39 @@
-Element is a new type of messenger and collaboration app that:
+Element is both a secure messenger and a productivity team collaboration app that is ideal for group chats while remote working. This chat app uses end-to-end encryption to provide powerful video conferencing, file sharing and voice calls.
-1. Puts you in control to preserve your privacy
-2. Lets you communicate with anyone in the Matrix network, and even beyond by integrating with apps such as Slack
-3. Protects you from advertising, datamining and walled gardens
-4. Secures you through end-to-end encryption, with cross-signing to verify others
+Element’s features include:
+- Advanced online communication tools
+- Fully encrypted messages to allow safer corporate communication, even for remote workers
+- Decentralized chat based on the Matrix open source framework
+- File sharing securely with encrypted data while managing projects
+- Video chats with Voice over IP and screen sharing
+- Easy integration with your favourite online collaboration tools, project management tools, VoIP services and other team messaging apps
-Element is completely different from other messaging and collaboration apps because it is decentralised and open source.
+Element is completely different from other messaging and collaboration apps. It operates on Matrix, an open network for secure messaging and decentralized communication. It allows self-hosting to give users maximum ownership and control of their data and messages.
-Element lets you self-host - or choose a host - so that you have privacy, ownership and control of your data and conversations. It gives you access to an open network; so you’re not just stuck speaking to other Element users only. And it is very secure.
+Privacy and encrypted messaging
+Element protects you from unwanted ads, data mining and walled gardens. It also secures all your data, one-to-one video and voice communication through end-to-end encryption and cross-signed device verification.
-Element is able to do all this because it operates on Matrix - the standard for open, decentralised communication.
+Element gives you control over your privacy while allowing you to communicate securely with anyone on the Matrix network, or other business collaboration tools by integrating with apps such as Slack.
-Element puts you in control by letting you choose who hosts your conversations. From the Element app, you can choose to host in different ways:
+Element can be self-hosted
+To allow more control of your sensitive data and conversations, Element can be self-hosted or you can choose any Matrix-based host - the standard for open source, decentralized communication. Element gives you privacy, security compliance and integration flexibility.
+Own your data
+You decide where to keep your data and messages. Without the risk of data mining or access from third parties.
+
+Element puts you in control in different ways:
1. Get a free account on the matrix.org public server hosted by the Matrix developers, or choose from thousands of public servers hosted by volunteers
-2. Self-host your account by running a server on your own hardware
+2. Self-host your account by running a server on your own IT infrastructure
3. Sign up for an account on a custom server by simply subscribing to the Element Matrix Services hosting platform
-Why choose Element?
+Open messaging and collaboration
+You can chat with anyone on the Matrix network, whether they’re using Element, another Matrix app or even if they are using a different messaging app.
-OWN YOUR DATA: You decide where to keep your data and messages. You own it and control it, not some MEGACORP that mines your data or gives access to third parties.
+Super secure
+Real end-to-end encryption (only those in the conversation can decrypt messages), and cross-signed device verification.
-OPEN MESSAGING AND COLLABORATION: You can chat with anyone else in the Matrix network, whether they’re using Element or another Matrix app, and even if they are using a different messaging system of the likes of Slack, IRC or XMPP.
+Complete communication and integration
+Messaging, voice and video calls, file sharing, screen sharing and a whole bunch of integrations, bots and widgets. Build rooms, communities, stay in touch and get things done.
-SUPER-SECURE: Real end-to-end encryption (only those in the conversation can decrypt messages), and cross-signing to verify the devices of conversation participants.
-
-COMPLETE COMMUNICATION: Messaging, voice and video calls, file sharing, screen sharing and a whole bunch of integrations, bots and widgets. Build rooms, communities, stay in touch and get things done.
-
-EVERYWHERE YOU ARE: Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://app.element.io.
+Pick up where you left off
+Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://app.element.io
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt
index 023b366c9a..5a98f6f772 100644
--- a/fastlane/metadata/android/en-US/short_description.txt
+++ b/fastlane/metadata/android/en-US/short_description.txt
@@ -1 +1 @@
-Secure decentralised chat & VoIP. Keep your data safe from third parties.
\ No newline at end of file
+Group messenger - encrypted messaging, group chat and video calls
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt
index 039da1fc3b..12fa89b99b 100644
--- a/fastlane/metadata/android/en-US/title.txt
+++ b/fastlane/metadata/android/en-US/title.txt
@@ -1 +1 @@
-Element (previously Riot.im)
\ No newline at end of file
+Element - Secure Messenger
\ No newline at end of file
diff --git a/fastlane/metadata/android/et/changelogs/40101020.txt b/fastlane/metadata/android/et/changelogs/40101020.txt
new file mode 100644
index 0000000000..5f34bb579f
--- /dev/null
+++ b/fastlane/metadata/android/et/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Põhilised muutused selles versioonis: jõudluse parandused ja pisikohendused.
+Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/et/changelogs/40101030.txt b/fastlane/metadata/android/et/changelogs/40101030.txt
new file mode 100644
index 0000000000..5a558d911a
--- /dev/null
+++ b/fastlane/metadata/android/et/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Põhilised muutused selles versioonis: jõudluse parandused ja pisikohendused.
+Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/et/changelogs/40101040.txt b/fastlane/metadata/android/et/changelogs/40101040.txt
new file mode 100644
index 0000000000..2e7849db9e
--- /dev/null
+++ b/fastlane/metadata/android/et/changelogs/40101040.txt
@@ -0,0 +1,2 @@
+Põhilised muutused selles versioonis: jõudluse parandused ja pisikohendused.
+Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.5
diff --git a/fastlane/metadata/android/et/changelogs/40101050.txt b/fastlane/metadata/android/et/changelogs/40101050.txt
new file mode 100644
index 0000000000..9aa2f4e10c
--- /dev/null
+++ b/fastlane/metadata/android/et/changelogs/40101050.txt
@@ -0,0 +1,2 @@
+Põhilised muutused selles versioonis: kiirparandused versioon 1.1.4 jaoks.
+Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.5
diff --git a/fastlane/metadata/android/et/changelogs/40101060.txt b/fastlane/metadata/android/et/changelogs/40101060.txt
new file mode 100644
index 0000000000..0577bf452d
--- /dev/null
+++ b/fastlane/metadata/android/et/changelogs/40101060.txt
@@ -0,0 +1,2 @@
+Põhilised muutused selles versioonis: kiirparandused versioon 1.1.5 jaoks.
+Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.6
diff --git a/fastlane/metadata/android/et/full_description.txt b/fastlane/metadata/android/et/full_description.txt
index 7c7f7195a8..ee0adef9ac 100644
--- a/fastlane/metadata/android/et/full_description.txt
+++ b/fastlane/metadata/android/et/full_description.txt
@@ -1,30 +1,39 @@
-Element on uut tüüpi suhtlus- ja koostöörakendus, mis:
+Element on nii suhtlus- ja koostöörakendus, mis sobib ideaalselt rühmavestlusteks kaugtöö ajal. Läbiv krüptimine on kasutusel sõnumivahetuseks, videokõnedeks, häälkõnedeks ja failide jagamiseks.
-1. Võimaldab täielikku kontrolli privaatsuse üle
-2. Võimaldab suhelda kõigiga Matrixi võrgus ja isegi väljaspool seda, olles integreeritud selliste rakendustega nagu Slack
-3. Kaitseb sind reklaamide ja andmekogumise eest
-4. Tagab turvalisuse läbiva krüptimise abil, kasutades risttunnustamist vestlejate tuvastamiseks
+Element pakub muu hulgas selliseid võimalusi
+- moodsad võrgupõhised suhtlusvahendid
+- läbivalt krüptitud sõnumid võimaldavad turvalist suhtlust, sealhulgas kaugtöötajatega
+- Matrix'i protokollil põhinev hajutatud suhtlusvõrk
+- projektide jaoks vajalike failide jagamine turvaliselt ja krüptitult
+- VoIP'i põhised videokõned ja ekraani jagamine
+- lihtne lõimimine harjumispäraste võrgupõhiste koostöövahenditega, projektihalduse rakendustega, VoIP-teenustega ja muude ühistöörakendustega
-Element erineb täielikult teistest sõnumside- ja koostöörakendustest, kuna see on detsentraliseeritud ja avatud lähtekoodiga.
+Element erineb teistest sõnumi- ja koostöörakendustest. Tema aluseks on Matrix - avatud võrk turvalise ja hajutatud suhtluse jaoks.
-Element võimaldab ise hostida - või valida hosti -, et oleks tagatud privaatsus ja kontroll oma andmete ja vestluste üle. Element annab ka juurdepääsu avatud võrgule, nii et te ei pea vaid Elemendi kasutajatega rääkima. Ning kogu süsteem on väga turvaline.
+Privaatsus ja krüptitud sõnumivahetus
+Element tagab, et sa ei ole seotud reklaamidega, andmekogumisega ja suletud süsteemidega. Kasutades läbivat krüptimist ja risttunnustamisel põhinevat verifitseerimist on sinu sõnumid, andmed, kahepoolsed videokõned ja häälkõned turvatud.
-Element töötab Matrixil - avatud, detsentraliseeritud suhtlus-standardil.
+Lubades suhelda turvaliselt ükspuha kellega Matrix'i võrgus või muude ärikeskondades kasutatavate koostöörakendustega nagu Slack, jätab Element sulle kontrolli oma privaatsuse üle.
-Võimaldades valida, kes vestlusi korraldab, annab Element annab kontrolli sinule. Rakendust Element saad kasutada mitmel viisil.
+Võid kasutada Element'i jaoks oma serverid
+Kui vajad suuremat kontrolli oma suhtluse ja andmete üle, siis võid kasutada oma serverit või tellida teenuse ükspuha missuguselt Matrix'i-teenuse pakkujalt. Matrix on standard avatud lähtekoodil põhineva detsentraliseeritud suhtluse jaoks.
-1. Tasuta konto Matrixi arendajate hostitud avalikus serveris matrix.org või vali tuhandete avalike serverite hulgast, mida haldavad vabatahtlikud
-2. Hosti oma kontot ise, paigaldades serveri oma riistvarale
-3. Registreeruge serveris olevale kontole, tellides Element Matrix Services teenuseplatvormi
+Andmed on Sinu omad
+Sina otsustad seda, kus hoiad oma sõnumeid ja andmeid. Ning seejuures puudub andmekaevandamise risk ja ligipääs kolmandatele osapooltele.
- Miks valida element?
+Element annab kontrolli sinule valikuga mitme võimaluse vahel:
+1. tasuta konto Matrix'i arendajate hostitud avalikus serveris matrix.org või vali tuhandete avalike serverite hulgast, mida haldavad vabatahtlikud
+2. hosti oma kontot ise, paigaldades serveri oma IT-taristule
+3. telli tasuline kasutajakonto Element Matrix Services teenuseplatvormilt
- KONTROLL ANDMETE ÜLE: otsustad ise, kus oma andmeid ja sõnumeid hoida. Need kuuluvad sulle ja sinu käes on kontroll, mitte mõne MEGAFIRMA käes, mis andmeid oma kasuks kaevandab või kolmandatele isikutele juurdepääsu annab.
+Avatud suhtlus ja koostöö
+Saad vestelda kõigi teistega Matrix'i võrgus, olenemata sellest, kas nad kasutavad Elementi või mõnda muud Matrixi rakendust ja isegi kui nad kasutavad mõnda teistsugust suhtlussüsteemi.
- AVATUD SUHTLUS JA KOOSTÖÖ : saad vestelda kõigi teistega Matrixi võrgus, olenemata sellest, kas nad kasutavad Elementi või mõnda muud Matrixi rakendust, ja isegi kui nad kasutavad teistsugust suhtlussüsteemi nagu Slack, IRC või XMPP.
+Üliturvaline
+Tõeline läbiv krüptimine (ainult vestluses osalejad saavad sõnumeid lugeda) ja risttunnustamine vestluses osalejate tuvastamiseks.
- ÜLITURVALINE : tõeline läbiv krüptimine (ainult vestluses osalejad saavad sõnumeid lugeda) ja risttunnustamine vestluses osalejate tuvastamiseks.
+Kõik suhtlusvõimalused
+Sõnumid, hääl- ja videokõned, failide jagamine, ekraani jagamine ja terve hulk lõiminguid, roboteid ja vidinaid. Loo tubasid, kogukondi, hoia ühendust ja saa asjad aetud.
- KÕIK SUHTLUSVÕIMALUSED: sõnumid, hääl- ja videokõned, failide jagamine, ekraani jagamine ja terve hulk lõiminguid, roboteid ja vidinaid. Loo tubasid, kogukondi, hoia ühendust ja saa asjad aetud.
-
- KÕIKJAL, KUS VIIBITE: saad suhelda kõigis oma seadmetes ja ka veebis aadressil https://app.element.io ning sealjuures täielikult sünkroonitud sõnumite ajalooga.
+Jätka sealt, kus pooleli jäid
+Saad suhelda kõigis oma seadmetes ja ka veebis aadressil https://app.element.io ning sealjuures täielikult sünkroonitud sõnumite ajalooga.
diff --git a/fastlane/metadata/android/et/short_description.txt b/fastlane/metadata/android/et/short_description.txt
index 4075c1f7cf..5352464a7a 100644
--- a/fastlane/metadata/android/et/short_description.txt
+++ b/fastlane/metadata/android/et/short_description.txt
@@ -1 +1 @@
-Turvalised ning hajutatud vestlused ja VoIP-kõned. Sinu suhtlus on üliturvaline.
+Vestlus- ja koostöörakendus: krüptitud sõnumid, rühmavestlused ja videokõned
diff --git a/fastlane/metadata/android/et/title.txt b/fastlane/metadata/android/et/title.txt
index f74f9ff18f..b0bf39ba23 100644
--- a/fastlane/metadata/android/et/title.txt
+++ b/fastlane/metadata/android/et/title.txt
@@ -1 +1 @@
-Element (varem Riot.im)
+Element - turvaline sõnumiklient
diff --git a/fastlane/metadata/android/fr/changelogs/40101020.txt b/fastlane/metadata/android/fr/changelogs/40101020.txt
new file mode 100644
index 0000000000..7bce8ac19a
--- /dev/null
+++ b/fastlane/metadata/android/fr/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : amélioration des performances et corrections de bugs !
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/fr/changelogs/40101030.txt b/fastlane/metadata/android/fr/changelogs/40101030.txt
new file mode 100644
index 0000000000..93f0b9e7fb
--- /dev/null
+++ b/fastlane/metadata/android/fr/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : amélioration des performances et corrections de bugs !
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/fr/changelogs/40101040.txt b/fastlane/metadata/android/fr/changelogs/40101040.txt
new file mode 100644
index 0000000000..0af0cf55d8
--- /dev/null
+++ b/fastlane/metadata/android/fr/changelogs/40101040.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : amélioration des performances et corrections de bugs !
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.4
diff --git a/fastlane/metadata/android/fr/changelogs/40101050.txt b/fastlane/metadata/android/fr/changelogs/40101050.txt
new file mode 100644
index 0000000000..dd6e8468af
--- /dev/null
+++ b/fastlane/metadata/android/fr/changelogs/40101050.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : Corrections de bugs sur la 1.1.4
+Liste de tous les changements : https://github.com/vector-im/element-android/releases/tag/v1.1.5
diff --git a/fastlane/metadata/android/fr/changelogs/40101060.txt b/fastlane/metadata/android/fr/changelogs/40101060.txt
new file mode 100644
index 0000000000..e10a8aa4f3
--- /dev/null
+++ b/fastlane/metadata/android/fr/changelogs/40101060.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : Corrections de bugs sur la 1.1.5
+Liste de tous les changements : https://github.com/vector-im/element-android/releases/tag/v1.1.6
diff --git a/fastlane/metadata/android/fr/full_description.txt b/fastlane/metadata/android/fr/full_description.txt
index 2b17d8f846..066b94868b 100644
--- a/fastlane/metadata/android/fr/full_description.txt
+++ b/fastlane/metadata/android/fr/full_description.txt
@@ -1,30 +1,30 @@
-Element est une nouvelle application de messagerie et de collaboration qui :
+Element est une nouvelle application de messagerie et de collaboration qui :
-1) Vous place aux commandes de votre vie privée
-2) Vous permet de communiquer avec n'importe qui du réseau Matrix, et plus encore par des intégrations d'autres applications comme Slack ou Discord
-3) Vous protège de la publicité et de la collecte de données
-4) Vous sécurise grâce à du chiffrement bout-à-bout, avec de la signature croisée pour authentifier les autres utilisateurs
+1. Vous permet de préserver votre vie privée
+2. Vous permet de communiquer avec n’importe qui sur réseau Matrix, et plus encore grâce aux intégrations d’autres applications telles que Slack ou Discord
+3. Vous protège de la publicité et de la collecte de données
+4. Vous protège grâce au chiffrement de bout-à-bout et à la signature croisée pour authentifier les autres utilisateurs
-Element est complètement différent des autres applications de messagerie et de collaboration puisque l'application est décentralisée et open-source.
+Element est complètement différente des autres applications de messagerie et de collaboration puisque l’application est décentralisée et open-source.
-Element vous permet d'héberger vous-même -ou de choisir un hôte- vous permettant d'assurer votre vie privée, la propriété et le contrôle de vos données et de vos conversations. Cela vous offre l'accès à un réseau ouvert, vous n'êtes donc pas condamné à parler à d'autres utilisateurs d'Element seulement. Et c'est très sécurisé.
+Element vous permet d’héberger vous-même ou de choisir un hôte vous permettant d’assurer 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 à d’autres utilisateurs d’Element seulement. Et c'est très sécurisé.
-Element peut faire tout ça car il est basé sur Matrix, le protocole standard pour la communication ouverte et décentralisée.
+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 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 :
+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 :
-1) Créer un compte gratuit sur le serveur public matrix.org hébergé par les développeurs de Matrix, ou choisir parler les milliers de serveurs public hébergés par des bénévoles
-2) Héberger vous-même votre compte en installant un serveur sur votre propre machine
-3) Créer un compte sur un serveur personnalisé en souscrivant sur la plateforme d'hébergement « Element Matrix Services » (EMS)
+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
+2. Héberger vous-même votre compte en installant un serveur sur votre propre machine
+3. Créer un compte sur un serveur personnalisé en souscrivant sur la plateforme d'hébergement « Element Matrix Services » (EMS)
Pourquoi choisir Element ?
-POSSÉDEZ VOS DONNÉES : Vous décidez où conserver vos données et vos messages. Vous les possédez et vous les contrôlez, et non une MEGACORP qui mine vos données ou les donnent à des tiers
+VOS DONNÉES VOUS APPARTIENNENT : 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.
-UNE MESSAGERIE OUVERTE ET COLLABORATIVE : Vous pouvez discuter avec n'importe qui sur le réseau Matrix, qu'ils utilisent Element ou une autre application basée sur Matrix, et même s'ils utilisent un système de messagerie différent comment Slack, Discord, IRC ou XMPP.
+MESSAGERIE ET COLLABORATION OUVERTES : vous pouvez discuter avec tout le réseau Matrix, qu’ils utilisent Element ou une autre application Matrix, même s’ils utilisent une autre plateforme de messagerie telle que Slack, IRC ou XMPP.
-SUPER SÉCURISÉ : Un réel chiffrement bout-à-bout (seulement ceux deux la conversation peuvent déchiffrer les messages), et une signature croisée pour vérifier les appareils des participants de la conversation.
+ULTRA SÉCURISÉ : chiffrement de bout en bout (seuls les membres d’une conversation peuvent déchiffrer les messages), et signature croisée pour vérifier les appareils de vos interlocuteurs.
-COMMUNICATION COMPLÈTE : Messagerie, appels vocaux et vidéo, transfert de fichiers, partage d'écran et un tas d'intégrations, robots et widgets. Construisez des salons, des communautés, restez en contact et accomplissez de grandes choses.
+TOUTES VOS COMMUNICATIONS : messagerie, appels audio et vidéo, partage de fichier, partage d’écran et un grand nombre d’intégrations, robots et widgets. Participez à des salons, des communautés, restez en contact et faites avancer vos projets.
-PARTOUT OÙ VOUS ÊTES : Restez connectés peu import où vous êtes avec la synchronisation complète de l'historique des messages sur tous vos appareils et sur le web sur https://app.element.io.
+PARTOUT AVEC VOUS : votre historique reste synchronisé entre tous vos appareils et sur le web sur https://element.io/app.
diff --git a/fastlane/metadata/android/fr/short_description.txt b/fastlane/metadata/android/fr/short_description.txt
index 2fb9762e97..6d86b77a6b 100644
--- a/fastlane/metadata/android/fr/short_description.txt
+++ b/fastlane/metadata/android/fr/short_description.txt
@@ -1 +1 @@
-Chat & VoIP sûr et décentralisé. Gardez vos données en sécurité.
+Messagerie de groupes - messages chiffrés, groupés et appels vidéos
diff --git a/fastlane/metadata/android/fr/title.txt b/fastlane/metadata/android/fr/title.txt
index 87cbc3b858..9152e92cfa 100644
--- a/fastlane/metadata/android/fr/title.txt
+++ b/fastlane/metadata/android/fr/title.txt
@@ -1 +1 @@
-Element (anciennement Riot.im)
+Element - Messagerie sécurisée
diff --git a/fastlane/metadata/android/fy/changelogs/40100100.txt b/fastlane/metadata/android/fy/changelogs/40100100.txt
new file mode 100644
index 0000000000..dc2eda9234
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40100100.txt
@@ -0,0 +1,2 @@
+Disse nije ferzje bestjit foar in grut diel út breksoplossings en ferbetteringen. Berjochten stjoere giet no in soad flugger.
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.0.10
diff --git a/fastlane/metadata/android/fy/changelogs/40100110.txt b/fastlane/metadata/android/fy/changelogs/40100110.txt
new file mode 100644
index 0000000000..8249b5c409
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40100110.txt
@@ -0,0 +1,2 @@
+Disse nije ferzje bestjit foar in grut diel út brûkersinterfaasje en brûkersûnderfingsferbetteringen. No kinst freonen útnûgje, en gau DM's meitsje troch QR koades te scannen.
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.0.11
diff --git a/fastlane/metadata/android/fy/changelogs/40100120.txt b/fastlane/metadata/android/fy/changelogs/40100120.txt
new file mode 100644
index 0000000000..9605ce2a75
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40100120.txt
@@ -0,0 +1,2 @@
+Haadferoaring yn disse ferzje: URL ynsjen, nij emoji toetseboerd, nij keamer ynstellings moochlikheden, en snie foar kryst.
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.0.12
diff --git a/fastlane/metadata/android/fy/changelogs/40100130.txt b/fastlane/metadata/android/fy/changelogs/40100130.txt
new file mode 100644
index 0000000000..ebc7285193
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40100130.txt
@@ -0,0 +1,2 @@
+Haadferoaring yn disse ferzje: URL ynsjen, nij emoji toetseboerd, nij keamer ynstellings moochlikheden, en snie foar kryst.
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.0.13
diff --git a/fastlane/metadata/android/fy/changelogs/40100140.txt b/fastlane/metadata/android/fy/changelogs/40100140.txt
new file mode 100644
index 0000000000..6886848889
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40100140.txt
@@ -0,0 +1,2 @@
+Haadferoaring yn disse ferzje: Keamer fjochten feroarje, automatysk ljocht/tsjuster tema, en breksferbetteringen.
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.0.14
diff --git a/fastlane/metadata/android/fy/changelogs/40100150.txt b/fastlane/metadata/android/fy/changelogs/40100150.txt
new file mode 100644
index 0000000000..d291da0475
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40100150.txt
@@ -0,0 +1,2 @@
+Haadferoaring yn disse ferzje: Stipe foar sosjaal ynlogge!
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.0.15
diff --git a/fastlane/metadata/android/fy/changelogs/40100160.txt b/fastlane/metadata/android/fy/changelogs/40100160.txt
new file mode 100644
index 0000000000..eb2be2d09c
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40100160.txt
@@ -0,0 +1,2 @@
+Haadferoaring yn disse ferzje: Stipe foar sosjaal ynlogge!
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16
diff --git a/fastlane/metadata/android/fy/changelogs/40100170.txt b/fastlane/metadata/android/fy/changelogs/40100170.txt
new file mode 100644
index 0000000000..281b087584
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40100170.txt
@@ -0,0 +1,2 @@
+Haadferoaring yn disse ferzje: Breksoplossings!
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.0.17
diff --git a/fastlane/metadata/android/fy/changelogs/40101000.txt b/fastlane/metadata/android/fy/changelogs/40101000.txt
new file mode 100644
index 0000000000..a860b7b468
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40101000.txt
@@ -0,0 +1,2 @@
+Haadferoaring yn disse ferzje: VoIP (lûds en video skilje yn DM) ferbetteringen en breksoplossings!
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.1.0
diff --git a/fastlane/metadata/android/fy/changelogs/40101010.txt b/fastlane/metadata/android/fy/changelogs/40101010.txt
new file mode 100644
index 0000000000..2369fc4d22
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40101010.txt
@@ -0,0 +1,2 @@
+Haadferoaring yn disse ferzje: Prestaasje feroaringen en breksoplossings!
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.1.1
diff --git a/fastlane/metadata/android/fy/changelogs/40101020.txt b/fastlane/metadata/android/fy/changelogs/40101020.txt
new file mode 100644
index 0000000000..13aa172071
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Haadferoaring yn disse ferzje: Prestaasje feroaringen en breksoplossings!
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/fy/changelogs/40101030.txt b/fastlane/metadata/android/fy/changelogs/40101030.txt
new file mode 100644
index 0000000000..595cbdf104
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Haadferoaring yn disse ferzje: Prestaasje feroaringen en breksoplossings!
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/fy/changelogs/40101040.txt b/fastlane/metadata/android/fy/changelogs/40101040.txt
new file mode 100644
index 0000000000..e86e7ce3e3
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40101040.txt
@@ -0,0 +1,2 @@
+Haadferoaring yn disse ferzje: Prestaasje feroaringen en breksoplossings!
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.1.4
diff --git a/fastlane/metadata/android/fy/changelogs/40101050.txt b/fastlane/metadata/android/fy/changelogs/40101050.txt
new file mode 100644
index 0000000000..fdf5f98eb7
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40101050.txt
@@ -0,0 +1,2 @@
+Haadferoaring yn disse ferzje: feroaringen foar 1.1.4
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.1.5
diff --git a/fastlane/metadata/android/fy/changelogs/40101060.txt b/fastlane/metadata/android/fy/changelogs/40101060.txt
new file mode 100644
index 0000000000..47ac5692d5
--- /dev/null
+++ b/fastlane/metadata/android/fy/changelogs/40101060.txt
@@ -0,0 +1,2 @@
+Haadferoaring yn disse ferzje: feroaringen foar 1.1.5
+Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.1.6
diff --git a/fastlane/metadata/android/fy/short_description.txt b/fastlane/metadata/android/fy/short_description.txt
new file mode 100644
index 0000000000..ddc559b59c
--- /dev/null
+++ b/fastlane/metadata/android/fy/short_description.txt
@@ -0,0 +1 @@
+Groepsberjochtetsjinst - fersifere berjochten, groeps petearen en fideo skilje
diff --git a/fastlane/metadata/android/fy/title.txt b/fastlane/metadata/android/fy/title.txt
new file mode 100644
index 0000000000..c4b5b596fc
--- /dev/null
+++ b/fastlane/metadata/android/fy/title.txt
@@ -0,0 +1 @@
+Element - Feilige Berjochtetsjinst
diff --git a/fastlane/metadata/android/it/changelogs/40101020.txt b/fastlane/metadata/android/it/changelogs/40101020.txt
new file mode 100644
index 0000000000..21057629e3
--- /dev/null
+++ b/fastlane/metadata/android/it/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Modifiche principali in questa versione: prestazioni migliorate e correzione di errori!
+Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/it/changelogs/40101030.txt b/fastlane/metadata/android/it/changelogs/40101030.txt
new file mode 100644
index 0000000000..a62c4a0736
--- /dev/null
+++ b/fastlane/metadata/android/it/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Modifiche principali in questa versione: prestazioni migliorate e correzioni di errori!
+Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/it/changelogs/40101040.txt b/fastlane/metadata/android/it/changelogs/40101040.txt
new file mode 100644
index 0000000000..93aac046a1
--- /dev/null
+++ b/fastlane/metadata/android/it/changelogs/40101040.txt
@@ -0,0 +1,2 @@
+Modifiche principali in questa versione: prestazioni migliorate e correzione di errori!
+Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.4
diff --git a/fastlane/metadata/android/it/changelogs/40101050.txt b/fastlane/metadata/android/it/changelogs/40101050.txt
new file mode 100644
index 0000000000..7fa5ba20df
--- /dev/null
+++ b/fastlane/metadata/android/it/changelogs/40101050.txt
@@ -0,0 +1,2 @@
+Modifiche principali in questa versione: correzioni per la 1.1.4
+Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.5
diff --git a/fastlane/metadata/android/it/changelogs/40101060.txt b/fastlane/metadata/android/it/changelogs/40101060.txt
new file mode 100644
index 0000000000..d915bf2d8f
--- /dev/null
+++ b/fastlane/metadata/android/it/changelogs/40101060.txt
@@ -0,0 +1,2 @@
+Modifiche principali in questa versione: correzioni per la 1.1.5
+Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.6
diff --git a/fastlane/metadata/android/it/full_description.txt b/fastlane/metadata/android/it/full_description.txt
index b6f7cf449c..dd7716ffbf 100644
--- a/fastlane/metadata/android/it/full_description.txt
+++ b/fastlane/metadata/android/it/full_description.txt
@@ -1,30 +1,39 @@
-Element è un nuovo tipo di app di messaggistica e collaborazione che:
+Element è sia un messenger sicuro sia un'app collaborativa per team di produttività, ideale per chat di gruppo durante il lavoro da remoto. Questa app usa una crittografia end-to-end per fornire videoconferenze, condivisione di file e videochiamate.
-1. Ti mette al controllo per preservare la tua privacy
-2. Ti lascia comunicare con chiunque nella rete Matrix e oltre, integrandosi con app come Slack
-3. Ti protegge da pubblicità, raccolta di dati e piattaforme chiuse
-4. Ti protegge con la crittografia end-to-end, con la firma incrociata per verificare gli altri
+Tra le caratteristiche di Element ci sono:
+- Strumenti di comunicazione online avanzati
+- Messaggi totalmente cifrati per consentire comunicazioni aziendali più sicure, anche per i lavoratori remoti
+- Chat decentralizzate basate sull'infrastruttura open source Matrix
+- Condivisione sicura di file con dati crittografati durante la gestione dei progetti
+- Videochiamate con "Voice over IP" e condivisione dello schermo
+- Facile integrazione con i tuoi strumenti collaborativi online preferiti, strumenti di gestione progetti, servizi VoIP ed altre app di messaggistica tra team
-Element è completamente diverso dalle altre app di messaggistica e collaborazione perchè è decentralizzato e open source.
+Element è completamente diverso dalle altre app di messaggistica e collaborazione. Funziona su Matrix, una rete aperta per messaggi sicuri e comunicazioni decentralizzate. Può essere installato in locale per dare agli utenti il pieno possesso e controllo dei propri dati e messaggi.
-Element può essere gestito in locale - o puoi scegliere un host - in modo che tu abbia privacy, possesso e controllo dei tuoi dati e conversazioni. Ti dà accesso ad una rete aperta, quindi non sei limitato a parlare solo con altri utenti Element. Ed è molto sicuro.
+Privacy e messaggi privati
+Element ti protegge da pubblicità indesiderate, dalla raccolta di dati e dalle piattaforme chiuse. Protegge tutti i tuoi dati e comunicazioni uno-ad-uno, attraverso la crittografia end-to-end e la verifica a firma incrociata tra dispositivi.
-Element può fare tutto ciò perchè funziona su Matrix - lo standard per comunicazioni aperte e decentralizzate.
+Element ti dà il controllo della tua privacy consentendoti di comunicazre in modo sicuro con chiunque nella rete di Matrix, o con altri strumenti collaborativi aziendali, integrandosi con app come Slack.
-Element ti mette al controllo lasciandoti scegliere chi gestisce il server delle tue conversazioni. Dall'app Element, hai diverse opzioni:
+Element può essere installato in locale
+Per consentire un maggiore controllo dei tuoi dati sensibili e delle conversazioni, Element può essere gestito in locale o puoi scegliere un qualsiasi host basato su Matrix - lo standard per le comunicazioni open source e decentralizzate. Element ti offre privacy, conformità alla sicurezza e flessibilità di integrazione.
+Possiedi i tuoi dati
+Decidi tu dove tenere i tuoi dati e messaggi. Senza il rischio di raccolta di dati o accessi da terze parti.
+
+Element ti mette al controllo in diversi modi:
1. Crea un account gratuito sul server pubblico matrix.org gestito dagli sviluppatori di Matrix, o scegli tra migliaia di server pubblici gestiti da volontari
-2. Gestisci autonomamente un account installando un server sul tuo hardware
+2. Gestisci autonomamente un account installando un server nella tua infrastruttura informatica
3. Registra un account su un server personalizzato iscrivendoti alla piattaforma Element Matrix Services
-Perchè scegliere Element?
+Messaggistica e collaborazioni aperte
+Puoi chattare con chiunque nella rete Matrix, sia che stiano usando Element, un'altra app Matrix, o anche un'app di messaggistica diversa.
-POSSIEDI I TUOI DATI: decidi dove tenere i tuoi dati e messaggi. Sono tuoi e li controlli tu, non qualche MEGADITTA che raccoglie i tuoi dati o ne dà l'accesso a terze parti.
+Super sicuro
+Vera crittografia end-to-end (solo chi è nella conversazione può decifrare i messaggi) e verifica di dispositivi a firma incrociata.
-MESSAGGISTICA E COLLABORAZIONE APERTE: puoi chattare con chiunque nella rete Matrix, usando Element o un'altra app Matrix, o anche se si sta usando un sistema di messaggistica diverso come Slack, IRC o XMPP.
+Comunicazioni ed integrazioni complete
+Messaggi, chiamate audio e video, condivisione file e schermo, un vasto numero di integrazioni, bot e widget. Crea stanze, comunità, resta in contatto e porta a termine gli obiettivi.
-SUPER SICURO: vera crittografia end-to-end (solo chi è nella conversazione può decifrare i messaggi) e firma incrociata per verificare i dispositivi dei partecipanti.
-
-COMUNICAZIONE COMPLETA: messaggi, chiamate audio e video, condivisione file e schermo, un vasto numero di integrazioni, bot e widget. Crea stanze, comunità, resta in contatto e porta a termine gli impegni.
-
-OVUNQUE TU SIA: resta in contatto ovunque tu sia con la cronologia dei messaggi sincronizzata tra tutti i tuoi dispositivi e in rete su https://app.element.io.
+Riprendi da dove ti eri fermato
+Resta in contatto ovunque tu sia con la cronologia dei messaggi sincronizzata tra tutti i tuoi dispositivi e in rete su https://app.element.io
diff --git a/fastlane/metadata/android/it/short_description.txt b/fastlane/metadata/android/it/short_description.txt
index 8c0c8fbee0..5050d0c1c5 100644
--- a/fastlane/metadata/android/it/short_description.txt
+++ b/fastlane/metadata/android/it/short_description.txt
@@ -1 +1 @@
-Chat e VoIP decentralizzati sicuri. Tieni lontani i tuoi dati dalle terze parti.
+Messenger di gruppo - messaggi cifrati, chat di gruppo e videochiamate
diff --git a/fastlane/metadata/android/it/title.txt b/fastlane/metadata/android/it/title.txt
index 54e3b456c7..c426a480d3 100644
--- a/fastlane/metadata/android/it/title.txt
+++ b/fastlane/metadata/android/it/title.txt
@@ -1 +1 @@
-Element (ex Riot.im)
+Element - Messaggi sicuri
diff --git a/fastlane/metadata/android/ja/changelogs/40100100.txt b/fastlane/metadata/android/ja/changelogs/40100100.txt
new file mode 100644
index 0000000000..8359a12964
--- /dev/null
+++ b/fastlane/metadata/android/ja/changelogs/40100100.txt
@@ -0,0 +1,2 @@
+今回の新バージョンでは、主にバグの修正と改善が行われています。メッセージの送信がより速くなりました。
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.10
diff --git a/fastlane/metadata/android/ja/changelogs/40100110.txt b/fastlane/metadata/android/ja/changelogs/40100110.txt
new file mode 100644
index 0000000000..c93db421af
--- /dev/null
+++ b/fastlane/metadata/android/ja/changelogs/40100110.txt
@@ -0,0 +1,2 @@
+今回の新バージョンでは、主にUI(ユーザーインターフェース)とUX(ユーザーエクスペリエンス)の向上が図られています。友達を招待したり、QRコードを読み取って素早くDMを作成できるようになりました。
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.11
diff --git a/fastlane/metadata/android/ja/changelogs/40100120.txt b/fastlane/metadata/android/ja/changelogs/40100120.txt
new file mode 100644
index 0000000000..aace2ef79f
--- /dev/null
+++ b/fastlane/metadata/android/ja/changelogs/40100120.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: URLプレビュー、新しい絵文字、新しいルーム設定機能、それにクリスマスには雪が!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.12
diff --git a/fastlane/metadata/android/ja/changelogs/40100130.txt b/fastlane/metadata/android/ja/changelogs/40100130.txt
new file mode 100644
index 0000000000..97633621c5
--- /dev/null
+++ b/fastlane/metadata/android/ja/changelogs/40100130.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: URLプレビュー、新しい絵文字、新しいルーム設定機能、それにクリスマスには雪が!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.13
diff --git a/fastlane/metadata/android/ja/changelogs/40100140.txt b/fastlane/metadata/android/ja/changelogs/40100140.txt
new file mode 100644
index 0000000000..c340663127
--- /dev/null
+++ b/fastlane/metadata/android/ja/changelogs/40100140.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: 部屋の許可、自動のテーマ切替、そして多くのバグを修正しました。
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.14
diff --git a/fastlane/metadata/android/ja/changelogs/40100150.txt b/fastlane/metadata/android/ja/changelogs/40100150.txt
new file mode 100644
index 0000000000..42f28c7bea
--- /dev/null
+++ b/fastlane/metadata/android/ja/changelogs/40100150.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: ソーシャルログインに対応しました。
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.15
diff --git a/fastlane/metadata/android/ja/changelogs/40100160.txt b/fastlane/metadata/android/ja/changelogs/40100160.txt
new file mode 100644
index 0000000000..8b5196998a
--- /dev/null
+++ b/fastlane/metadata/android/ja/changelogs/40100160.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16
diff --git a/fastlane/metadata/android/ja/changelogs/40100170.txt b/fastlane/metadata/android/ja/changelogs/40100170.txt
new file mode 100644
index 0000000000..586b01cb2b
--- /dev/null
+++ b/fastlane/metadata/android/ja/changelogs/40100170.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: バグの修正!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.17
diff --git a/fastlane/metadata/android/ja/changelogs/40101000.txt b/fastlane/metadata/android/ja/changelogs/40101000.txt
new file mode 100644
index 0000000000..25bbd7ab87
--- /dev/null
+++ b/fastlane/metadata/android/ja/changelogs/40101000.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.0
diff --git a/fastlane/metadata/android/ja/changelogs/40101010.txt b/fastlane/metadata/android/ja/changelogs/40101010.txt
new file mode 100644
index 0000000000..35ba933069
--- /dev/null
+++ b/fastlane/metadata/android/ja/changelogs/40101010.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.1
diff --git a/fastlane/metadata/android/ja/changelogs/40101020.txt b/fastlane/metadata/android/ja/changelogs/40101020.txt
new file mode 100644
index 0000000000..88e3c79ca8
--- /dev/null
+++ b/fastlane/metadata/android/ja/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/ja/changelogs/40101030.txt b/fastlane/metadata/android/ja/changelogs/40101030.txt
new file mode 100644
index 0000000000..87d191b226
--- /dev/null
+++ b/fastlane/metadata/android/ja/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/ja/full_description.txt b/fastlane/metadata/android/ja/full_description.txt
new file mode 100644
index 0000000000..855eb309c9
--- /dev/null
+++ b/fastlane/metadata/android/ja/full_description.txt
@@ -0,0 +1,30 @@
+Elementはまったく新しいタイプのメッセンジャーアプリです。
+
+1. あなた自身がプライバシーをコントロールすることを可能にします。
+2. Matrixネットワークにいる誰とでも通信できることはもちろん、Slackなどのアプリとの連携によって他のネットワークとも通信ができます。
+3. 広告、データ収集、バックドア、ユーザーの囲い込みから逃れることができます。
+4. エンドツーエンド暗号化とクロス署名によってあなたを保護します。
+
+Elementは非中央集権型でオープンソースであるため、他のメッセンジャーアプリとは完全に異なっています。
+
+Elementはあなた自身でサーバーをホストすることも、サーバーを選ぶこともできます。これによってあなたのデータと会話に関するプライバシーや所有権はあなた自身で管理できるようになります。さらに、あなたは他のElementユーザーと話せるだけでなくオープンネットワークへのアクセスも可能です。とてもセキュアです。
+
+Elementは、オープンな分散型通信の標準規格であるMatrixで動作するため、これらすべてを実現することができています。
+
+Elementではあなたの会話をどのサーバーでホストするか決めることができます。アプリでは、さまざまな方法で選択できます。
+
+1. matrix.orgの公開サーバーで無料のアカウントを取得します。
+2. あなた自身のハードウェアでサーバーを動かし、アカウントを管理します。
+3. Element Matrix Servicesのホスティングプラットフォームに登録することで、カスタムサーバー上のアカウントを取得できます。
+
+なぜElementを選ぶべきなのか?
+
+データの所有権: 自分でデータやメッセージを保管する場所を決めることができます。あなたが所有権を持ってコントロールすることで、第三者にあなたのデータを渡したり、ビッグデータを収集する巨大テック企業に依存する必要がなくなります。
+
+開かれたネットワークと共同作業: Matrixネットワーク内の他の誰とでも、あるいはElementや他のMatrixアプリを使っているかどうかに関わらず、またSlack、IRC、XMPPのような他のメッセージングシステムを使っているかどうかに関わらず、チャットすることができます。
+
+はるかに安全: 本物のエンドツーエンド暗号化(会話に参加している者のみがメッセージを読める)と会話参加者の真正性を確認するためクロス署名によって。
+
+完全なるコミュニケーションの訪れ: テキスト、音声通話、ビデオ通話、ファイル共有、画面共有、連携機能、ボット、ウィジェットなどのコミュニケーションに必要な機能の全てが実装されています。ルームやコミュニティを立ち上げて連絡を取り合い、物事をスムーズに成し遂げることができます。
+
+いつでもどこでも!: すべてのデバイスとウェブ(https://app.element.io)でメッセージの履歴が完全に同期されるため、どこにいても連絡を取ることができます。
diff --git a/fastlane/metadata/android/ja/short_description.txt b/fastlane/metadata/android/ja/short_description.txt
new file mode 100644
index 0000000000..c3991b7a93
--- /dev/null
+++ b/fastlane/metadata/android/ja/short_description.txt
@@ -0,0 +1 @@
+安全な分散型チャットとVoIP。あなたの情報が第三者から守られます。
diff --git a/fastlane/metadata/android/ja/title.txt b/fastlane/metadata/android/ja/title.txt
new file mode 100644
index 0000000000..376f4a95de
--- /dev/null
+++ b/fastlane/metadata/android/ja/title.txt
@@ -0,0 +1 @@
+Element(エレメントメッセンジャー)
diff --git a/fastlane/metadata/android/nb-NO/changelogs/40100120.txt b/fastlane/metadata/android/nb-NO/changelogs/40100120.txt
new file mode 100644
index 0000000000..163cd64cdc
--- /dev/null
+++ b/fastlane/metadata/android/nb-NO/changelogs/40100120.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: URL-forhåndsvisning, nytt Emoji-tastatur, nye rominnstillingsmuligheter og snø til jul!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.12
diff --git a/fastlane/metadata/android/nb-NO/changelogs/40100130.txt b/fastlane/metadata/android/nb-NO/changelogs/40100130.txt
new file mode 100644
index 0000000000..23ab42ef2c
--- /dev/null
+++ b/fastlane/metadata/android/nb-NO/changelogs/40100130.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: URL-forhåndsvisning, nytt Emoji-tastatur, nye rominnstillingsmuligheter og snø til jul!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.13
diff --git a/fastlane/metadata/android/nb-NO/changelogs/40100140.txt b/fastlane/metadata/android/nb-NO/changelogs/40100140.txt
new file mode 100644
index 0000000000..10a3d9b925
--- /dev/null
+++ b/fastlane/metadata/android/nb-NO/changelogs/40100140.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: Rediger romtillatelser, automatisk lys/mørkt tema og en haug med feilrettinger.
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.14
diff --git a/fastlane/metadata/android/nb-NO/changelogs/40100150.txt b/fastlane/metadata/android/nb-NO/changelogs/40100150.txt
new file mode 100644
index 0000000000..3237da115d
--- /dev/null
+++ b/fastlane/metadata/android/nb-NO/changelogs/40100150.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: Sosial innloggingsstøtte.
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.15
diff --git a/fastlane/metadata/android/nb-NO/changelogs/40100160.txt b/fastlane/metadata/android/nb-NO/changelogs/40100160.txt
new file mode 100644
index 0000000000..5502fd3ab1
--- /dev/null
+++ b/fastlane/metadata/android/nb-NO/changelogs/40100160.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: Sosial innloggingsstøtte.
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.15 og https://github.com/vector-im/element-android/releases/tag/v1.0.16
diff --git a/fastlane/metadata/android/nb-NO/changelogs/40100170.txt b/fastlane/metadata/android/nb-NO/changelogs/40100170.txt
new file mode 100644
index 0000000000..f9174a2ee4
--- /dev/null
+++ b/fastlane/metadata/android/nb-NO/changelogs/40100170.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: Feilrettinger!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.17
diff --git a/fastlane/metadata/android/nb-NO/changelogs/40101000.txt b/fastlane/metadata/android/nb-NO/changelogs/40101000.txt
new file mode 100644
index 0000000000..370dbb36ce
--- /dev/null
+++ b/fastlane/metadata/android/nb-NO/changelogs/40101000.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: forbedring av VoIP (lyd og videosamtaler i DM) og feilrettinger!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.0
diff --git a/fastlane/metadata/android/nb-NO/changelogs/40101010.txt b/fastlane/metadata/android/nb-NO/changelogs/40101010.txt
new file mode 100644
index 0000000000..c6109b8d9b
--- /dev/null
+++ b/fastlane/metadata/android/nb-NO/changelogs/40101010.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: forbedring av ytelsen og feilrettinger!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.1
diff --git a/fastlane/metadata/android/nb-NO/changelogs/40101020.txt b/fastlane/metadata/android/nb-NO/changelogs/40101020.txt
new file mode 100644
index 0000000000..9464c6fb0f
--- /dev/null
+++ b/fastlane/metadata/android/nb-NO/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: forbedring av ytelsen og feilrettinger!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/nb-NO/changelogs/40101030.txt b/fastlane/metadata/android/nb-NO/changelogs/40101030.txt
new file mode 100644
index 0000000000..1e12246e9a
--- /dev/null
+++ b/fastlane/metadata/android/nb-NO/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: forbedring av ytelsen og feilrettinger!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/nb-NO/full_description.txt b/fastlane/metadata/android/nb-NO/full_description.txt
new file mode 100644
index 0000000000..92a3c4c5c3
--- /dev/null
+++ b/fastlane/metadata/android/nb-NO/full_description.txt
@@ -0,0 +1,30 @@
+Element er en ny type messenger og samarbeidsapp som:
+
+1. Gir deg kontrollen for å bevare personvernet ditt
+2. Lar deg kommunisere med hvem som helst i Matrix-nettverket, og til og med ved å integrere med apper som Slack
+3. Beskytter deg mot reklame, datamining og inngjerdede hager
+4. Sikrer deg gjennom end-to-end-kryptering, med kryssignering for å bekrefte andre
+
+Element er helt forskjellig fra andre meldings- og samarbeidsapper fordi det er desentralisert og åpen kildekode.
+
+Element lar deg selv være vert - eller velge en vert - slik at du har personvern, eierskap og kontroll over dataene og samtalene dine. Det gir deg tilgang til et åpent nettverk; slik at du ikke bare holder på å snakke med bare andre Element-brukere. Og det er veldig sikkert.
+
+Element er i stand til å gjøre alt dette fordi det opererer på Matrix - standarden for åpen, desentralisert kommunikasjon.
+
+Element setter deg i kontroll ved å la deg velge hvem som er vert for samtalene dine. Fra Element-appen kan du velge å være vert på forskjellige måter:
+
+1. Få en gratis konto på matrix.org-serveren som er vert for Matrix-utviklerne, eller velg blant tusenvis av offentlige servere som er vert for frivillige
+2. Vær vert for kontoen din ved å kjøre en server på din egen maskinvare
+3. Registrer deg for en konto på en tilpasset server ved å bare abonnere på Hosting Matrix Services-vertsplattformen
+
+ Hvorfor velge Element?
+
+ EGNE DATA DINE : Du bestemmer hvor du vil oppbevare dataene og meldingene dine. Du eier den og kontrollerer den, ikke noe MEGACORP som utvinner dataene dine eller gir tilgang til tredjeparter.
+
+ ÅPEN MELDING OG SAMARBEID : Du kan chatte med alle andre i Matrix-nettverket, enten de bruker Element eller en annen Matrix-app, og selv om de bruker et annet meldingssystem som Slack, IRC eller XMPP.
+
+ SUPER-SECURE : Ekte end-to-end-kryptering (bare de i samtalen kan dekryptere meldinger), og kryssignering for å verifisere enhetene til samtaledeltakerne.
+
+ KOMPLETT KOMMUNIKASJON : Meldinger, tale- og videosamtaler, fildeling, skjermdeling og en hel haug med integrasjoner, bots og widgets. Bygg rom, lokalsamfunn, hold kontakten og få ting gjort.
+
+ ALT DER DU ER : Hold kontakten uansett hvor du er med fullt synkronisert meldingslogg på alle enhetene dine og på nettet på https://app.element.io.
diff --git a/fastlane/metadata/android/nb/changelogs/40100170.txt b/fastlane/metadata/android/nb/changelogs/40100170.txt
new file mode 100644
index 0000000000..3593e50e05
--- /dev/null
+++ b/fastlane/metadata/android/nb/changelogs/40100170.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: Bugfikser!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.17
diff --git a/fastlane/metadata/android/nb/changelogs/40101010.txt b/fastlane/metadata/android/nb/changelogs/40101010.txt
new file mode 100644
index 0000000000..2d80855ed8
--- /dev/null
+++ b/fastlane/metadata/android/nb/changelogs/40101010.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: Forbedringer i ytelse og bugfikser!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.1
diff --git a/fastlane/metadata/android/ru/changelogs/40101020.txt b/fastlane/metadata/android/ru/changelogs/40101020.txt
new file mode 100644
index 0000000000..70e164f39d
--- /dev/null
+++ b/fastlane/metadata/android/ru/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Основные изменения в этой версии: улучшение производительности и исправления ошибок!
+Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/ru/changelogs/40101030.txt b/fastlane/metadata/android/ru/changelogs/40101030.txt
new file mode 100644
index 0000000000..381c2761d0
--- /dev/null
+++ b/fastlane/metadata/android/ru/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Основные изменения в этой версии: улучшение производительности и исправления ошибок!
+Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/sv/changelogs/40101020.txt b/fastlane/metadata/android/sv/changelogs/40101020.txt
new file mode 100644
index 0000000000..229793ab31
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: prestandaförbättringar och buggfixar!
+Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/sv/changelogs/40101030.txt b/fastlane/metadata/android/sv/changelogs/40101030.txt
new file mode 100644
index 0000000000..7e0f8c80d2
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: prestandaförbättringar och buggfixar!
+Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/sv/changelogs/40101040.txt b/fastlane/metadata/android/sv/changelogs/40101040.txt
new file mode 100644
index 0000000000..46a004ec54
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/40101040.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: prestandaförbättringar och buggfixar!
+Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.4
diff --git a/fastlane/metadata/android/sv/changelogs/40101050.txt b/fastlane/metadata/android/sv/changelogs/40101050.txt
new file mode 100644
index 0000000000..158e2032b7
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/40101050.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: hotfixar för 1.1.4
+Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.5
diff --git a/fastlane/metadata/android/sv/changelogs/40101060.txt b/fastlane/metadata/android/sv/changelogs/40101060.txt
new file mode 100644
index 0000000000..cc7a9b84b6
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/40101060.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: hotfixar för 1.1.5
+Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.6
diff --git a/fastlane/metadata/android/sv/full_description.txt b/fastlane/metadata/android/sv/full_description.txt
index d130e9214a..5302976ed7 100644
--- a/fastlane/metadata/android/sv/full_description.txt
+++ b/fastlane/metadata/android/sv/full_description.txt
@@ -1,30 +1,39 @@
-Element är en ny sorts meddelande- och samarbetsapp som:
+Element är både en säker meddelandeapp och en samarbetsapp för produktivitet som är ideal för gruppchattar vid distansarbete. Appen använder totalsträckskryptering för att tillhandahålla kraftfulla videogruppsamtal, fildelning och röstsamtal.
-1. Sätter dig i kontroll för att kunna säkerställa ditt privatliv
-2. Låter dig kommunicera med vem som helst i Matrix-nätverket, och till och med bortom det genom integrationer med appar som Slack
-3. Skyddar dig från reklam, datainsamling och inlåsning
-4. Säkrar dig genom totalsträckskryptering, med korssingering för att verifiera andra
+Elements funktioner inkluderar:
+- Avancerade kommunikationsverktyg
+- Fullt krypterade meddelanden för att tillåta säkrare företagskommunikation, även för distansarbetare
+- Decentraliserad chatt baserad på det öppna ramverket Matrix
+- Säker fildelning med krypterad data vid hantering av projekt
+- Videochattar med Voice over IP och skärmdelning
+- Enkel integration med dina föredragna onlinesamarbetsverktyg, projektledningsverktyg, VoIP-tjänster och andra teammeddelandeappar
-Element skiljer sig helt från andra meddelande- och samarbetsappar genom att vara decentraliserad och öppen källkod.
+Element är helt olik andra meddelande- och samarbetsappar. Den använder Matrix, ett öppet nätverk för säkra meddelanden och decentraliserad kommunikation. Den låter dig driva en igen server för att ge användare maximalt ägandeskap över sin data och sina meddelanden.
-Element låter dig driva en egen server - eller välja en värd - så att du har sekretess, ägande och kontroll över din data och dina konversationer. Den ger dig tillgång till ett öppet nätverk; så att du inte kan prata bara med Element-användare. Och den är väldigt säker.
+Sekretess och krypterade meddelanden
+Element skyddar dig från oönskad reklam, datainsamling och inlåsning. Den skyddar även all din data, en-till-en-video- och röstkommunikation genom totalsträckskryptering och korssignerad enhetsverifiering.
-Element kan göra allt detta för att den använder Matrix - standarden för öppen decentraliserad kommunikation.
+Element sätter dig i kontroll över ditt privatliv och låter dig kommunicera säkert med vem som helst i Matrix-nätverket, eller andra samarbetsverktyg genom att integrera med appar som Slack.
-Element sätter dig i kontroll genom att låta dig välja att vara värd för dina konversationer. Från appen Element kan du välja att ansluta på följande sätt:
+Du kan driva Element själv
+För att tillåta större kontroll över din känsliga data och konversationer, så kan du lägga Element på en egen server eller använda valfri Matrix-baserad värd - standarden för open source-baserad, decentraliserad kommunikation. Element ger dig sekretess, säkerhetsefterlevnad och integrationsflexibilitet.
-1. Skaffa ett gratis konto på den publika servern på matrix.org, vilken drivs av Matrix-utvecklarna, eller välj bland tusentals offentliga servrar som drivs av volontärer
-2. Var värd för ditt eget konto genom att driva en server på din egen hårdvara
-3. Skapa ett konto på en anpassad server genom att registrera dig på värdplattformen Element Matrix Services
+Äg din data
+Du bestämmer vart du vill lagra din data och dina meddelanden, utan rist för datainsamling eller åtkomst av tredje parter.
-Varför välja Element?
+Element sätter dig i kontroll på olika sätt:
+1. Få ett gratiskonto på den offentliga servern matrix.org som drivas av Matrixutvecklarna, eller välj bland tusentals offentliga servrar som drivs av volontärer
+2. Självdriv ditt konto genom att driva en server på din egen IT-infrastruktur
+3. Skapa ett konto på en anpassad server genom att abonnera på värdplattformen Element Matrix Services
-ÄG DIN DATA: Du väljer var du vill ha din data och dina meddelanden. Du äger den och kontrollerar den, inte nåt stort företag som samlar in din data och ger den till tredje parter.
+Öppen meddelandehantering och kommunikation
+Du kan chatta med vem som helst i Matrix-nätverket, oavsett om de använder Matrix, en annan Matrix-app eller till och med en annan meddelandeapp.
-ÖPPEN KOMMUNIKATION OCH ÖPPET SAMARBETE: Du kan chatta med vem som helst på Matrix-nätverket, oavsett om de använder Element eller en annan Matrix-app, och till och med om de använder ett annat meddelandesystem som Slack, IRC eller XMPP.
+Supersäker
+Riktig totalsträckskryptering (bara de i konversationen kan avkryptera meddelanden), och korssignerad enhetsverifiering.
-SUPERSÄKER: Riktig totalsträckskryptering (bara de in konversationen kan avkryptera meddelandena), och korssingering för att verifiera konversationsmedlemmars enheter.
+Komplett kommunikation och integration
+Meddelanden, röst- och videosamtal, fildelning, skärmdelning och massa integrationer, bottar och widgets. Bygg rum och gemenskaper, håll kontakten och få saker gjorda.
-KOMPLETT KOMMUNIKATION: Meddelanden, röst- och videosamtal, fildelning, skärmdelning och massa integrationer, bottar och widgets. Skapa rum och gemenskaper, håll kontakten och få saker gjorda.
-
-ÖVERALLT DÄR DU ÄR: Håll kontakten vart du än befinner dig med fullständigt synkroniserad meddelandehistorik på alla dina enheter och på webben på https://app.element.io.
+Fortsätt där du lämnade
+Håll kontakten vart du än är med fullt synkroniserad meddelandehistorik på alla dina enheter och på webben på https://app.element.io
diff --git a/fastlane/metadata/android/sv/short_description.txt b/fastlane/metadata/android/sv/short_description.txt
index ddfc5dcbfb..c16da5c761 100644
--- a/fastlane/metadata/android/sv/short_description.txt
+++ b/fastlane/metadata/android/sv/short_description.txt
@@ -1 +1 @@
-Säker decentraliserad chatt och VoIP. Håll din data säker från tredje parter.
+Gruppmeddelandeapp - krypterade meddelanden, gruppchatt och videosamtal
diff --git a/fastlane/metadata/android/sv/title.txt b/fastlane/metadata/android/sv/title.txt
index 573e27fab9..4fc189de15 100644
--- a/fastlane/metadata/android/sv/title.txt
+++ b/fastlane/metadata/android/sv/title.txt
@@ -1 +1 @@
-Element (före detta Riot.im)
+Element - Säker meddelandeapp
diff --git a/fastlane/metadata/android/uk/changelogs/40101020.txt b/fastlane/metadata/android/uk/changelogs/40101020.txt
new file mode 100644
index 0000000000..469de21a6f
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Основні зміни в цій версії: поліпшення продуктивності та виправлення помилок!
+Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/uk/changelogs/40101030.txt b/fastlane/metadata/android/uk/changelogs/40101030.txt
new file mode 100644
index 0000000000..da2bb0ddd6
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Основні зміни в цій версії: поліпшення продуктивності та виправлення помилок!
+Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/zh-Hant/changelogs/40101020.txt b/fastlane/metadata/android/zh-Hant/changelogs/40101020.txt
new file mode 100644
index 0000000000..90e76b074e
--- /dev/null
+++ b/fastlane/metadata/android/zh-Hant/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+此版本中的主要變更:效能改進與錯誤修復!
+完整變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/zh-Hant/changelogs/40101030.txt b/fastlane/metadata/android/zh-Hant/changelogs/40101030.txt
new file mode 100644
index 0000000000..c13d6ecfd4
--- /dev/null
+++ b/fastlane/metadata/android/zh-Hant/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+此版本中的主要變更:效能改進與錯誤修復!
+完整變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/zh-Hant/changelogs/40101040.txt b/fastlane/metadata/android/zh-Hant/changelogs/40101040.txt
new file mode 100644
index 0000000000..1786691c42
--- /dev/null
+++ b/fastlane/metadata/android/zh-Hant/changelogs/40101040.txt
@@ -0,0 +1,2 @@
+此版本中的主要變動:效能改善與錯誤修復!
+完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.1.4
diff --git a/fastlane/metadata/android/zh-Hant/changelogs/40101050.txt b/fastlane/metadata/android/zh-Hant/changelogs/40101050.txt
new file mode 100644
index 0000000000..899ce72c9a
--- /dev/null
+++ b/fastlane/metadata/android/zh-Hant/changelogs/40101050.txt
@@ -0,0 +1,2 @@
+此版本中的主要變動:1.1.4 的快速修補
+完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.1.5
diff --git a/fastlane/metadata/android/zh-Hant/changelogs/40101060.txt b/fastlane/metadata/android/zh-Hant/changelogs/40101060.txt
new file mode 100644
index 0000000000..838dc6d731
--- /dev/null
+++ b/fastlane/metadata/android/zh-Hant/changelogs/40101060.txt
@@ -0,0 +1,2 @@
+此版本中的主要變動:1.1.5 的快速修補
+完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.1.6
diff --git a/fastlane/metadata/android/zh-Hant/full_description.txt b/fastlane/metadata/android/zh-Hant/full_description.txt
index 2fdf6fa478..90c0eb4c4c 100644
--- a/fastlane/metadata/android/zh-Hant/full_description.txt
+++ b/fastlane/metadata/android/zh-Hant/full_description.txt
@@ -1,30 +1,39 @@
-Element 是一種新型態的即時通訊軟體與協作應用程式:
+Element 同時是安全的通訊軟體,也是生產力團隊協作應用程式,非常適合在遠端工作時進行群組聊天。此聊天應用程式使用了端到端加密來提供強大的視訊會議、檔案分享與語音通話。
-1. 自己的隱私自己掌控
-2. 讓您與任何在 Matrix 網路中的人通訊,甚至可與如 Slack 等的應用程式整合
-3. 保護您免受廣告、資料採礦與圍牆花園的侵害
-4. 透過端到端加密保護您,並使用交叉簽章來驗證其他人
+Element 的功能包含了:
+- 進階線上通訊工具
+- 完全加密的訊息,即使對於遠端工作者,也可以有更安全的公司通訊
+- 以 Matrix 開放原始碼框架為基礎的去中心化的聊天
+- 在管理專案時透過加密資料安全地分享檔案
+- 包含了 VoIP 與畫面分享的視訊聊天
+- 與您最喜歡的協作工具、專案管理工具、VoIP 服務與其他團隊訊息應用程式輕鬆整合
-Element 是去中心化且開放原始碼的應用程式,因此與其他即時通訊與協作軟體完全不同。
+Element 與其他訊息傳遞與協作應用程式完全不同。它在 Matrix(一個用於安全傳遞訊息與去中心化通訊的開放網路)上執行。其可以自架,讓使用者對他們的資料與訊息有最大的所有權與控制權。
-Element 讓您可以自架(或是自行選擇服務提供者)所以您擁有您資料與對話的隱私、所有權與控制權。它讓您可以存取開放的網路;因此,您不僅可以與其他 Matrix 使用者聊天。而且非常安全。
+隱私與加密訊息傳遞
+Element 保護您不受不想要的廣告、資料挖礦與圍牆花園侵擾。其也透過端到端加密與交叉簽章裝置驗證保護了您所有的資料,並提供一對一視訊以及語音通訊。
-Element 能作到這些事情是因為它在 Matrix 上執行,這是一個開放的去中心化通訊的標準。
+Element 透過與其他商業協作工具,如 Slack 等應用程式整合,讓您可以在控制您的隱私的同時,也可以與 Matrix 網路上的任何人安全地通訊。
-Element 讓您選擇您要在哪裡託管您的對話來將控制權還給您。在 Element 應用程式中,您可以選擇其他方式來託管:
+Element 可以自架
+為了可以完整控制您的敏感資料與對話,Element 可以自架,您也可以選擇任何以 Matrix 為基礎的服務提供商,開放原始碼、去中心化的通訊標準。Element 為您提供隱私、安全合規與整合活性。
-1. 在由 Matrix 開發者架設的 matrix.org 公開伺服器上取得免費的帳號,或是從數千個由志願者所架設的公開伺服器中選擇
-2. 在您自己的硬體上自行架設伺服器並建立帳號
-3. 訂閱 Element Matrix 服務託管平台並在自訂伺服氣上註冊帳號
+擁有您的資料
+您可以決定將您的資料與訊息儲存在何處。沒有資料挖礦或被第三方存取的風險。
-為何選擇 Element?
+Element 透過不同的方式讓您掌控一切:
+1. 在 Matrix 開發者架設的 matrix.org 公開伺服器上取得免費帳號,或是從數千個由志願者架設的公開伺服器中選擇
+2. 在您自己的 IT 基礎架構上執行伺服器來自行託管您的帳號
+3. 只要訂閱 Element Matrix Services 託管平台就可以在自訂的伺服器上註冊帳號
-擁有您的資料:您決定您的資料與訊息要放在哪裡。您擁有並控制它,而非某些科技巨頭會挖掘您的資料並將其售予第三方。
+開放訊息傳遞與協作
+您可以與 Matrix 網路上的任何人聊天,不論他們是使用 Element、其他 Matrix 應用程式或其他通訊應用程式。
-開放的即時通訊與協作:您可以與 Matrix 網路中的任何人聊天,不管他們是使用 Element 或其他 Matrix 應用程式都可以,或甚至是其他的訊息系統,如 Slack、IRC 或 XMPP 也都可以。
+超級安全
+真的端到端加密(僅有那些在對話中的可以解密訊息)以及交叉簽章裝置驗證。
-超級安全:即時的端到端加密(僅有參與對話的人可以解密訊息),以及交叉簽章以驗證對話參與者的裝置。
+完整的通訊與整合Complete communication and integration
+訊息傳遞、語音與視訊通話、檔案分享、畫面分享與超多的整合、機器人與小工具。建構聊天室、社群、保持聯絡並完成工作。
-完整通訊:即時通訊、語音與視訊通話、檔案分享、畫面分享與超多的整合、機器人與小工具。建立聊天室、保持聯繫並完成工作。
-
-無論您身在何處:無論您身在何處,都可以透過 https://app.element.io 來在所有裝置與網路上保持訊息歷史同步。
+從上次離開的地方開始
+無論您身在何處,都可以透過在您所有裝置與網頁 https://app.element.io 間完全同步的訊息歷史保持聯絡
diff --git a/fastlane/metadata/android/zh-Hant/short_description.txt b/fastlane/metadata/android/zh-Hant/short_description.txt
index 23bb82c04e..0d1f5bb7cd 100644
--- a/fastlane/metadata/android/zh-Hant/short_description.txt
+++ b/fastlane/metadata/android/zh-Hant/short_description.txt
@@ -1 +1 @@
-安全的去中心化聊天與 VoIP。確保您的資料不受第三方的影響。
+群組通訊軟體 - 訊息加密、群組聊天與視訊通話
diff --git a/fastlane/metadata/android/zh-Hant/title.txt b/fastlane/metadata/android/zh-Hant/title.txt
index 3be2260b73..47d8a6b3ad 100644
--- a/fastlane/metadata/android/zh-Hant/title.txt
+++ b/fastlane/metadata/android/zh-Hant/title.txt
@@ -1 +1 @@
-Element(曾名為 Riot.im)
+Element - 安全的通訊軟體
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 6e61ea7487..9d174797f7 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=9af5c8e7e2cd1a3b0f694a4ac262b9f38c75262e74a9e8b5101af302a6beadd7
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
+distributionSha256Sum=81003f83b0056d20eedf48cddd4f52a9813163d4ba185bcf8abd34b8eeea4cbd
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/matrix-sdk-android-rx/src/main/AndroidManifest.xml b/matrix-sdk-android-rx/src/main/AndroidManifest.xml
index f1bb42638f..5f399e9f84 100644
--- a/matrix-sdk-android-rx/src/main/AndroidManifest.xml
+++ b/matrix-sdk-android-rx/src/main/AndroidManifest.xml
@@ -1,2 +1 @@
-
+
diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt
deleted file mode 100644
index ec30a31f6d..0000000000
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.rx
-
-import org.matrix.android.sdk.api.MatrixCallback
-import org.matrix.android.sdk.api.util.Cancelable
-import io.reactivex.Completable
-import io.reactivex.Single
-
-fun singleBuilder(builder: (MatrixCallback) -> Cancelable): Single = Single.create { emitter ->
- val callback = object : MatrixCallback {
- override fun onSuccess(data: T) {
- // Add `!!` to fix the warning:
- // "Type mismatch: type parameter with nullable bounds is used T is used where T was expected. This warning will become an error soon"
- emitter.onSuccess(data!!)
- }
-
- override fun onFailure(failure: Throwable) {
- emitter.tryOnError(failure)
- }
- }
- val cancelable = builder(callback)
- emitter.setCancellable {
- cancelable.cancel()
- }
-}
-
-fun completableBuilder(builder: (MatrixCallback) -> Cancelable): Completable = Completable.create { emitter ->
- val callback = object : MatrixCallback {
- override fun onSuccess(data: T) {
- emitter.onComplete()
- }
-
- override fun onFailure(failure: Throwable) {
- emitter.tryOnError(failure)
- }
- }
- val cancelable = builder(callback)
- emitter.setCancellable {
- cancelable.cancel()
- }
-}
diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
index 0d5b5ed821..67a35cac2e 100644
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
+++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
@@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
import org.matrix.android.sdk.api.session.sync.SyncState
import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.api.session.widgets.model.Widget
@@ -66,6 +67,13 @@ class RxSession(private val session: Session) {
}
}
+ fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Observable> {
+ return session.spaceService().getSpaceSummariesLive(queryParams).asObservable()
+ .startWithCallable {
+ session.spaceService().getSpaceSummaries(queryParams)
+ }
+ }
+
fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Observable> {
return session.getBreadcrumbsLive(queryParams).asObservable()
.startWithCallable {
@@ -124,8 +132,8 @@ class RxSession(private val session: Session) {
.startWithCallable { session.getPendingThreePids() }
}
- fun createRoom(roomParams: CreateRoomParams): Single = singleBuilder {
- session.createRoom(roomParams, it)
+ fun createRoom(roomParams: CreateRoomParams): Single = rxSingle {
+ session.createRoom(roomParams)
}
fun searchUsersDirectory(search: String,
@@ -136,13 +144,13 @@ class RxSession(private val session: Session) {
fun joinRoom(roomIdOrAlias: String,
reason: String? = null,
- viaServers: List = emptyList()): Single = singleBuilder {
- session.joinRoom(roomIdOrAlias, reason, viaServers, it)
+ viaServers: List = emptyList()): Single = rxSingle {
+ session.joinRoom(roomIdOrAlias, reason, viaServers)
}
fun getRoomIdByAlias(roomAlias: String,
- searchOnServer: Boolean): Single> = singleBuilder {
- session.getRoomIdByAlias(roomAlias, searchOnServer, it)
+ searchOnServer: Boolean): Single> = rxSingle {
+ session.getRoomIdByAlias(roomAlias, searchOnServer)
}
fun getProfileInfo(userId: String): Single = rxSingle {
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 1191e11b11..108240f94d 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -6,13 +6,10 @@ apply plugin: 'realm-android'
buildscript {
repositories {
- // mavenCentral()
- //noinspection GrDeprecatedAPIUsage
- jcenter()
+ mavenCentral()
}
dependencies {
- // Stick to this version until https://github.com/realm/realm-java/issues/7402 is fixed
- classpath "io.realm:realm-gradle-plugin:10.3.1"
+ classpath "io.realm:realm-gradle-plugin:10.4.0"
}
}
@@ -115,7 +112,7 @@ dependencies {
def lifecycle_version = '2.2.0'
def arch_version = '2.1.0'
def markwon_version = '3.1.0'
- def daggerVersion = '2.33'
+ def daggerVersion = '2.35'
def work_version = '2.5.0'
def retrofit_version = '2.9.0'
@@ -168,8 +165,11 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
+ // Video compression
+ implementation 'com.otaliastudios:transcoder:0.10.3'
+
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.21'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.22'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.5.1'
@@ -187,7 +187,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
- androidTestImplementation 'org.amshove.kluent:kluent-android:1.61'
+ androidTestImplementation 'org.amshove.kluent:kluent-android:1.65'
androidTestImplementation 'io.mockk:mockk-android:1.11.0'
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
index 5815b23c06..da176491c6 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
@@ -66,8 +66,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
- val roomId = mTestHelper.doSync {
- aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it)
+ val roomId = mTestHelper.runBlockingTest {
+ aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
}
if (encryptedRoom) {
@@ -135,7 +135,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
bobRoomSummariesLive.observeForever(roomJoinedObserver)
}
- mTestHelper.doSync { bobSession.joinRoom(aliceRoomId, callback = it) }
+ mTestHelper.runBlockingTest { bobSession.joinRoom(aliceRoomId) }
mTestHelper.await(lock)
@@ -176,8 +176,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
room.invite(samSession.myUserId, null)
}
- mTestHelper.doSync {
- samSession.joinRoom(room.roomId, null, emptyList(), it)
+ mTestHelper.runBlockingTest {
+ samSession.joinRoom(room.roomId, null, emptyList())
}
return samSession
@@ -256,8 +256,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
}
fun createDM(alice: Session, bob: Session): String {
- val roomId = mTestHelper.doSync {
- alice.createDirectRoom(bob.myUserId, it)
+ val roomId = mTestHelper.runBlockingTest {
+ alice.createDirectRoom(bob.myUserId)
}
mTestHelper.waitWithLatch { latch ->
@@ -300,7 +300,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
bobRoomSummariesLive.observeForever(newRoomObserver)
}
- mTestHelper.doSync { bob.joinRoom(roomId, callback = it) }
+ mTestHelper.runBlockingTest { bob.joinRoom(roomId) }
}
return roomId
@@ -398,8 +398,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
- val roomId = mTestHelper.doSync {
- aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it)
+ val roomId = mTestHelper.runBlockingTest {
+ aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
}
val room = aliceSession.getRoom(roomId)!!
@@ -412,7 +412,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
val session = mTestHelper.createAccount("User_$index", defaultSessionParams)
mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
println("TEST -> " + session.myUserId + " invited")
- mTestHelper.doSync { session.joinRoom(room.roomId, null, emptyList(), it) }
+ mTestHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) }
println("TEST -> " + session.myUserId + " joined")
sessions.add(session)
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
index 122584142e..a2566c1414 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
@@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.crypto
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -29,8 +31,6 @@ import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@@ -54,7 +54,7 @@ class PreShareKeysTest : InstrumentedTest {
&& it.getClearType() == EventType.ROOM_KEY
}
- assertEquals(0, preShareCount, "Bob should not have receive any key from alice at this point")
+ assertEquals("Bob should not have receive any key from alice at this point", 0, preShareCount)
Log.d("#Test", "Room Key Received from alice $preShareCount")
// Force presharing of new outbound key
@@ -78,14 +78,14 @@ class PreShareKeysTest : InstrumentedTest {
}
val content = latest?.getClearContent().toModel()
- assertNotNull(content, "Bob should have received and decrypted a room key event from alice")
- assertEquals(e2eRoomID, content.roomId, "Wrong room")
+ assertNotNull("Bob should have received and decrypted a room key event from alice", content)
+ assertEquals("Wrong room", e2eRoomID, content!!.roomId)
val megolmSessionId = content.sessionId!!
val sharedIndex = aliceSession.cryptoService().getSharedWithInfo(e2eRoomID, megolmSessionId)
.getObject(bobSession.myUserId, bobSession.sessionParams.deviceId)
- assertEquals(0, sharedIndex, "The session received by bob should match what alice sent")
+ assertEquals("The session received by bob should match what alice sent", 0, sharedIndex)
// Just send a real message as test
val sentEvent = mTestHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first()
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
index e6b364f3fb..40659cef11 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
@@ -71,13 +71,12 @@ class KeyShareTests : InstrumentedTest {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
// Create an encrypted room and add a message
- val roomId = mTestHelper.doSync {
+ val roomId = mTestHelper.runBlockingTest {
aliceSession.createRoom(
CreateRoomParams().apply {
visibility = RoomDirectoryVisibility.PRIVATE
enableEncryption()
- },
- it
+ }
)
}
val room = aliceSession.getRoom(roomId)
@@ -332,13 +331,12 @@ class KeyShareTests : InstrumentedTest {
}
// Create an encrypted room and send a couple of messages
- val roomId = mTestHelper.doSync {
+ val roomId = mTestHelper.runBlockingTest {
aliceSession.createRoom(
CreateRoomParams().apply {
visibility = RoomDirectoryVisibility.PRIVATE
enableEncryption()
- },
- it
+ }
)
}
val roomAlicePov = aliceSession.getRoom(roomId)
@@ -371,8 +369,8 @@ class KeyShareTests : InstrumentedTest {
roomAlicePov.invite(bobSession.myUserId, null)
}
- mTestHelper.doSync {
- bobSession.joinRoom(roomAlicePov.roomId, null, emptyList(), it)
+ mTestHelper.runBlockingTest {
+ bobSession.joinRoom(roomAlicePov.roomId, null, emptyList())
}
// we want to discard alice outbound session
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt
index ff07cf1d1d..ace48cef77 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt
@@ -16,6 +16,7 @@
package org.matrix.android.sdk.session.room.timeline
+import org.junit.Assert.fail
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -29,7 +30,6 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper
import java.util.concurrent.CountDownLatch
-import kotlin.test.fail
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@@ -80,6 +80,7 @@ class TimelineWithManyMembersTest : InstrumentedTest {
return@createEventListener true
} else {
fail("User " + session.myUserId + " decrypted as " + body + " CryptoError: " + it.root.mCryptoError)
+ false
}
} ?: return@createEventListener false
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
new file mode 100644
index 0000000000..a1744a0dae
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
@@ -0,0 +1,222 @@
+/*
+ * 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.session.space
+
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.InstrumentedTest
+import org.matrix.android.sdk.api.query.ActiveSpaceFilter
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
+import org.matrix.android.sdk.api.session.room.model.GuestAccess
+import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
+import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent
+import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
+import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
+import org.matrix.android.sdk.api.session.room.model.RoomType
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
+import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
+import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
+import org.matrix.android.sdk.api.session.space.JoinSpaceResult
+import org.matrix.android.sdk.common.CommonTestHelper
+import org.matrix.android.sdk.common.SessionTestParams
+
+@RunWith(JUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+class SpaceCreationTest : InstrumentedTest {
+
+ private val commonTestHelper = CommonTestHelper(context())
+
+ @Test
+ fun createSimplePublicSpace() {
+ val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true))
+ val roomName = "My Space"
+ val topic = "A public space for test"
+ var spaceId: String = ""
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ spaceId = session.spaceService().createSpace(roomName, topic, null, true)
+ // wait a bit to let the summary update it self :/
+ it.countDown()
+ }
+ }
+
+ val syncedSpace = session.spaceService().getSpace(spaceId)
+ commonTestHelper.waitWithLatch {
+ commonTestHelper.retryPeriodicallyWithLatch(it) {
+ syncedSpace?.asRoom()?.roomSummary()?.name != null
+ }
+ }
+ assertEquals("Room name should be set", roomName, syncedSpace?.asRoom()?.roomSummary()?.name)
+ assertEquals("Room topic should be set", topic, syncedSpace?.asRoom()?.roomSummary()?.topic)
+ // assertEquals(topic, syncedSpace.asRoom().roomSummary()?., "Room topic should be set")
+
+ assertNotNull("Space should be found by Id", syncedSpace)
+ val creationEvent = syncedSpace!!.asRoom().getStateEvent(EventType.STATE_ROOM_CREATE)
+ val createContent = creationEvent?.content.toModel()
+ assertEquals("Room type should be space", RoomType.SPACE, createContent?.type)
+
+ var powerLevelsContent: PowerLevelsContent? = null
+ commonTestHelper.waitWithLatch { latch ->
+ commonTestHelper.retryPeriodicallyWithLatch(latch) {
+ val toModel = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)?.content.toModel()
+ powerLevelsContent = toModel
+ toModel != null
+ }
+ }
+ assertEquals("Space-rooms should be created with a power level for events_default of 100", 100, powerLevelsContent?.eventsDefault)
+
+ val guestAccess = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_GUEST_ACCESS)?.content
+ ?.toModel()?.guestAccess
+
+ assertEquals("Public space room should be peekable by guest", GuestAccess.CanJoin, guestAccess)
+
+ val historyVisibility = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY)?.content
+ ?.toModel()?.historyVisibility
+
+ assertEquals("Public space room should be world readable", RoomHistoryVisibility.WORLD_READABLE, historyVisibility)
+
+ commonTestHelper.signOutAndClose(session)
+ }
+
+ @Test
+ fun testJoinSimplePublicSpace() {
+ val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
+ val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true))
+
+ val roomName = "My Space"
+ val topic = "A public space for test"
+ val spaceId: String
+ runBlocking {
+ spaceId = aliceSession.spaceService().createSpace(roomName, topic, null, true)
+ // wait a bit to let the summary update it self :/
+ delay(400)
+ }
+
+ // Try to join from bob, it's a public space no need to invite
+
+ val joinResult: JoinSpaceResult
+ runBlocking {
+ joinResult = bobSession.spaceService().joinSpace(spaceId)
+ }
+
+ assertEquals(JoinSpaceResult.Success, joinResult)
+
+ val spaceBobPov = bobSession.spaceService().getSpace(spaceId)
+ assertEquals("Room name should be set", roomName, spaceBobPov?.asRoom()?.roomSummary()?.name)
+ assertEquals("Room topic should be set", topic, spaceBobPov?.asRoom()?.roomSummary()?.topic)
+
+ commonTestHelper.signOutAndClose(aliceSession)
+ commonTestHelper.signOutAndClose(bobSession)
+ }
+
+ @Test
+ fun testSimplePublicSpaceWithChildren() {
+ val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
+ val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true))
+
+ val roomName = "My Space"
+ val topic = "A public space for test"
+
+ val spaceId: String = runBlocking { aliceSession.spaceService().createSpace(roomName, topic, null, true) }
+ val syncedSpace = aliceSession.spaceService().getSpace(spaceId)
+
+ // create a room
+ var firstChild: String? = null
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ firstChild = aliceSession.createRoom(CreateRoomParams().apply {
+ this.name = "FirstRoom"
+ this.topic = "Description of first room"
+ this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
+ })
+ it.countDown()
+ }
+ }
+
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", true, suggested = true)
+ it.countDown()
+ }
+ }
+
+ var secondChild: String? = null
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ secondChild = aliceSession.createRoom(CreateRoomParams().apply {
+ this.name = "SecondRoom"
+ this.topic = "Description of second room"
+ this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
+ })
+ it.countDown()
+ }
+ }
+
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ syncedSpace?.addChildren(secondChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", false, suggested = true)
+ it.countDown()
+ }
+ }
+
+ // Try to join from bob, it's a public space no need to invite
+ var joinResult: JoinSpaceResult? = null
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ joinResult = bobSession.spaceService().joinSpace(spaceId)
+ // wait a bit to let the summary update it self :/
+ it.countDown()
+ }
+ }
+
+ assertEquals(JoinSpaceResult.Success, joinResult)
+
+ val spaceBobPov = bobSession.spaceService().getSpace(spaceId)
+ assertEquals("Room name should be set", roomName, spaceBobPov?.asRoom()?.roomSummary()?.name)
+ assertEquals("Room topic should be set", topic, spaceBobPov?.asRoom()?.roomSummary()?.topic)
+
+ // check if bob has joined automatically the first room
+
+ val bobMembershipFirstRoom = bobSession.getRoomSummary(firstChild!!)?.membership
+ assertEquals("Bob should have joined this room", Membership.JOIN, bobMembershipFirstRoom)
+ RoomSummaryQueryParams.Builder()
+
+ val childCount = bobSession.getRoomSummaries(
+ roomSummaryQueryParams {
+ activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(spaceId)
+ }
+ ).size
+
+ assertEquals("Unexpected number of joined children", 1, childCount)
+
+ commonTestHelper.signOutAndClose(aliceSession)
+ commonTestHelper.signOutAndClose(bobSession)
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
new file mode 100644
index 0000000000..521b5805bd
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
@@ -0,0 +1,472 @@
+/*
+ * 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.session.space
+
+import android.util.Log
+import androidx.lifecycle.Observer
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.InstrumentedTest
+import org.matrix.android.sdk.api.query.ActiveSpaceFilter
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.RoomType
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
+import org.matrix.android.sdk.common.CommonTestHelper
+import org.matrix.android.sdk.common.SessionTestParams
+
+@RunWith(JUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+class SpaceHierarchyTest : InstrumentedTest {
+
+ private val commonTestHelper = CommonTestHelper(context())
+
+ @Test
+ fun createCanonicalChildRelation() {
+ val session = commonTestHelper.createAccount("John", SessionTestParams(true))
+ val spaceName = "My Space"
+ val topic = "A public space for test"
+ var spaceId: String = ""
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
+ it.countDown()
+ }
+ }
+
+ val syncedSpace = session.spaceService().getSpace(spaceId)
+
+ var roomId: String = ""
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ roomId = session.createRoom(CreateRoomParams().apply { name = "General" })
+ it.countDown()
+ }
+ }
+
+ val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ syncedSpace!!.addChildren(roomId, viaServers, null, true)
+ it.countDown()
+ }
+ }
+
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers)
+ it.countDown()
+ }
+ }
+
+ Thread.sleep(9000)
+
+ val parents = session.getRoom(roomId)?.roomSummary()?.spaceParents
+ val canonicalParents = session.getRoom(roomId)?.roomSummary()?.spaceParents?.filter { it.canonical == true }
+
+ parents?.forEach {
+ Log.d("## TEST", "parent : $it")
+ }
+
+ assertNotNull(parents)
+ assertEquals(1, parents!!.size)
+ assertEquals(spaceName, parents.first().roomSummary?.name)
+
+ assertNotNull(canonicalParents)
+ assertEquals(1, canonicalParents!!.size)
+ assertEquals(spaceName, canonicalParents.first().roomSummary?.name)
+ }
+
+// @Test
+// fun testCreateChildRelations() {
+// val session = commonTestHelper.createAccount("Jhon", SessionTestParams(true))
+// val spaceName = "My Space"
+// val topic = "A public space for test"
+// Log.d("## TEST", "Before")
+//
+// var spaceId = ""
+// commonTestHelper.waitWithLatch {
+// GlobalScope.launch {
+// spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
+// it.countDown()
+// }
+// }
+//
+// Log.d("## TEST", "created space $spaceId ${Thread.currentThread()}")
+// val syncedSpace = session.spaceService().getSpace(spaceId)
+//
+// val children = listOf("General" to true /*canonical*/, "Random" to false)
+//
+// // val roomIdList = children.map {
+// // runBlocking {
+// // session.createRoom(CreateRoomParams().apply { name = it.first })
+// // } to it.second
+// // }
+// val roomIdList = mutableListOf>()
+// commonTestHelper.waitWithLatch {
+// GlobalScope.launch {
+// children.forEach {
+// val rID = session.createRoom(CreateRoomParams().apply { name = it.first })
+// roomIdList.add(rID to it.second)
+// }
+// it.countDown()
+// }
+// }
+//
+// val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+//
+// commonTestHelper.waitWithLatch {
+// GlobalScope.launch {
+// roomIdList.forEach { entry ->
+// syncedSpace!!.addChildren(entry.first, viaServers, null, true)
+// }
+// it.countDown()
+// }
+// }
+//
+// commonTestHelper.waitWithLatch {
+// GlobalScope.launch {
+// roomIdList.forEach {
+// session.spaceService().setSpaceParent(it.first, spaceId, it.second, viaServers)
+// }
+// it.countDown()
+// }
+// }
+//
+// roomIdList.forEach {
+// val parents = session.getRoom(it.first)?.roomSummary()?.spaceParents
+// val canonicalParents = session.getRoom(it.first)?.roomSummary()?.spaceParents?.filter { it.canonical == true }
+//
+// assertNotNull(parents)
+// assertEquals("Unexpected number of parent", 1, parents!!.size)
+// assertEquals("Unexpected parent name", spaceName, parents.first().roomSummary?.name)
+// assertEquals("Parent of ${it.first} should be canonical ${it.second}", if (it.second) 1 else 0, canonicalParents?.size ?: 0)
+// }
+// }
+
+ @Test
+ fun testFilteringBySpace() {
+ val session = commonTestHelper.createAccount("John", SessionTestParams(true))
+
+ val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
+ Triple("A1", true /*auto-join*/, true/*canonical*/),
+ Triple("A2", true, true)
+ ))
+
+ val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
+ Triple("B1", true /*auto-join*/, true/*canonical*/),
+ Triple("B2", true, true),
+ Triple("B3", true, true)
+ ))
+
+ val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
+ Triple("C1", true /*auto-join*/, true/*canonical*/),
+ Triple("C2", true, true)
+ ))
+
+ // add C as a subspace of A
+ val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
+ val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
+ session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
+ it.countDown()
+ }
+ }
+
+ // Create orphan rooms
+
+ var orphan1 = ""
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ orphan1 = session.createRoom(CreateRoomParams().apply { name = "O1" })
+ it.countDown()
+ }
+ }
+
+ var orphan2 = ""
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ orphan2 = session.createRoom(CreateRoomParams().apply { name = "O2" })
+ it.countDown()
+ }
+ }
+
+ val allRooms = session.getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) })
+
+ assertEquals("Unexpected number of rooms", 9, allRooms.size)
+
+ val orphans = session.getFlattenRoomSummaryChildrenOf(null)
+
+ assertEquals("Unexpected number of orphan rooms", 2, orphans.size)
+ assertTrue("O1 should be an orphan", orphans.any { it.roomId == orphan1 })
+ assertTrue("O2 should be an orphan ${orphans.map { it.name }}", orphans.any { it.roomId == orphan2 })
+
+ val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
+
+ assertEquals("Unexpected number of flatten child rooms", 4, aChildren.size)
+ assertTrue("A1 should be a child of A", aChildren.any { it.name == "A1" })
+ assertTrue("A2 should be a child of A", aChildren.any { it.name == "A2" })
+ assertTrue("CA should be a grand child of A", aChildren.any { it.name == "C1" })
+ assertTrue("A1 should be a grand child of A", aChildren.any { it.name == "C2" })
+
+ // Add a non canonical child and check that it does not appear as orphan
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ val a3 = session.createRoom(CreateRoomParams().apply { name = "A3" })
+ spaceA!!.addChildren(a3, viaServers, null, false)
+ it.countDown()
+ }
+ }
+
+ Thread.sleep(2_000)
+ val orphansUpdate = session.getRoomSummaries(roomSummaryQueryParams {
+ activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
+ })
+ assertEquals("Unexpected number of orphan rooms ${orphansUpdate.map { it.name }}", 2, orphansUpdate.size)
+ }
+
+ @Test
+ fun testBreakCycle() {
+ val session = commonTestHelper.createAccount("John", SessionTestParams(true))
+
+ val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
+ Triple("A1", true /*auto-join*/, true/*canonical*/),
+ Triple("A2", true, true)
+ ))
+
+ val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
+ Triple("C1", true /*auto-join*/, true/*canonical*/),
+ Triple("C2", true, true)
+ ))
+
+ // add C as a subspace of A
+ val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
+ val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
+ session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
+ it.countDown()
+ }
+ }
+
+ // add back A as subspace of C
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId)
+ spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true)
+ it.countDown()
+ }
+ }
+
+ Thread.sleep(1000)
+
+ // A -> C -> A
+
+ val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
+
+ assertEquals("Unexpected number of flatten child rooms ${aChildren.map { it.name }}", 4, aChildren.size)
+ assertTrue("A1 should be a child of A", aChildren.any { it.name == "A1" })
+ assertTrue("A2 should be a child of A", aChildren.any { it.name == "A2" })
+ assertTrue("CA should be a grand child of A", aChildren.any { it.name == "C1" })
+ assertTrue("A1 should be a grand child of A", aChildren.any { it.name == "C2" })
+ }
+
+ @Test
+ fun testLiveFlatChildren() {
+ val session = commonTestHelper.createAccount("John", SessionTestParams(true))
+
+ val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
+ Triple("A1", true /*auto-join*/, true/*canonical*/),
+ Triple("A2", true, true)
+ ))
+
+ val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
+ Triple("B1", true /*auto-join*/, true/*canonical*/),
+ Triple("B2", true, true),
+ Triple("B3", true, true)
+ ))
+
+ // add B as a subspace of A
+ val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
+ val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+ runBlocking {
+ spaceA!!.addChildren(spaceBInfo.spaceId, viaServers, null, true)
+ session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
+ }
+
+ val flatAChildren = runBlocking(Dispatchers.Main) {
+ session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
+ }
+
+ commonTestHelper.waitWithLatch { latch ->
+
+ val childObserver = object : Observer> {
+ override fun onChanged(children: List?) {
+// Log.d("## TEST", "Space A flat children update : ${children?.map { it.name }}")
+ System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}")
+ if (children?.any { it.name == "C1" } == true && children.any { it.name == "C2" }) {
+ // B1 has been added live!
+ latch.countDown()
+ flatAChildren.removeObserver(this)
+ }
+ }
+ }
+
+ val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
+ Triple("C1", true /*auto-join*/, true/*canonical*/),
+ Triple("C2", true, true)
+ ))
+
+ // add C as subspace of B
+ runBlocking {
+ val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
+ spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
+ }
+
+ // C1 and C2 should be in flatten child of A now
+
+ GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
+ }
+
+ // Test part one of the rooms
+
+ val bRoomId = spaceBInfo.roomIds.first()
+ val bRoom = session.getRoom(bRoomId)
+
+ commonTestHelper.waitWithLatch { latch ->
+
+ val childObserver = object : Observer> {
+ override fun onChanged(children: List?) {
+ System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}")
+ if (children?.any { it.roomId == bRoomId } == false) {
+ // B1 has been added live!
+ latch.countDown()
+ flatAChildren.removeObserver(this)
+ }
+ }
+ }
+
+ // part from b room
+ runBlocking {
+ bRoom!!.leave(null)
+ }
+ // The room should have disapear from flat children
+ GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
+ }
+ }
+
+ data class TestSpaceCreationResult(
+ val spaceId: String,
+ val roomIds: List
+ )
+
+ private fun createPublicSpace(session: Session,
+ spaceName: String,
+ childInfo: List>
+ /** Name, auto-join, canonical*/
+ ): TestSpaceCreationResult {
+ var spaceId = ""
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true)
+ it.countDown()
+ }
+ }
+
+ val syncedSpace = session.spaceService().getSpace(spaceId)
+ val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+
+ val roomIds =
+ childInfo.map { entry ->
+ var roomId = ""
+ commonTestHelper.waitWithLatch {
+ GlobalScope.launch {
+ roomId = session.createRoom(CreateRoomParams().apply { name = entry.first })
+ it.countDown()
+ }
+ }
+ roomId
+ }
+
+ roomIds.forEachIndexed { index, roomId ->
+ runBlocking {
+ syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
+ val canonical = childInfo[index].third
+ if (canonical != null) {
+ session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
+ }
+ }
+ }
+ return TestSpaceCreationResult(spaceId, roomIds)
+ }
+
+ @Test
+ fun testRootSpaces() {
+ val session = commonTestHelper.createAccount("John", SessionTestParams(true))
+
+ val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
+ Triple("A1", true /*auto-join*/, true/*canonical*/),
+ Triple("A2", true, true)
+ ))
+
+ val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
+ Triple("B1", true /*auto-join*/, true/*canonical*/),
+ Triple("B2", true, true),
+ Triple("B3", true, true)
+ ))
+
+ val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
+ Triple("C1", true /*auto-join*/, true/*canonical*/),
+ Triple("C2", true, true)
+ ))
+
+ val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+
+ // add C as subspace of B
+ runBlocking {
+ val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
+ spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
+ }
+
+ Thread.sleep(2000)
+ // + A
+ // a1, a2
+ // + B
+ // b1, b2, b3
+ // + C
+ // + c1, c2
+
+ val rootSpaces = session.spaceService().getRootSpaceSummaries()
+
+ assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
index f1f9ba3916..7d1407c0d8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
@@ -16,12 +16,10 @@
package org.matrix.android.sdk.api.auth.data
-sealed class LoginFlowResult {
- data class Success(
- val supportedLoginTypes: List,
- val ssoIdentityProviders: List?,
- val isLoginAndRegistrationSupported: Boolean,
- val homeServerUrl: String,
- val isOutdatedHomeserver: Boolean
- ) : LoginFlowResult()
-}
+data class LoginFlowResult(
+ val supportedLoginTypes: List,
+ val ssoIdentityProviders: List?,
+ val isLoginAndRegistrationSupported: Boolean,
+ val homeServerUrl: String,
+ val isOutdatedHomeserver: Boolean
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
index 38a5a77291..f059bf26c4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
@@ -20,7 +20,9 @@ interface RegistrationWizard {
suspend fun getRegistrationFlow(): RegistrationResult
- suspend fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?): RegistrationResult
+ suspend fun createAccount(userName: String?,
+ password: String?,
+ initialDeviceDisplayName: String?): RegistrationResult
suspend fun performReCaptcha(response: String): RegistrationResult
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt
index b241903364..8f1bbb6941 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt
@@ -32,7 +32,6 @@ import java.io.IOException
*/
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
- data class Cancelled(val throwable: Throwable? = null) : Failure(throwable)
data class UnrecognizedCertificateFailure(val url: String, val fingerprint: Fingerprint) : Failure()
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt
index 3820a442aa..73b0fe0a7c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt
@@ -41,7 +41,7 @@ data class MatrixError(
// For M_LIMIT_EXCEEDED
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
// For M_UNKNOWN_TOKEN
- @Json(name = "soft_logout") val isSoftLogout: Boolean = false,
+ @Json(name = "soft_logout") val isSoftLogout: Boolean? = null,
// For M_INVALID_PEPPER
// {"error": "pepper does not match 'erZvr'", "lookup_pepper": "pQgMS", "algorithm": "sha256", "errcode": "M_INVALID_PEPPER"}
@Json(name = "lookup_pepper") val newLookupPepper: String? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/ActiveSpaceFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/ActiveSpaceFilter.kt
new file mode 100644
index 0000000000..48619b9394
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/ActiveSpaceFilter.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.query
+
+sealed class ActiveSpaceFilter {
+ object None : ActiveSpaceFilter()
+ data class ActiveSpace(val currentSpaceId: String?) : ActiveSpaceFilter()
+ data class ExcludeSpace(val spaceId: String) : ActiveSpaceFilter()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
index a15799d862..86252665a6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
@@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.search.SearchService
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
import org.matrix.android.sdk.api.session.signout.SignOutService
+import org.matrix.android.sdk.api.session.space.SpaceService
import org.matrix.android.sdk.api.session.sync.FilterService
import org.matrix.android.sdk.api.session.sync.SyncState
import org.matrix.android.sdk.api.session.terms.TermsService
@@ -227,6 +228,11 @@ interface Session :
*/
fun thirdPartyService(): ThirdPartyService
+ /**
+ * Returns the space service associated with the session
+ */
+ fun spaceService(): SpaceService
+
/**
* Add a listener to the session.
* @param listener the listener to add.
@@ -249,13 +255,13 @@ interface Session :
/**
* A global session listener to get notified for some events.
*/
- interface Listener {
+ interface Listener : SessionLifecycleObserver {
/**
* Possible cases:
* - The access token is not valid anymore,
* - a M_CONSENT_NOT_GIVEN error has been received from the homeserver
*/
- fun onGlobalError(globalError: GlobalError)
+ fun onGlobalError(session: Session, globalError: GlobalError)
}
val sharedSecretStorageService: SharedSecretStorageService
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionLifecycleObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionLifecycleObserver.kt
similarity index 80%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionLifecycleObserver.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionLifecycleObserver.kt
index cb37fbec75..b76e454e4b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionLifecycleObserver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionLifecycleObserver.kt
@@ -14,20 +14,19 @@
* limitations under the License.
*/
-package org.matrix.android.sdk.internal.session
+package org.matrix.android.sdk.api.session
import androidx.annotation.MainThread
/**
* This defines methods associated with some lifecycle events of a session.
- * A list of SessionLifecycle will be injected into [DefaultSession]
*/
-internal interface SessionLifecycleObserver {
+interface SessionLifecycleObserver {
/*
Called when the session is opened
*/
@MainThread
- fun onSessionStarted() {
+ fun onSessionStarted(session: Session) {
// noop
}
@@ -35,7 +34,7 @@ internal interface SessionLifecycleObserver {
Called when the session is cleared
*/
@MainThread
- fun onClearCache() {
+ fun onClearCache(session: Session) {
// noop
}
@@ -43,7 +42,7 @@ internal interface SessionLifecycleObserver {
Called when the session is closed
*/
@MainThread
- fun onSessionStopped() {
+ fun onSessionStopped(session: Session) {
// noop
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUploadStateTracker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUploadStateTracker.kt
index 924da6c19b..ec63eb0be2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUploadStateTracker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUploadStateTracker.kt
@@ -31,6 +31,8 @@ interface ContentUploadStateTracker {
sealed class State {
object Idle : State()
object EncryptingThumbnail : State()
+ object CompressingImage : State()
+ data class CompressingVideo(val percent: Float) : State()
data class UploadingThumbnail(val current: Long, val total: Long) : State()
data class Encrypting(val current: Long, val total: Long) : State()
data class Uploading(val current: Long, val total: Long) : State()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 89b873febb..6400dd6444 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -28,6 +28,8 @@ import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.json.JSONObject
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.failure.MatrixError
import timber.log.Timber
typealias Content = JsonDict
@@ -90,6 +92,16 @@ data class Event(
@Transient
var sendState: SendState = SendState.UNKNOWN
+ @Transient
+ var sendStateDetails: String? = null
+
+ fun sendStateError(): MatrixError? {
+ return sendStateDetails?.let {
+ val matrixErrorAdapter = MoshiProvider.providesMoshi().adapter(MatrixError::class.java)
+ tryOrNull { matrixErrorAdapter.fromJson(it) }
+ }
+ }
+
/**
* The `age` value transcoded in a timestamp based on the device clock when the SDK received
* the event from the home server.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
index 905e18b8e8..d2befca1ee 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
@@ -52,6 +52,10 @@ object EventType {
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
+ const val STATE_SPACE_CHILD = "m.space.child"
+
+ const val STATE_SPACE_PARENT = "m.space.parent"
+
/**
* Note that this Event has been deprecated, see
* - https://matrix.org/docs/spec/client_server/r0.6.1#historical-events
@@ -74,6 +78,7 @@ object EventType {
const val CALL_NEGOTIATE = "m.call.negotiate"
const val CALL_REJECT = "m.call.reject"
const val CALL_HANGUP = "m.call.hangup"
+
// This type is not processed by the client, just sent to the server
const val CALL_REPLACES = "m.call.replaces"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
index adfdc2498e..23dc1e0ba8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
@@ -29,14 +29,19 @@ import java.io.File
*/
interface FileService {
- enum class FileState {
- IN_CACHE,
- DOWNLOADING,
- UNKNOWN
+ sealed class FileState {
+ /**
+ * The original file is in cache, but the decrypted files can be deleted for security reason.
+ * To decrypt the file again, call [downloadFile], the encrypted file will not be downloaded again
+ * @param decryptedFileInCache true if the decrypted file is available. Always true for clear files.
+ */
+ data class InCache(val decryptedFileInCache: Boolean) : FileState()
+ object Downloading : FileState()
+ object Unknown : FileState()
}
/**
- * Download a file.
+ * Download a file if necessary and ensure that if the file is encrypted, the file is decrypted.
* Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision.
*/
suspend fun downloadFile(fileName: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt
index e493adeaf2..05fa24946a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt
@@ -66,6 +66,7 @@ interface ProfileService {
/**
* Get the combined profile information for this user.
* This may return keys which are not limited to displayname or avatar_url.
+ * If server is configured as limit_profile_requests_to_users_who_share_rooms: true then response can be HTTP 403.
* @param userId the userId param to look for
*
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
index 9ea820f5b3..a5ec100f64 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
@@ -66,12 +66,13 @@ interface PushersService {
/**
* Directly ask the push gateway to send a push to this device
+ * If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
+ * In case of error, PusherRejected will be thrown. In this case it means that the pushkey is not valid.
+ *
* @param url the push gateway url (full path)
* @param appId the application id
* @param pushkey the FCM token
* @param eventId the eventId which will be sent in the Push message. Use a fake eventId.
- * @param callback callback to know if the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
- * In case of error, PusherRejected failure can happen. In this case it means that the pushkey is not valid.
*/
suspend fun testPush(url: String,
appId: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
index 257c83564e..f3eeb902a8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
@@ -34,6 +34,7 @@ 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.uploads.UploadsService
import org.matrix.android.sdk.api.session.search.SearchResult
+import org.matrix.android.sdk.api.session.space.Space
import org.matrix.android.sdk.api.util.Optional
/**
@@ -91,4 +92,9 @@ interface Room :
beforeLimit: Int,
afterLimit: Int,
includeProfile: Boolean): SearchResult
+
+ /**
+ * Use this room as a Space, if the type is correct.
+ */
+ fun asSpace(): Space?
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
index 8c833644ee..871c5378a6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
@@ -18,15 +18,14 @@ package org.matrix.android.sdk.api.session.room
import androidx.lifecycle.LiveData
import androidx.paging.PagedList
-import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
+import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
-import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
@@ -38,22 +37,19 @@ interface RoomService {
/**
* Create a room asynchronously
*/
- fun createRoom(createRoomParams: CreateRoomParams,
- callback: MatrixCallback): Cancelable
+ suspend fun createRoom(createRoomParams: CreateRoomParams): String
/**
* Create a direct room asynchronously. This is a facility method to create a direct room with the necessary parameters
*/
- fun createDirectRoom(otherUserId: String,
- callback: MatrixCallback): Cancelable {
+ suspend fun createDirectRoom(otherUserId: String): String {
return createRoom(
CreateRoomParams()
.apply {
invitedUserIds.add(otherUserId)
setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = true
- },
- callback
+ }
)
}
@@ -63,10 +59,9 @@ interface RoomService {
* @param reason optional reason for joining the room
* @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room.
*/
- fun joinRoom(roomIdOrAlias: String,
- reason: String? = null,
- viaServers: List = emptyList(),
- callback: MatrixCallback): Cancelable
+ suspend fun joinRoom(roomIdOrAlias: String,
+ reason: String? = null,
+ viaServers: List = emptyList())
/**
* Get a room from a roomId
@@ -112,20 +107,18 @@ interface RoomService {
* Inform the Matrix SDK that a room is displayed.
* The SDK will update the breadcrumbs in the user account data
*/
- fun onRoomDisplayed(roomId: String): Cancelable
+ suspend fun onRoomDisplayed(roomId: String)
/**
* Mark all rooms as read
*/
- fun markAllAsRead(roomIds: List,
- callback: MatrixCallback): Cancelable
+ suspend fun markAllAsRead(roomIds: List)
/**
* Resolve a room alias to a room ID.
*/
- fun getRoomIdByAlias(roomAlias: String,
- searchOnServer: Boolean,
- callback: MatrixCallback>): Cancelable
+ suspend fun getRoomIdByAlias(roomAlias: String,
+ searchOnServer: Boolean): Optional
/**
* Delete a room alias
@@ -172,26 +165,28 @@ interface RoomService {
/**
* Get some state events about a room
*/
- fun getRoomState(roomId: String, callback: MatrixCallback>)
+ suspend fun getRoomState(roomId: String): List
/**
* Use this if you want to get information from a room that you are not yet in (or invited)
* It might be possible to get some information on this room if it is public or if guest access is allowed
* This call will try to gather some information on this room, but it could fail and get nothing more
*/
- fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback)
+ suspend fun peekRoom(roomIdOrAlias: String): PeekResult
/**
* TODO Doc
*/
fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
- pagedListConfig: PagedList.Config = defaultPagedListConfig): LiveData>
+ pagedListConfig: PagedList.Config = defaultPagedListConfig,
+ sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData>
/**
* TODO Doc
*/
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
- pagedListConfig: PagedList.Config = defaultPagedListConfig): UpdatableFilterLivePageResult
+ pagedListConfig: PagedList.Config = defaultPagedListConfig,
+ sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult
/**
* TODO Doc
@@ -205,4 +200,12 @@ interface RoomService {
.setEnablePlaceholders(false)
.setPrefetchDistance(10)
.build()
+
+ fun getFlattenRoomSummaryChildrenOf(spaceId: String?, memberships: List = Membership.activeMemberships()) : List
+
+ /**
+ * Returns all the children of this space, as LiveData
+ */
+ fun getFlattenRoomSummaryChildrenOfLive(spaceId: String?,
+ memberships: List = Membership.activeMemberships()): LiveData>
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt
new file mode 100644
index 0000000000..36da242527
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt
@@ -0,0 +1,23 @@
+/*
+ * 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
+
+enum class RoomSortOrder {
+ NAME,
+ ACTIVITY,
+ NONE
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
index 7e04ebb5f2..88ec2de768 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
@@ -16,15 +16,35 @@
package org.matrix.android.sdk.api.session.room
+import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.RoomType
+import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams {
return RoomSummaryQueryParams.Builder().apply(init).build()
}
+fun spaceSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): SpaceSummaryQueryParams {
+ return RoomSummaryQueryParams.Builder()
+ .apply(init)
+ .apply {
+ includeType = listOf(RoomType.SPACE)
+ excludeType = null
+ roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
+ }
+ .build()
+}
+
+enum class RoomCategoryFilter {
+ ONLY_DM,
+ ONLY_ROOMS,
+ ALL
+}
+
/**
* This class can be used to filter room summaries to use with:
* [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService]
@@ -35,7 +55,11 @@ data class RoomSummaryQueryParams(
val canonicalAlias: QueryStringValue,
val memberships: List,
val roomCategoryFilter: RoomCategoryFilter?,
- val roomTagQueryFilter: RoomTagQueryFilter?
+ val roomTagQueryFilter: RoomTagQueryFilter?,
+ val excludeType: List?,
+ val includeType: List?,
+ val activeSpaceFilter: ActiveSpaceFilter?,
+ var activeGroupId: String? = null
) {
class Builder {
@@ -46,6 +70,10 @@ data class RoomSummaryQueryParams(
var memberships: List = Membership.all()
var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL
var roomTagQueryFilter: RoomTagQueryFilter? = null
+ var excludeType: List? = listOf(RoomType.SPACE)
+ var includeType: List? = null
+ var activeSpaceFilter: ActiveSpaceFilter = ActiveSpaceFilter.None
+ var activeGroupId: String? = null
fun build() = RoomSummaryQueryParams(
roomId = roomId,
@@ -53,7 +81,11 @@ data class RoomSummaryQueryParams(
canonicalAlias = canonicalAlias,
memberships = memberships,
roomCategoryFilter = roomCategoryFilter,
- roomTagQueryFilter = roomTagQueryFilter
+ roomTagQueryFilter = roomTagQueryFilter,
+ excludeType = excludeType,
+ includeType = includeType,
+ activeSpaceFilter = activeSpaceFilter,
+ activeGroupId = activeGroupId
)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt
similarity index 72%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt
index 71b3c665e7..b83f57f5ef 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt
@@ -20,8 +20,16 @@ import androidx.lifecycle.LiveData
import androidx.paging.PagedList
import org.matrix.android.sdk.api.session.room.model.RoomSummary
-interface UpdatableFilterLivePageResult {
+interface UpdatableLivePageResult {
val livePagedList: LiveData>
- fun updateQuery(queryParams: RoomSummaryQueryParams)
+ fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams)
+
+ val liveBoundaries: LiveData
}
+
+data class ResultBoundaries(
+ val frontLoaded: Boolean = false,
+ val endLoaded: Boolean = false,
+ val zeroItemLoaded: Boolean = false
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt
index d2cb7c58a9..1102eda11c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt
@@ -17,7 +17,7 @@
package org.matrix.android.sdk.api.session.room.alias
sealed class RoomAliasError : Throwable() {
- object AliasEmpty : RoomAliasError()
+ object AliasIsBlank : RoomAliasError()
object AliasNotAvailable : RoomAliasError()
object AliasInvalid : RoomAliasError()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt
index 208cdd4556..deab0ca3e7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt
@@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
sealed class CreateRoomFailure : Failure.FeatureFailure() {
- object CreatedWithTimeout : CreateRoomFailure()
+ data class CreatedWithTimeout(val roomID: String) : CreateRoomFailure()
data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure()
data class AliasError(val aliasError: RoomAliasError) : CreateRoomFailure()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
index e778f5740d..5c46db7166 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
@@ -28,43 +28,43 @@ data class PowerLevelsContent(
/**
* The level required to ban a user. Defaults to 50 if unspecified.
*/
- @Json(name = "ban") val ban: Int = Role.Moderator.value,
+ @Json(name = "ban") val ban: Int? = null,
/**
* The level required to kick a user. Defaults to 50 if unspecified.
*/
- @Json(name = "kick") val kick: Int = Role.Moderator.value,
+ @Json(name = "kick") val kick: Int? = null,
/**
* The level required to invite a user. Defaults to 50 if unspecified.
*/
- @Json(name = "invite") val invite: Int = Role.Moderator.value,
+ @Json(name = "invite") val invite: Int? = null,
/**
* The level required to redact an event. Defaults to 50 if unspecified.
*/
- @Json(name = "redact") val redact: Int = Role.Moderator.value,
+ @Json(name = "redact") val redact: Int? = null,
/**
* The default level required to send message events. Can be overridden by the events key. Defaults to 0 if unspecified.
*/
- @Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
+ @Json(name = "events_default") val eventsDefault: Int? = null,
/**
* The level required to send specific event types. This is a mapping from event type to power level required.
*/
- @Json(name = "events") val events: Map = emptyMap(),
+ @Json(name = "events") val events: Map? = null,
/**
* The default power level for every user in the room, unless their user_id is mentioned in the users key. Defaults to 0 if unspecified.
*/
- @Json(name = "users_default") val usersDefault: Int = Role.Default.value,
+ @Json(name = "users_default") val usersDefault: Int? = null,
/**
* The power levels for specific users. This is a mapping from user_id to power level for that user.
*/
- @Json(name = "users") val users: Map = emptyMap(),
+ @Json(name = "users") val users: Map? = null,
/**
* The default level required to send state events. Can be overridden by the events key. Defaults to 50 if unspecified.
*/
- @Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
+ @Json(name = "state_default") val stateDefault: Int? = null,
/**
* The power level requirements for specific notification types. This is a mapping from key to power level for that notifications key.
*/
- @Json(name = "notifications") val notifications: Map = emptyMap()
+ @Json(name = "notifications") val notifications: Map? = null
) {
/**
* Return a copy of this content with a new power level for the specified user
@@ -74,7 +74,7 @@ data class PowerLevelsContent(
*/
fun setUserPowerLevel(userId: String, powerLevel: Int?): PowerLevelsContent {
return copy(
- users = users.toMutableMap().apply {
+ users = users.orEmpty().toMutableMap().apply {
if (powerLevel == null || powerLevel == usersDefault) {
remove(userId)
} else {
@@ -91,7 +91,7 @@ data class PowerLevelsContent(
* @return the level, default to Moderator if the key is not found
*/
fun notificationLevel(key: String): Int {
- return when (val value = notifications[key]) {
+ return when (val value = notifications.orEmpty()[key]) {
// the first implementation was a string value
is String -> value.toInt()
is Double -> value.toInt()
@@ -107,3 +107,12 @@ data class PowerLevelsContent(
const val NOTIFICATIONS_ROOM_KEY = "room"
}
}
+
+// Fallback to default value, defined in the Matrix specification
+fun PowerLevelsContent.banOrDefault() = ban ?: Role.Moderator.value
+fun PowerLevelsContent.kickOrDefault() = kick ?: Role.Moderator.value
+fun PowerLevelsContent.inviteOrDefault() = invite ?: Role.Moderator.value
+fun PowerLevelsContent.redactOrDefault() = redact ?: Role.Moderator.value
+fun PowerLevelsContent.eventsDefaultOrDefault() = eventsDefault ?: Role.Default.value
+fun PowerLevelsContent.usersDefaultOrDefault() = usersDefault ?: Role.Default.value
+fun PowerLevelsContent.stateDefaultOrDefault() = stateDefault ?: Role.Moderator.value
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt
index 0760c6f1b4..020e7ed39e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt
@@ -40,7 +40,7 @@ data class RoomGuestAccessContent(
}
@JsonClass(generateAdapter = false)
-enum class GuestAccess {
- @Json(name = "can_join") CanJoin,
- @Json(name = "forbidden") Forbidden
+enum class GuestAccess(val value: String) {
+ @Json(name = "can_join") CanJoin("can_join"),
+ @Json(name = "forbidden") Forbidden("forbidden")
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt
index f3e8d357f3..a86301a276 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt
@@ -24,9 +24,10 @@ import com.squareup.moshi.JsonClass
* Enum for [RoomJoinRulesContent] : https://matrix.org/docs/spec/client_server/r0.4.0#m-room-join-rules
*/
@JsonClass(generateAdapter = false)
-enum class RoomJoinRules {
- @Json(name = "public") PUBLIC,
- @Json(name = "invite") INVITE,
- @Json(name = "knock") KNOCK,
- @Json(name = "private") PRIVATE
+enum class RoomJoinRules(val value: String) {
+ @Json(name = "public") PUBLIC("public"),
+ @Json(name = "invite") INVITE("invite"),
+ @Json(name = "knock") KNOCK("knock"),
+ @Json(name = "private") PRIVATE("private"),
+ @Json(name = "restricted") RESTRICTED("restricted")
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesAllowEntry.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesAllowEntry.kt
new file mode 100644
index 0000000000..7b87bc34d2
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesAllowEntry.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.matrix.android.sdk.api.session.room.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class RoomJoinRulesAllowEntry(
+ /**
+ * space: The room ID of the space to check the membership of.
+ */
+ @Json(name = "space") val spaceID: String,
+ /**
+ * via: A list of servers which may be used to peek for membership of the space.
+ */
+ @Json(name = "via") val via: List
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
index 8082486b22..33f402cad3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
@@ -1,5 +1,6 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
+ * 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.
@@ -26,14 +27,19 @@ import timber.log.Timber
*/
@JsonClass(generateAdapter = true)
data class RoomJoinRulesContent(
- @Json(name = "join_rule") val _joinRules: String? = null
+ @Json(name = "join_rule") val _joinRules: String? = null,
+ /**
+ * If the allow key is an empty list (or not a list at all), then the room reverts to standard public join rules
+ */
+ @Json(name = "allow") val allowList: List? = null
) {
val joinRules: RoomJoinRules? = when (_joinRules) {
- "public" -> RoomJoinRules.PUBLIC
- "invite" -> RoomJoinRules.INVITE
- "knock" -> RoomJoinRules.KNOCK
+ "public" -> RoomJoinRules.PUBLIC
+ "invite" -> RoomJoinRules.INVITE
+ "knock" -> RoomJoinRules.KNOCK
"private" -> RoomJoinRules.PRIVATE
- else -> {
+ "restricted" -> RoomJoinRules.RESTRICTED
+ else -> {
Timber.w("Invalid value for RoomJoinRules: `$_joinRules`")
null
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt
index 9455a83aff..cae4775e71 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt
@@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
* This class holds some data of a room.
* It can be retrieved by [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService]
*/
-data class RoomSummary constructor(
+data class RoomSummary(
val roomId: String,
// Computed display name
val displayName: String = "",
@@ -35,7 +35,9 @@ data class RoomSummary constructor(
val avatarUrl: String = "",
val canonicalAlias: String? = null,
val aliases: List = emptyList(),
+ val joinRules: RoomJoinRules? = null,
val isDirect: Boolean = false,
+ val directUserId: String? = null,
val joinedMembersCount: Int? = 0,
val invitedMembersCount: Int? = 0,
val latestPreviewableEvent: TimelineEvent? = null,
@@ -54,7 +56,11 @@ data class RoomSummary constructor(
val inviterId: String? = null,
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null,
- val hasFailedSending: Boolean = false
+ val hasFailedSending: Boolean = false,
+ val roomType: String? = null,
+ val spaceParents: List? = null,
+ val spaceChildren: List? = null,
+ val flattenParentIds: List = emptyList()
) {
val isVersioned: Boolean
@@ -69,6 +75,9 @@ data class RoomSummary constructor(
val isFavorite: Boolean
get() = hasTag(RoomTag.ROOM_TAG_FAVOURITE)
+ val isPublic: Boolean
+ get() = joinRules == RoomJoinRules.PUBLIC
+
fun hasTag(tag: String) = tags.any { it.name == tag }
val canStartCall: Boolean
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomThirdPartyInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomThirdPartyInviteContent.kt
index 56503e3e35..a8a2cfb68b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomThirdPartyInviteContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomThirdPartyInviteContent.kt
@@ -47,7 +47,7 @@ data class RoomThirdPartyInviteContent(
/**
* Keys with which the token may be signed.
*/
- @Json(name = "public_keys") val publicKeys: List? = emptyList()
+ @Json(name = "public_keys") val publicKeys: List?
)
@JsonClass(generateAdapter = true)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.kt
new file mode 100644
index 0000000000..b0f3a56d67
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.model
+
+object RoomType {
+
+ const val SPACE = "m.space"
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt
new file mode 100644
index 0000000000..2d31930b33
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.model
+
+data class SpaceChildInfo(
+ val childRoomId: String,
+ // We might not know this child at all,
+ // i.e we just know it exists but no info on type/name/etc..
+ val isKnown: Boolean,
+ val roomType: String?,
+ val name: String?,
+ val topic: String?,
+ val avatarUrl: String?,
+ val order: String?,
+ val activeMemberCount: Int?,
+ val autoJoin: Boolean,
+ val viaServers: List,
+ val parentRoomId: String?,
+ val suggested: Boolean?
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceParentInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceParentInfo.kt
new file mode 100644
index 0000000000..5ed81b0646
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceParentInfo.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.model
+
+data class SpaceParentInfo(
+ val parentId: String?,
+ val roomSummary: RoomSummary?,
+ val canonical: Boolean?,
+ val viaServers: List
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt
index 80e3741a0c..ca8c66bb3b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt
@@ -18,13 +18,15 @@ package org.matrix.android.sdk.api.session.room.model.create
import android.net.Uri
import org.matrix.android.sdk.api.session.identity.ThreePid
+import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
// TODO Give a way to include other initial states
-class CreateRoomParams {
+open class CreateRoomParams {
/**
* A public visibility indicates that the room will be shown in the published room list.
* A private visibility will hide the room from the published room list.
@@ -68,6 +70,11 @@ class CreateRoomParams {
*/
val invite3pids = mutableListOf()
+ /**
+ * Initial Guest Access
+ */
+ var guestAccess: GuestAccess? = null
+
/**
* If set to true, when the room will be created, if cross-signing is enabled and we can get keys for every invited users,
* the encryption will be enabled on the created room
@@ -111,6 +118,17 @@ class CreateRoomParams {
}
}
+ var roomType: String? = null // RoomType.MESSAGING
+ set(value) {
+ field = value
+ if (value != null) {
+ creationContent[CREATION_CONTENT_KEY_ROOM_TYPE] = value
+ } else {
+ // This is the default value, we remove the field
+ creationContent.remove(CREATION_CONTENT_KEY_ROOM_TYPE)
+ }
+ }
+
/**
* The power level content to override in the default power level event
*/
@@ -136,7 +154,12 @@ class CreateRoomParams {
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
}
+ var roomVersion: String? = null
+
+ var joinRuleRestricted: List? = null
+
companion object {
private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate"
+ private const val CREATION_CONTENT_KEY_ROOM_TYPE = "type"
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt
index 0b595b1b2b..52e5c0e9c7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt
@@ -26,5 +26,7 @@ import com.squareup.moshi.JsonClass
data class RoomCreateContent(
@Json(name = "creator") val creator: String? = null,
@Json(name = "room_version") val roomVersion: String? = null,
- @Json(name = "predecessor") val predecessor: Predecessor? = null
+ @Json(name = "predecessor") val predecessor: Predecessor? = null,
+ // Defines the room type, see #RoomType (user extensible)
+ @Json(name = "type") val type: String? = null
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt
index e85bb0800a..f21074096e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt
@@ -47,3 +47,10 @@ data class FileInfo(
*/
@Json(name = "thumbnail_file") val thumbnailFile: EncryptedFileInfo? = null
)
+
+/**
+ * Get the url of the encrypted thumbnail or of the thumbnail
+ */
+fun FileInfo.getThumbnailUrl(): String? {
+ return thumbnailFile?.url ?: thumbnailUrl
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt
index 048febec39..c38ef5bc27 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt
@@ -40,7 +40,7 @@ data class ImageInfo(
/**
* Size of the image in bytes.
*/
- @Json(name = "size") val size: Int = 0,
+ @Json(name = "size") val size: Long = 0,
/**
* Metadata about the image referred to in thumbnail_url.
@@ -57,3 +57,10 @@ data class ImageInfo(
*/
@Json(name = "thumbnail_file") val thumbnailFile: EncryptedFileInfo? = null
)
+
+/**
+ * Get the url of the encrypted thumbnail or of the thumbnail
+ */
+fun ImageInfo.getThumbnailUrl(): String? {
+ return thumbnailFile?.url ?: thumbnailUrl
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt
index a6908dce5b..a76c3c5b64 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt
@@ -37,3 +37,10 @@ data class LocationInfo(
*/
@Json(name = "thumbnail_file") val thumbnailFile: EncryptedFileInfo? = null
)
+
+/**
+ * Get the url of the encrypted thumbnail or of the thumbnail
+ */
+fun LocationInfo.getThumbnailUrl(): String? {
+ return thumbnailFile?.url ?: thumbnailUrl
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt
index 8379ee9338..8a36c26313 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt
@@ -62,3 +62,10 @@ data class VideoInfo(
*/
@Json(name = "thumbnail_file") val thumbnailFile: EncryptedFileInfo? = null
)
+
+/**
+ * Get the url of the encrypted thumbnail or of the thumbnail
+ */
+fun VideoInfo.getThumbnailUrl(): String? {
+ return thumbnailFile?.url ?: thumbnailUrl
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt
index db70dadef3..888950dc12 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt
@@ -16,6 +16,8 @@
package org.matrix.android.sdk.api.session.room.peeking
+import org.matrix.android.sdk.api.util.MatrixItem
+
sealed class PeekResult {
data class Success(
val roomId: String,
@@ -24,7 +26,9 @@ sealed class PeekResult {
val topic: String?,
val avatarUrl: String?,
val numJoinedMembers: Int?,
- val viaServers: List
+ val roomType: String?,
+ val viaServers: List,
+ val someMembers: List?
) : PeekResult()
data class PeekingNotAllowed(
@@ -34,4 +38,6 @@ sealed class PeekResult {
) : PeekResult()
object UnknownAlias : PeekResult()
+
+ fun isSuccess() = this is Success
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
index 4f1253c6df..99139723a8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
@@ -18,6 +18,13 @@
package org.matrix.android.sdk.api.session.room.powerlevels
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
+import org.matrix.android.sdk.api.session.room.model.banOrDefault
+import org.matrix.android.sdk.api.session.room.model.eventsDefaultOrDefault
+import org.matrix.android.sdk.api.session.room.model.inviteOrDefault
+import org.matrix.android.sdk.api.session.room.model.kickOrDefault
+import org.matrix.android.sdk.api.session.room.model.redactOrDefault
+import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault
+import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault
/**
* This class is an helper around PowerLevelsContent.
@@ -31,9 +38,9 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
* @return the power level
*/
fun getUserPowerLevelValue(userId: String): Int {
- return powerLevelsContent.users.getOrElse(userId) {
- powerLevelsContent.usersDefault
- }
+ return powerLevelsContent.users
+ ?.get(userId)
+ ?: powerLevelsContent.usersDefaultOrDefault()
}
/**
@@ -45,7 +52,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
fun getUserRole(userId: String): Role {
val value = getUserPowerLevelValue(userId)
// I think we should use powerLevelsContent.usersDefault, but Ganfra told me that it was like that on riot-Web
- return Role.fromValue(value, powerLevelsContent.eventsDefault)
+ return Role.fromValue(value, powerLevelsContent.eventsDefaultOrDefault())
}
/**
@@ -59,11 +66,11 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
fun isUserAllowedToSend(userId: String, isState: Boolean, eventType: String?): Boolean {
return if (userId.isNotEmpty()) {
val powerLevel = getUserPowerLevelValue(userId)
- val minimumPowerLevel = powerLevelsContent.events[eventType]
+ val minimumPowerLevel = powerLevelsContent.events?.get(eventType)
?: if (isState) {
- powerLevelsContent.stateDefault
+ powerLevelsContent.stateDefaultOrDefault()
} else {
- powerLevelsContent.eventsDefault
+ powerLevelsContent.eventsDefaultOrDefault()
}
powerLevel >= minimumPowerLevel
} else false
@@ -76,7 +83,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
*/
fun isUserAbleToInvite(userId: String): Boolean {
val powerLevel = getUserPowerLevelValue(userId)
- return powerLevel >= powerLevelsContent.invite
+ return powerLevel >= powerLevelsContent.inviteOrDefault()
}
/**
@@ -86,7 +93,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
*/
fun isUserAbleToBan(userId: String): Boolean {
val powerLevel = getUserPowerLevelValue(userId)
- return powerLevel >= powerLevelsContent.ban
+ return powerLevel >= powerLevelsContent.banOrDefault()
}
/**
@@ -96,7 +103,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
*/
fun isUserAbleToKick(userId: String): Boolean {
val powerLevel = getUserPowerLevelValue(userId)
- return powerLevel >= powerLevelsContent.kick
+ return powerLevel >= powerLevelsContent.kickOrDefault()
}
/**
@@ -106,6 +113,6 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
*/
fun isUserAbleToRedact(userId: String): Boolean {
val powerLevel = getUserPowerLevelValue(userId)
- return powerLevel >= powerLevelsContent.redact
+ return powerLevel >= powerLevelsContent.redactOrDefault()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt
index 066178b1ec..b3440059e8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt
@@ -20,6 +20,6 @@ data class RoomAggregateNotificationCount(
val notificationCount: Int,
val highlightCount: Int
) {
- val totalCount = notificationCount + highlightCount
+ val totalCount = notificationCount
val isHighlight = highlightCount > 0
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
index 32f6b94cd8..4a6462477d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
@@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.RelationType
import org.matrix.android.sdk.api.session.events.model.getRelationContent
+import org.matrix.android.sdk.api.session.events.model.isEdition
import org.matrix.android.sdk.api.session.events.model.isReply
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
@@ -151,6 +152,10 @@ fun TimelineEvent.isReply(): Boolean {
return root.isReply()
}
+fun TimelineEvent.isEdition(): Boolean {
+ return root.isEdition()
+}
+
fun TimelineEvent.getTextEditableContent(): String? {
val lastContent = getLastMessageContent()
return if (isReply()) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt
new file mode 100644
index 0000000000..42e6584838
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.space
+
+import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
+import org.matrix.android.sdk.api.session.room.model.RoomType
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+
+class CreateSpaceParams : CreateRoomParams() {
+
+ init {
+ // Space-rooms are distinguished from regular messaging rooms by the m.room.type of m.space
+ roomType = RoomType.SPACE
+
+ // Space-rooms should be created with a power level for events_default of 100,
+ // to prevent the rooms accidentally/maliciously clogging up with messages from random members of the space.
+ powerLevelContentOverride = PowerLevelsContent(
+ eventsDefault = 100
+ )
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/JoinSpaceResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/JoinSpaceResult.kt
new file mode 100644
index 0000000000..e8c69977c6
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/JoinSpaceResult.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.space
+
+sealed class JoinSpaceResult {
+ object Success : JoinSpaceResult()
+ data class Fail(val error: Throwable) : JoinSpaceResult()
+
+ /** Success fully joined the space, but failed to join all or some of it's rooms */
+ data class PartialSuccess(val failedRooms: Map) : JoinSpaceResult()
+
+ fun isSuccess() = this is Success || this is PartialSuccess
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
new file mode 100644
index 0000000000..db25762c2f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.space
+
+import org.matrix.android.sdk.api.session.room.Room
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+
+interface Space {
+
+ fun asRoom(): Room
+
+ val spaceId: String
+
+ suspend fun leave(reason: String? = null)
+
+ /**
+ * A current snapshot of [RoomSummary] associated with the space
+ */
+ fun spaceSummary(): RoomSummary?
+
+ suspend fun addChildren(roomId: String,
+ viaServers: List?,
+ order: String?,
+ autoJoin: Boolean = false,
+ suggested: Boolean? = false)
+
+ suspend fun removeChildren(roomId: String)
+
+ @Throws
+ suspend fun setChildrenOrder(roomId: String, order: String?)
+
+ @Throws
+ suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean)
+
+ @Throws
+ suspend fun setChildrenSuggested(roomId: String, suggested: Boolean)
+
+// fun getChildren() : List
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
new file mode 100644
index 0000000000..fedf38fe06
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.space
+
+import android.net.Uri
+import androidx.lifecycle.LiveData
+import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
+import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
+
+typealias SpaceSummaryQueryParams = RoomSummaryQueryParams
+
+interface SpaceService {
+
+ /**
+ * Create a space asynchronously
+ * @return the spaceId of the created space
+ */
+ suspend fun createSpace(params: CreateSpaceParams): String
+
+ /**
+ * Just a shortcut for space creation for ease of use
+ */
+ suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String
+
+ /**
+ * Get a space from a roomId
+ * @param spaceId the roomId to look for.
+ * @return a space with spaceId or null if room type is not space
+ */
+ fun getSpace(spaceId: String): Space?
+
+ /**
+ * Try to resolve (peek) rooms and subspace in this space.
+ * Use this call get preview of children of this space, particularly useful to get a
+ * preview of rooms that you did not join yet.
+ */
+ suspend fun peekSpace(spaceId: String): SpacePeekResult
+
+ /**
+ * Get's information of a space by querying the server
+ */
+ suspend fun querySpaceChildren(spaceId: String,
+ suggestedOnly: Boolean? = null,
+ autoJoinedOnly: Boolean? = null): Pair>
+
+ /**
+ * Get a live list of space summaries. This list is refreshed as soon as the data changes.
+ * @return the [LiveData] of List[SpaceSummary]
+ */
+ fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData>
+
+ fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List
+
+ suspend fun joinSpace(spaceIdOrAlias: String,
+ reason: String? = null,
+ viaServers: List = emptyList()): JoinSpaceResult
+
+ suspend fun rejectInvite(spaceId: String, reason: String?)
+
+// fun getSpaceParentsOfRoom(roomId: String) : List
+
+ /**
+ * Let this room declare that it has a parent.
+ * @param canonical true if it should be the main parent of this room
+ * In practice, well behaved rooms should only have one canonical parent, but given this is not enforced:
+ * if multiple are present the client should select the one with the lowest room ID, as determined via a lexicographic utf-8 ordering.
+ */
+ suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List)
+
+ fun getRootSpaceSummaries(): List
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt
new file mode 100644
index 0000000000..0c33cfa1e6
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.space.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * "content": {
+ * "via": ["example.com"],
+ * "order": "abcd",
+ * "default": true
+ * }
+ */
+@JsonClass(generateAdapter = true)
+data class SpaceChildContent(
+ /**
+ * Key which gives a list of candidate servers that can be used to join the room
+ * Children where via is not present are ignored.
+ */
+ @Json(name = "via") val via: List? = null,
+ /**
+ * The order key is a string which is used to provide a default ordering of siblings in the room list.
+ * (Rooms are sorted based on a lexicographic ordering of order values; rooms with no order come last.
+ * orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7F (~),
+ * or consist of more than 50 characters, are forbidden and should be ignored if received.)
+ */
+ @Json(name = "order") val order: String? = null,
+ /**
+ * The auto_join flag on a child listing allows a space admin to list the sub-spaces and rooms in that space which should
+ * be automatically joined by members of that space.
+ * (This is not a force-join, which are descoped for a future MSC; the user can subsequently part these room if they desire.)
+ */
+ @Json(name = "auto_join") val autoJoin: Boolean? = false,
+
+ /**
+ * If `suggested` is set to `true`, that indicates that the child should be advertised to
+ * members of the space by the client. This could be done by showing them eagerly
+ * in the room list. This is should be ignored if `auto_join` is set to `true`.
+ */
+ @Json(name = "suggested") val suggested: Boolean? = false
+) {
+ /**
+ * Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7F (~),
+ * or consist of more than 50 characters, are forbidden and should be ignored if received.)
+ */
+ fun validOrder(): String? {
+ return order
+ ?.takeIf { it.length <= 50 }
+ ?.takeIf { ORDER_VALID_CHAR_REGEX.matches(it) }
+ }
+
+ companion object {
+ private val ORDER_VALID_CHAR_REGEX = "[ -~]+".toRegex()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt
new file mode 100644
index 0000000000..871a494914
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.space.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * Rooms can claim parents via the m.space.parent state event.
+ * {
+ * "type": "m.space.parent",
+ * "state_key": "!space:example.com",
+ * "content": {
+ * "via": ["example.com"],
+ * "canonical": true,
+ * }
+ * }
+ */
+@JsonClass(generateAdapter = true)
+data class SpaceParentContent(
+ /**
+ * Key which gives a list of candidate servers that can be used to join the parent.
+ * Parents where via is not present are ignored.
+ */
+ @Json(name = "via") val via: List? = null,
+ /**
+ * Canonical determines whether this is the main parent for the space.
+ * When a user joins a room with a canonical parent, clients may switch to view the room
+ * in the context of that space, peeking into it in order to find other rooms and group them together.
+ * In practice, well behaved rooms should only have one canonical parent, but given this is not enforced:
+ * if multiple are present the client should select the one with the lowest room ID, as determined via a lexicographic utf-8 ordering.
+ */
+ @Json(name = "canonical") val canonical: Boolean? = false
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
index db229a6453..7b2fae86ef 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
@@ -20,6 +20,7 @@ import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
import org.matrix.android.sdk.api.session.user.model.User
@@ -157,3 +158,5 @@ fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAl
fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
+
+fun SpaceChildInfo.toMatrixItem() = MatrixItem.RoomItem(childRoomId, name, avatarUrl)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt
index c74999b4ab..182b37f2ad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.extensions.orFalse
object MimeTypes {
const val Any: String = "*/*"
const val OctetStream = "application/octet-stream"
+ const val Apk = "application/vnd.android.package-archive"
const val Images = "image/*"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
index e26286ad2f..46256f4b81 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
@@ -144,16 +144,14 @@ internal class DefaultAuthenticationService @Inject constructor(
}
return result.fold(
{
- if (it is LoginFlowResult.Success) {
- // The homeserver exists and up to date, keep the config
- // Homeserver url may have been changed, if it was a Riot url
- val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
- homeServerUri = Uri.parse(it.homeServerUrl)
- )
+ // The homeserver exists and up to date, keep the config
+ // Homeserver url may have been changed, if it was a Riot url
+ val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
+ homeServerUri = Uri.parse(it.homeServerUrl)
+ )
- pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
- .also { data -> pendingSessionStore.savePendingSessionData(data) }
- }
+ pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
+ .also { data -> pendingSessionStore.savePendingSessionData(data) }
it
},
{
@@ -307,12 +305,12 @@ internal class DefaultAuthenticationService @Inject constructor(
val loginFlowResponse = executeRequest(null) {
authAPI.getLoginFlows()
}
- return LoginFlowResult.Success(
- loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
- loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
- versions.isLoginAndRegistrationSupportedBySdk(),
- homeServerUrl,
- !versions.isSupportedBySdk()
+ return LoginFlowResult(
+ supportedLoginTypes = loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
+ ssoIdentityProviders = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
+ isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(),
+ homeServerUrl = homeServerUrl,
+ isOutdatedHomeserver = !versions.isSupportedBySdk()
)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
index 4a3d53a8fc..4a156e74cd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
@@ -66,8 +66,8 @@ internal class DefaultRegistrationWizard(
return performRegistrationRequest(params)
}
- override suspend fun createAccount(userName: String,
- password: String,
+ override suspend fun createAccount(userName: String?,
+ password: String?,
initialDeviceDisplayName: String?): RegistrationResult {
val params = RegistrationParams(
username = userName,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt
index 7eebbd9b2c..4004294d97 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt
@@ -44,7 +44,7 @@ data class CryptoDeviceInfo(
*/
fun fingerprint(): String? {
return keys
- ?.takeIf { !deviceId.isBlank() }
+ ?.takeIf { deviceId.isNotBlank() }
?.get("ed25519:$deviceId")
}
@@ -53,7 +53,7 @@ data class CryptoDeviceInfo(
*/
fun identityKey(): String? {
return keys
- ?.takeIf { !deviceId.isBlank() }
+ ?.takeIf { deviceId.isNotBlank() }
?.get("curve25519:$deviceId")
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt
index 3c651c27a0..00b8bde5d9 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt
@@ -103,7 +103,7 @@ data class MXDeviceInfo(
*/
fun fingerprint(): String? {
return keys
- ?.takeIf { !deviceId.isBlank() }
+ ?.takeIf { deviceId.isNotBlank() }
?.get("ed25519:$deviceId")
}
@@ -112,7 +112,7 @@ data class MXDeviceInfo(
*/
fun identityKey(): String? {
return keys
- ?.takeIf { !deviceId.isBlank() }
+ ?.takeIf { deviceId.isNotBlank() }
?.get("curve25519:$deviceId")
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt
index d8b9d3cd86..7fa48c3da1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt
@@ -23,6 +23,7 @@ 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.send.LocalEchoRepository
import org.matrix.android.sdk.internal.task.Task
+import org.matrix.android.sdk.internal.util.toMatrixErrorStr
import javax.inject.Inject
internal interface SendVerificationMessageTask : Task {
@@ -55,7 +56,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
localEchoRepository.updateSendState(localId, event.roomId, SendState.SENT)
return response.eventId
} catch (e: Throwable) {
- localEchoRepository.updateSendState(localId, event.roomId, SendState.UNDELIVERED)
+ localEchoRepository.updateSendState(localId, event.roomId, SendState.UNDELIVERED, e.toMatrixErrorStr())
throw e
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt
index f11ecc5d75..ee58880eb8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt
@@ -20,6 +20,7 @@ import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.database.helper.nextDisplayIndex
import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
@@ -29,7 +30,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
import org.matrix.android.sdk.internal.di.SessionDatabase
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
import org.matrix.android.sdk.internal.task.TaskExecutor
import timber.log.Timber
@@ -47,7 +48,7 @@ private const val MIN_NUMBER_OF_EVENTS_BY_CHUNK = 300
internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration,
private val taskExecutor: TaskExecutor) : SessionLifecycleObserver {
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
taskExecutor.executorScope.launch(Dispatchers.Default) {
awaitTransaction(realmConfiguration) { realm ->
val allRooms = realm.where(RoomEntity::class.java).findAll()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt
index 2a0cd963b2..c602ed7075 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt
@@ -17,7 +17,7 @@
package org.matrix.android.sdk.internal.database
import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.util.createBackgroundHandler
import io.realm.Realm
import io.realm.RealmChangeListener
@@ -28,6 +28,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.cancelChildren
+import org.matrix.android.sdk.api.session.Session
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
@@ -46,7 +47,7 @@ internal abstract class RealmLiveEntityObserver(protected val r
private val backgroundRealm = AtomicReference()
private lateinit var results: AtomicReference>
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
if (isStarted.compareAndSet(false, true)) {
BACKGROUND_HANDLER.post {
val realm = Realm.getInstance(realmConfiguration)
@@ -58,7 +59,7 @@ internal abstract class RealmLiveEntityObserver(protected val r
}
}
- override fun onSessionStopped() {
+ override fun onSessionStopped(session: Session) {
if (isStarted.compareAndSet(true, false)) {
BACKGROUND_HANDLER.post {
results.getAndSet(null).removeAllChangeListeners()
@@ -70,7 +71,7 @@ internal abstract class RealmLiveEntityObserver(protected val r
}
}
- override fun onClearCache() {
+ override fun onClearCache(session: Session) {
observerScope.coroutineContext.cancelChildren()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt
index f8d5d323a5..52fbabb49f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt
@@ -20,8 +20,9 @@ import android.os.Looper
import androidx.annotation.MainThread
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
+import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.di.SessionDatabase
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.session.SessionScope
import javax.inject.Inject
import kotlin.concurrent.getOrSet
@@ -44,14 +45,14 @@ internal class RealmSessionProvider @Inject constructor(@SessionDatabase private
}
@MainThread
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
realmThreadLocal.getOrSet {
Realm.getInstance(monarchy.realmConfiguration)
}
}
@MainThread
- override fun onSessionStopped() {
+ override fun onSessionStopped(session: Session) {
realmThreadLocal.get()?.close()
realmThreadLocal.remove()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index 1daae906f2..211059a345 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -19,7 +19,11 @@ package org.matrix.android.sdk.internal.database
import io.realm.DynamicRealm
import io.realm.FieldAttribute
import io.realm.RealmMigration
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
+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.internal.database.model.CurrentStateEventEntityFields
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
import org.matrix.android.sdk.internal.database.model.EventEntityFields
@@ -30,14 +34,17 @@ import org.matrix.android.sdk.internal.database.model.RoomEntityFields
import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields
+import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
+import org.matrix.android.sdk.internal.di.MoshiProvider
import timber.log.Timber
import javax.inject.Inject
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
companion object {
- const val SESSION_STORE_SCHEMA_VERSION = 9L
+ const val SESSION_STORE_SCHEMA_VERSION = 12L
}
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
@@ -52,6 +59,9 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
if (oldVersion <= 6) migrateTo7(realm)
if (oldVersion <= 7) migrateTo8(realm)
if (oldVersion <= 8) migrateTo9(realm)
+ if (oldVersion <= 9) migrateTo10(realm)
+ if (oldVersion <= 10) migrateTo11(realm)
+ if (oldVersion <= 11) migrateTo12(realm)
}
private fun migrateTo1(realm: DynamicRealm) {
@@ -136,10 +146,6 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
Timber.d("Step 7 -> 8")
val editionOfEventSchema = realm.schema.create("EditionOfEvent")
- .apply {
- // setEmbedded does not return `this`...
- isEmbedded = true
- }
.addField(EditionOfEventFields.CONTENT, String::class.java)
.addField(EditionOfEventFields.EVENT_ID, String::class.java)
.setRequired(EditionOfEventFields.EVENT_ID, true)
@@ -154,9 +160,13 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
?.removeField("lastEditTs")
?.removeField("sourceLocalEchoEvents")
?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema)
+
+ // This has to be done once a parent use the model as a child
+ // See https://github.com/realm/realm-java/issues/7402
+ editionOfEventSchema.isEmbedded = true
}
- fun migrateTo9(realm: DynamicRealm) {
+ private fun migrateTo9(realm: DynamicRealm) {
Timber.d("Step 8 -> 9")
realm.schema.get("RoomSummaryEntity")
@@ -174,7 +184,6 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE)
?.transform { obj ->
-
val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_FAVOURITE
}
@@ -194,4 +203,75 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
}
}
}
+
+ private fun migrateTo10(realm: DynamicRealm) {
+ Timber.d("Step 9 -> 10")
+ realm.schema.create("SpaceChildSummaryEntity")
+ ?.addField(SpaceChildSummaryEntityFields.ORDER, String::class.java)
+ ?.addField(SpaceChildSummaryEntityFields.CHILD_ROOM_ID, String::class.java)
+ ?.addField(SpaceChildSummaryEntityFields.AUTO_JOIN, Boolean::class.java)
+ ?.setNullable(SpaceChildSummaryEntityFields.AUTO_JOIN, true)
+ ?.addRealmObjectField(SpaceChildSummaryEntityFields.CHILD_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
+ ?.addRealmListField(SpaceChildSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
+
+ realm.schema.create("SpaceParentSummaryEntity")
+ ?.addField(SpaceParentSummaryEntityFields.PARENT_ROOM_ID, String::class.java)
+ ?.addField(SpaceParentSummaryEntityFields.CANONICAL, Boolean::class.java)
+ ?.setNullable(SpaceParentSummaryEntityFields.CANONICAL, true)
+ ?.addRealmObjectField(SpaceParentSummaryEntityFields.PARENT_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
+ ?.addRealmListField(SpaceParentSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
+
+ val creationContentAdapter = MoshiProvider.providesMoshi().adapter(RoomCreateContent::class.java)
+ realm.schema.get("RoomSummaryEntity")
+ ?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java)
+ ?.addField(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, String::class.java)
+ ?.addField(RoomSummaryEntityFields.GROUP_IDS, String::class.java)
+ ?.transform { obj ->
+
+ val creationEvent = realm.where("CurrentStateEventEntity")
+ .equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
+ .equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_CREATE)
+ .findFirst()
+
+ val roomType = creationEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
+ ?.getString(EventEntityFields.CONTENT)?.let {
+ creationContentAdapter.fromJson(it)?.type
+ }
+
+ obj.setString(RoomSummaryEntityFields.ROOM_TYPE, roomType)
+ }
+ ?.addRealmListField(RoomSummaryEntityFields.PARENTS.`$`, realm.schema.get("SpaceParentSummaryEntity")!!)
+ ?.addRealmListField(RoomSummaryEntityFields.CHILDREN.`$`, realm.schema.get("SpaceChildSummaryEntity")!!)
+ }
+
+ private fun migrateTo11(realm: DynamicRealm) {
+ Timber.d("Step 10 -> 11")
+ realm.schema.get("EventEntity")
+ ?.addField(EventEntityFields.SEND_STATE_DETAILS, String::class.java)
+ }
+
+ private fun migrateTo12(realm: DynamicRealm) {
+ Timber.d("Step 11 -> 12")
+
+ val joinRulesContentAdapter = MoshiProvider.providesMoshi().adapter(RoomJoinRulesContent::class.java)
+ realm.schema.get("RoomSummaryEntity")
+ ?.addField(RoomSummaryEntityFields.JOIN_RULES_STR, String::class.java)
+ ?.transform { obj ->
+ val joinRulesEvent = realm.where("CurrentStateEventEntity")
+ .equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
+ .equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_JOIN_RULES)
+ .findFirst()
+
+ val roomJoinRules = joinRulesEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
+ ?.getString(EventEntityFields.CONTENT)?.let {
+ joinRulesContentAdapter.fromJson(it)?.joinRules
+ }
+
+ obj.setString(RoomSummaryEntityFields.JOIN_RULES_STR, roomJoinRules?.name)
+ }
+
+ realm.schema.get("SpaceChildSummaryEntity")
+ ?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
+ ?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
index a4a2fadd21..613b38e340 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
@@ -80,6 +80,7 @@ internal object EventMapper {
).also {
it.ageLocalTs = eventEntity.ageLocalTs
it.sendState = eventEntity.sendState
+ it.sendStateDetails = eventEntity.sendStateDetails
eventEntity.decryptionResultJson?.let { json ->
try {
it.mxDecryptionResult = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).fromJson(json)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
index 6dc70b60fc..fbecbf37be 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
@@ -17,6 +17,8 @@
package org.matrix.android.sdk.internal.database.mapper
import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
+import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker
@@ -42,7 +44,9 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
name = roomSummaryEntity.name ?: "",
topic = roomSummaryEntity.topic ?: "",
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
+ joinRules = roomSummaryEntity.joinRules,
isDirect = roomSummaryEntity.isDirect,
+ directUserId = roomSummaryEntity.directUserId,
latestPreviewableEvent = latestEvent,
joinedMembersCount = roomSummaryEntity.joinedMembersCount,
invitedMembersCount = roomSummaryEntity.invitedMembersCount,
@@ -63,7 +67,33 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel,
inviterId = roomSummaryEntity.inviterId,
- hasFailedSending = roomSummaryEntity.hasFailedSending
+ hasFailedSending = roomSummaryEntity.hasFailedSending,
+ roomType = roomSummaryEntity.roomType,
+ spaceParents = roomSummaryEntity.parents.map { relationInfoEntity ->
+ SpaceParentInfo(
+ parentId = relationInfoEntity.parentRoomId,
+ roomSummary = relationInfoEntity.parentSummaryEntity?.let { map(it) },
+ canonical = relationInfoEntity.canonical ?: false,
+ viaServers = relationInfoEntity.viaServers.toList()
+ )
+ },
+ spaceChildren = roomSummaryEntity.children.map {
+ SpaceChildInfo(
+ childRoomId = it.childRoomId ?: "",
+ isKnown = it.childSummaryEntity != null,
+ roomType = it.childSummaryEntity?.roomType,
+ name = it.childSummaryEntity?.name,
+ topic = it.childSummaryEntity?.topic,
+ avatarUrl = it.childSummaryEntity?.avatarUrl,
+ activeMemberCount = it.childSummaryEntity?.joinedMembersCount,
+ order = it.order,
+ autoJoin = it.autoJoin ?: false,
+ viaServers = it.viaServers.toList(),
+ parentRoomId = roomSummaryEntity.roomId,
+ suggested = it.suggested
+ )
+ },
+ flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList()
)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
index fe59f4fceb..c9edbcd889 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
@@ -32,6 +32,8 @@ internal open class EventEntity(@Index var eventId: String = "",
@Index var stateKey: String? = null,
var originServerTs: Long? = null,
@Index var sender: String? = null,
+ // Can contain a serialized MatrixError
+ var sendStateDetails: String? = null,
var age: Long? = 0,
var unsignedData: String? = null,
var redacts: String? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
index 3ff2532604..58297776f0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
@@ -43,6 +43,5 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "",
set(value) {
membersLoadStatusStr = value.name
}
-
companion object
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
index c87ac15a78..1001f9cd66 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
@@ -21,13 +21,18 @@ import io.realm.RealmObject
import io.realm.annotations.Index
import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
+import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.VersioningState
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
internal open class RoomSummaryEntity(
- @PrimaryKey var roomId: String = ""
+ @PrimaryKey var roomId: String = "",
+ var roomType: String? = null,
+ var parents: RealmList = RealmList(),
+ var children: RealmList = RealmList()
) : RealmObject() {
var displayName: String? = ""
@@ -204,6 +209,16 @@ internal open class RoomSummaryEntity(
if (value != field) field = value
}
+ var flattenParentIds: String? = null
+ set(value) {
+ if (value != field) field = value
+ }
+
+ var groupIds: String? = null
+ set(value) {
+ if (value != field) field = value
+ }
+
@Index
private var membershipStr: String = Membership.NONE.name
@@ -229,6 +244,19 @@ internal open class RoomSummaryEntity(
}
}
+ private var joinRulesStr: String? = null
+ var joinRules: RoomJoinRules?
+ get() {
+ return joinRulesStr?.let {
+ tryOrNull { RoomJoinRules.valueOf(it) }
+ }
+ }
+ set(value) {
+ if (value?.name != joinRulesStr) {
+ joinRulesStr = value?.name
+ }
+ }
+
var roomEncryptionTrustLevel: RoomEncryptionTrustLevel?
get() {
return roomEncryptionTrustLevelStr?.let {
@@ -244,6 +272,5 @@ internal open class RoomSummaryEntity(
roomEncryptionTrustLevelStr = value?.name
}
}
-
companion object
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
index 6e6096cf8a..72ae512fa5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
@@ -61,6 +61,8 @@ import io.realm.annotations.RealmModule
CurrentStateEventEntity::class,
UserAccountDataEntity::class,
ScalarTokenEntity::class,
- WellknownIntegrationManagerConfigEntity::class
+ WellknownIntegrationManagerConfigEntity::class,
+ SpaceChildSummaryEntity::class,
+ SpaceParentSummaryEntity::class
])
internal class SessionRealmModule
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildSummaryEntity.kt
new file mode 100644
index 0000000000..ce1afbb507
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildSummaryEntity.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.model
+
+import io.realm.RealmList
+import io.realm.RealmObject
+
+/**
+ * Decorates room summary with space related information.
+ */
+internal open class SpaceChildSummaryEntity(
+// var isSpace: Boolean = false,
+
+ var order: String? = null,
+
+ var autoJoin: Boolean? = null,
+
+ var suggested: Boolean? = null,
+
+ var childRoomId: String? = null,
+ // Link to the actual space summary if it is known locally
+ var childSummaryEntity: RoomSummaryEntity? = null,
+
+ var viaServers: RealmList = RealmList()
+// var owner: RoomSummaryEntity? = null,
+
+// var level: Int = 0
+
+) : RealmObject() {
+
+ companion object
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt
new file mode 100644
index 0000000000..30517717f4
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.model
+
+import io.realm.RealmList
+import io.realm.RealmObject
+
+/**
+ * Decorates room summary with space related information.
+ */
+internal open class SpaceParentSummaryEntity(
+ /**
+ * Determines whether this is the main parent for the space
+ * When a user joins a room with a canonical parent, clients may switch to view the room in the context of that space,
+ * peeking into it in order to find other rooms and group them together.
+ * In practice, well behaved rooms should only have one canonical parent, but given this is not enforced:
+ * if multiple are present the client should select the one with the lowest room ID,
+ * as determined via a lexicographic utf-8 ordering.
+ */
+ var canonical: Boolean? = null,
+
+ var parentRoomId: String? = null,
+ // Link to the actual space summary if it is known locally
+ var parentSummaryEntity: RoomSummaryEntity? = null,
+
+ var viaServers: RealmList = RealmList()
+
+) : RealmObject() {
+
+ companion object
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt
index 0246bae024..e045cebd3e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt
@@ -88,8 +88,8 @@ internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErr
throw when (exception) {
is IOException -> Failure.NetworkConnection(exception)
is Failure.ServerError,
- is Failure.OtherServerError -> exception
- is CancellationException -> Failure.Cancelled(exception)
+ is Failure.OtherServerError,
+ is CancellationException -> exception
else -> Failure.Unknown(exception)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt
index 7132b4ff7a..2116063626 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt
@@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.internal.di.MoshiProvider
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.ResponseBody
+import org.matrix.android.sdk.api.extensions.orFalse
import retrofit2.HttpException
import retrofit2.Response
import timber.log.Timber
@@ -91,7 +92,7 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int, globalErrorReceiv
} else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED /* 401 */
&& matrixError.code == MatrixError.M_UNKNOWN_TOKEN) {
// Also send this error to the globalErrorReceiver, for a global management
- globalErrorReceiver?.handleGlobalError(GlobalError.InvalidToken(matrixError.isSoftLogout))
+ globalErrorReceiver?.handleGlobalError(GlobalError.InvalidToken(matrixError.isSoftLogout.orFalse()))
}
return Failure.ServerError(matrixError, httpCode)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt
new file mode 100644
index 0000000000..7a06c2129c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.query
+
+import io.realm.RealmQuery
+import io.realm.Sort
+import org.matrix.android.sdk.api.session.room.RoomSortOrder
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+
+internal fun RealmQuery.process(sortOrder: RoomSortOrder): RealmQuery {
+ when (sortOrder) {
+ RoomSortOrder.NAME -> {
+ sort(RoomSummaryEntityFields.DISPLAY_NAME, Sort.ASCENDING)
+ }
+ RoomSortOrder.ACTIVITY -> {
+ sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
+ }
+ RoomSortOrder.NONE -> {
+ }
+ }
+ return this
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt
index 899024458a..fd33682231 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt
@@ -16,10 +16,10 @@
package org.matrix.android.sdk.internal.query
-import org.matrix.android.sdk.api.query.QueryStringValue
import io.realm.Case
import io.realm.RealmObject
import io.realm.RealmQuery
+import org.matrix.android.sdk.api.query.QueryStringValue
import timber.log.Timber
fun RealmQuery.process(field: String, queryStringValue: QueryStringValue): RealmQuery {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
index d05ee48c1b..891858d857 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
@@ -219,7 +219,7 @@ internal class DefaultFileService @Inject constructor(
fileName: String,
mimeType: String?,
elementToDecrypt: ElementToDecrypt?): Boolean {
- return fileState(mxcUrl, fileName, mimeType, elementToDecrypt) == FileService.FileState.IN_CACHE
+ return fileState(mxcUrl, fileName, mimeType, elementToDecrypt) is FileService.FileState.InCache
}
internal data class CachedFiles(
@@ -256,12 +256,17 @@ internal class DefaultFileService @Inject constructor(
fileName: String,
mimeType: String?,
elementToDecrypt: ElementToDecrypt?): FileService.FileState {
- mxcUrl ?: return FileService.FileState.UNKNOWN
- if (getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null).file.exists()) return FileService.FileState.IN_CACHE
+ mxcUrl ?: return FileService.FileState.Unknown
+ val files = getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null)
+ if (files.file.exists()) {
+ return FileService.FileState.InCache(
+ decryptedFileInCache = files.getClearFile().exists()
+ )
+ }
val isDownloading = synchronized(ongoing) {
ongoing[mxcUrl] != null
}
- return if (isDownloading) FileService.FileState.DOWNLOADING else FileService.FileState.UNKNOWN
+ return if (isDownloading) FileService.FileState.Downloading else FileService.FileState.Unknown
}
/**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
index 821a9cba8c..53e13c14ec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
@@ -19,13 +19,15 @@ package org.matrix.android.sdk.internal.session
import androidx.annotation.MainThread
import dagger.Lazy
import io.realm.RealmConfiguration
+import kotlinx.coroutines.NonCancellable
+import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.failure.GlobalError
import org.matrix.android.sdk.api.federation.FederationService
import org.matrix.android.sdk.api.pushrules.PushRuleService
-import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.api.session.account.AccountService
import org.matrix.android.sdk.api.session.accountdata.AccountDataService
import org.matrix.android.sdk.api.session.cache.CacheService
@@ -38,6 +40,7 @@ import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.group.GroupService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
+import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.session.media.MediaService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
@@ -49,6 +52,7 @@ import org.matrix.android.sdk.api.session.search.SearchService
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
import org.matrix.android.sdk.api.session.signout.SignOutService
+import org.matrix.android.sdk.api.session.space.SpaceService
import org.matrix.android.sdk.api.session.sync.FilterService
import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService
@@ -120,6 +124,7 @@ internal class DefaultSession @Inject constructor(
private val integrationManagerService: IntegrationManagerService,
private val thirdPartyService: Lazy,
private val callSignalingService: Lazy,
+ private val spaceService: Lazy,
@UnauthenticatedWithCertificate
private val unauthenticatedWithCertificateOkHttpClient: Lazy
) : Session,
@@ -159,7 +164,12 @@ internal class DefaultSession @Inject constructor(
isOpen = true
cryptoService.get().ensureDevice()
uiHandler.post {
- lifecycleObservers.forEach { it.onSessionStarted() }
+ lifecycleObservers.forEach {
+ it.onSessionStarted(this)
+ }
+ sessionListeners.dispatch {
+ it.onSessionStarted(this)
+ }
}
globalErrorHandler.listener = this
}
@@ -200,7 +210,10 @@ internal class DefaultSession @Inject constructor(
stopSync()
// timelineEventDecryptor.destroy()
uiHandler.post {
- lifecycleObservers.forEach { it.onSessionStopped() }
+ lifecycleObservers.forEach { it.onSessionStopped(this) }
+ sessionListeners.dispatch {
+ it.onSessionStopped(this)
+ }
}
cryptoService.get().close()
isOpen = false
@@ -225,14 +238,23 @@ internal class DefaultSession @Inject constructor(
stopSync()
stopAnyBackgroundSync()
uiHandler.post {
- lifecycleObservers.forEach { it.onClearCache() }
+ lifecycleObservers.forEach {
+ it.onClearCache(this)
+ }
+ sessionListeners.dispatch {
+ it.onClearCache(this)
+ }
+ }
+ withContext(NonCancellable) {
+ cacheService.get().clearCache()
}
- cacheService.get().clearCache()
workManagerProvider.cancelAllWorks()
}
override fun onGlobalError(globalError: GlobalError) {
- sessionListeners.dispatchGlobalError(globalError)
+ sessionListeners.dispatch {
+ it.onGlobalError(this, globalError)
+ }
}
override fun contentUrlResolver() = contentUrlResolver
@@ -265,6 +287,8 @@ internal class DefaultSession @Inject constructor(
override fun thirdPartyService(): ThirdPartyService = thirdPartyService.get()
+ override fun spaceService(): SpaceService = spaceService.get()
+
override fun getOkHttpClient(): OkHttpClient {
return unauthenticatedWithCertificateOkHttpClient.get()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
index 7e1e3d0f70..541c877b1d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
@@ -52,6 +52,7 @@ import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
import org.matrix.android.sdk.internal.session.search.SearchModule
import org.matrix.android.sdk.internal.session.signout.SignOutModule
+import org.matrix.android.sdk.internal.session.space.SpaceModule
import org.matrix.android.sdk.internal.session.sync.SyncModule
import org.matrix.android.sdk.internal.session.sync.SyncTask
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
@@ -91,7 +92,8 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
FederationModule::class,
CallModule::class,
SearchModule::class,
- ThirdPartyModule::class
+ ThirdPartyModule::class,
+ SpaceModule::class
]
)
@SessionScope
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionCoroutineScopeHolder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionCoroutineScopeHolder.kt
new file mode 100644
index 0000000000..82a8f79fd5
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionCoroutineScopeHolder.kt
@@ -0,0 +1,43 @@
+/*
+ * 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
+
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancelChildren
+import javax.inject.Inject
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
+
+@SessionScope
+internal class SessionCoroutineScopeHolder @Inject constructor(): SessionLifecycleObserver {
+
+ val scope: CoroutineScope = CoroutineScope(SupervisorJob())
+
+ override fun onSessionStopped(session: Session) {
+ scope.cancelChildren()
+ }
+
+ override fun onClearCache(session: Session) {
+ scope.cancelChildren()
+ }
+
+ private fun CoroutineScope.cancelChildren() {
+ coroutineContext.cancelChildren(CancellationException("Closing session"))
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt
index 64f2d249f3..563ff4ada3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt
@@ -16,7 +16,6 @@
package org.matrix.android.sdk.internal.session
-import org.matrix.android.sdk.api.failure.GlobalError
import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject
@@ -36,10 +35,10 @@ internal class SessionListeners @Inject constructor() {
}
}
- fun dispatchGlobalError(globalError: GlobalError) {
+ fun dispatch(block: (Session.Listener) -> Unit) {
synchronized(listeners) {
listeners.forEach {
- it.onGlobalError(globalError)
+ block(it)
}
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
index e61e4ecd89..63423b72c6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.auth.data.sessionId
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.api.session.accountdata.AccountDataService
import org.matrix.android.sdk.api.session.events.EventService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
@@ -343,6 +344,10 @@ internal abstract class SessionModule {
@IntoSet
abstract fun bindRealmSessionProvider(provider: RealmSessionProvider): SessionLifecycleObserver
+ @Binds
+ @IntoSet
+ abstract fun bindSessionCoroutineScopeHolder(holder: SessionCoroutineScopeHolder): SessionLifecycleObserver
+
@Binds
@IntoSet
abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUploadStateTracker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUploadStateTracker.kt
index 754f12bd68..17e0a930c1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUploadStateTracker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUploadStateTracker.kt
@@ -78,6 +78,16 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
updateState(key, progressData)
}
+ internal fun setCompressingImage(key: String) {
+ val progressData = ContentUploadStateTracker.State.CompressingImage
+ updateState(key, progressData)
+ }
+
+ internal fun setCompressingVideo(key: String, percent: Float) {
+ val progressData = ContentUploadStateTracker.State.CompressingVideo(percent)
+ updateState(key, progressData)
+ }
+
internal fun setProgress(key: String, current: Long, total: Long) {
val progressData = ContentUploadStateTracker.State.Uploading(current, total)
updateState(key, progressData)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
index 8fa595db30..6bb43d599c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
@@ -31,22 +31,28 @@ import okhttp3.RequestBody.Companion.toRequestBody
import okio.BufferedSink
import okio.source
import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
+import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.internal.di.Authenticated
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.ProgressRequestBody
import org.matrix.android.sdk.internal.network.awaitResponse
import org.matrix.android.sdk.internal.network.toFailure
+import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService
+import org.matrix.android.sdk.internal.util.TemporaryFileCreator
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
-import java.util.UUID
import javax.inject.Inject
internal class FileUploader @Inject constructor(@Authenticated
private val okHttpClient: OkHttpClient,
private val globalErrorReceiver: GlobalErrorReceiver,
+ private val homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService,
private val context: Context,
+ private val temporaryFileCreator: TemporaryFileCreator,
contentUrlResolver: ContentUrlResolver,
moshi: Moshi) {
@@ -57,6 +63,21 @@ internal class FileUploader @Inject constructor(@Authenticated
filename: String?,
mimeType: String?,
progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse {
+ // Check size limit
+ val maxUploadFileSize = homeServerCapabilitiesService.getHomeServerCapabilities().maxUploadFileSize
+
+ if (maxUploadFileSize != HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN
+ && file.length() > maxUploadFileSize) {
+ // Known limitation and file too big for the server, save the pain to upload it
+ throw Failure.ServerError(
+ error = MatrixError(
+ code = MatrixError.M_TOO_LARGE,
+ message = "Cannot upload files larger than ${maxUploadFileSize / 1048576L}mb"
+ ),
+ httpCode = 413
+ )
+ }
+
val uploadBody = object : RequestBody() {
override fun contentLength() = file.length()
@@ -90,7 +111,7 @@ internal class FileUploader @Inject constructor(@Authenticated
val inputStream = withContext(Dispatchers.IO) {
context.contentResolver.openInputStream(uri)
} ?: throw FileNotFoundException()
- val workingFile = File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
+ val workingFile = temporaryFileCreator.create()
workingFile.outputStream().use {
inputStream.copyTo(it)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
index 1d6cd61060..9b01d0a00e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
@@ -16,19 +16,20 @@
package org.matrix.android.sdk.internal.session.content
-import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import androidx.exifinterface.media.ExifInterface
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.internal.util.TemporaryFileCreator
import timber.log.Timber
import java.io.File
-import java.util.UUID
import javax.inject.Inject
-internal class ImageCompressor @Inject constructor(private val context: Context) {
+internal class ImageCompressor @Inject constructor(
+ private val temporaryFileCreator: TemporaryFileCreator
+) {
suspend fun compress(
imageFile: File,
desiredWidth: Int,
@@ -45,7 +46,7 @@ internal class ImageCompressor @Inject constructor(private val context: Context)
}
} ?: return@withContext imageFile
- val destinationFile = createDestinationFile()
+ val destinationFile = temporaryFileCreator.create()
runCatching {
destinationFile.outputStream().use {
@@ -53,7 +54,7 @@ internal class ImageCompressor @Inject constructor(private val context: Context)
}
}
- return@withContext destinationFile
+ destinationFile
}
}
@@ -64,16 +65,16 @@ internal class ImageCompressor @Inject constructor(private val context: Context)
val orientation = exifInfo.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
val matrix = Matrix()
when (orientation) {
- ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
- ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
- ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
+ ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
+ ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
+ ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.preScale(-1f, 1f)
- ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.preScale(1f, -1f)
- ExifInterface.ORIENTATION_TRANSPOSE -> {
+ ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.preScale(1f, -1f)
+ ExifInterface.ORIENTATION_TRANSPOSE -> {
matrix.preRotate(-90f)
matrix.preScale(-1f, 1f)
}
- ExifInterface.ORIENTATION_TRANSVERSE -> {
+ ExifInterface.ORIENTATION_TRANSVERSE -> {
matrix.preRotate(90f)
matrix.preScale(-1f, 1f)
}
@@ -116,8 +117,4 @@ internal class ImageCompressor @Inject constructor(private val context: Context)
null
}
}
-
- private fun createDestinationFile(): File {
- return File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
- }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
index 3b727690bf..d5c3deeec6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
@@ -18,9 +18,12 @@ package org.matrix.android.sdk.internal.session.content
import android.content.Context
import android.graphics.BitmapFactory
+import android.media.MediaMetadataRetriever
+import androidx.core.net.toUri
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
@@ -41,12 +44,13 @@ import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
import org.matrix.android.sdk.internal.session.room.send.LocalEchoIdentifiers
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
+import org.matrix.android.sdk.internal.util.TemporaryFileCreator
+import org.matrix.android.sdk.internal.util.toMatrixErrorStr
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import timber.log.Timber
import java.io.File
-import java.util.UUID
import javax.inject.Inject
private data class NewAttachmentAttributes(
@@ -77,7 +81,9 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
@Inject lateinit var fileService: DefaultFileService
@Inject lateinit var cancelSendTracker: CancelSendTracker
@Inject lateinit var imageCompressor: ImageCompressor
+ @Inject lateinit var videoCompressor: VideoCompressor
@Inject lateinit var localEchoRepository: LocalEchoRepository
+ @Inject lateinit var temporaryFileCreator: TemporaryFileCreator
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
@@ -109,7 +115,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
val attachment = params.attachment
val filesToDelete = mutableListOf()
- try {
+ return try {
val inputStream = context.contentResolver.openInputStream(attachment.queryUri)
?: return Result.success(
WorkerParamsFactory.toData(
@@ -120,7 +126,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
)
// always use a temporary file, it guaranties that we could report progress on upload and simplifies the flows
- val workingFile = File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
+ val workingFile = temporaryFileCreator.create()
.also { filesToDelete.add(it) }
workingFile.outputStream().use { outputStream ->
inputStream.use { inputStream ->
@@ -128,8 +134,6 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
}
}
- val uploadThumbnailResult = dealWithThumbnail(params)
-
val progressListener = object : ProgressRequestBody.Listener {
override fun onProgress(current: Long, total: Long) {
notifyTracker(params) {
@@ -144,7 +148,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null
- return try {
+ try {
val fileToUpload: File
var newAttachmentAttributes = NewAttachmentAttributes(
params.attachment.width?.toInt(),
@@ -156,6 +160,8 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
// Do not compress gif
&& attachment.mimeType != MimeTypes.Gif
&& params.compressBeforeSending) {
+ notifyTracker(params) { contentUploadStateTracker.setCompressingImage(it) }
+
fileToUpload = imageCompressor.compress(workingFile, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE)
.also { compressedFile ->
// Get new Bitmap size
@@ -170,6 +176,48 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
}
}
.also { filesToDelete.add(it) }
+ } else if (attachment.type == ContentAttachmentData.Type.VIDEO
+ // Do not compress gif
+ && attachment.mimeType != MimeTypes.Gif
+ && params.compressBeforeSending) {
+ fileToUpload = videoCompressor.compress(workingFile, object : ProgressListener {
+ override fun onProgress(progress: Int, total: Int) {
+ notifyTracker(params) { contentUploadStateTracker.setCompressingVideo(it, progress.toFloat()) }
+ }
+ })
+ .let { videoCompressionResult ->
+ when (videoCompressionResult) {
+ is VideoCompressionResult.Success -> {
+ val compressedFile = videoCompressionResult.compressedFile
+ var compressedWidth: Int? = null
+ var compressedHeight: Int? = null
+
+ tryOrNull {
+ context.contentResolver.openFileDescriptor(compressedFile.toUri(), "r")?.use { pfd ->
+ MediaMetadataRetriever().let {
+ it.setDataSource(pfd.fileDescriptor)
+ compressedWidth = it.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toInt()
+ compressedHeight = it.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toInt()
+ }
+ }
+ }
+
+ // Get new Video file size and dimensions
+ newAttachmentAttributes = newAttachmentAttributes.copy(
+ newFileSize = compressedFile.length(),
+ newWidth = compressedWidth ?: newAttachmentAttributes.newWidth,
+ newHeight = compressedHeight ?: newAttachmentAttributes.newHeight
+ )
+ compressedFile
+ .also { filesToDelete.add(it) }
+ }
+ VideoCompressionResult.CompressionNotNeeded,
+ VideoCompressionResult.CompressionCancelled,
+ is VideoCompressionResult.CompressionFailed -> {
+ workingFile
+ }
+ }
+ }
} else {
fileToUpload = workingFile
// Fix: OpenableColumns.SIZE may return -1 or 0
@@ -180,9 +228,9 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
val encryptedFile: File?
val contentUploadResponse = if (params.isEncrypted) {
- Timber.v("## FileService: Encrypt file")
+ Timber.v("## Encrypt file")
- encryptedFile = File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
+ encryptedFile = temporaryFileCreator.create()
.also { filesToDelete.add(it) }
uploadedFileEncryptedFileInfo =
@@ -192,18 +240,18 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
}
}
- Timber.v("## FileService: Uploading file")
+ Timber.v("## Uploading file")
fileUploader
.uploadFile(encryptedFile, attachment.name, MimeTypes.OctetStream, progressListener)
} else {
- Timber.v("## FileService: Clear file")
+ Timber.v("## Clear file")
encryptedFile = null
fileUploader
.uploadFile(fileToUpload, attachment.name, attachment.getSafeMimeType(), progressListener)
}
- Timber.v("## FileService: Update cache storage for ${contentUploadResponse.contentUri}")
+ Timber.v("## Update cache storage for ${contentUploadResponse.contentUri}")
try {
fileService.storeDataFor(
mxcUrl = contentUploadResponse.contentUri,
@@ -212,11 +260,13 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
originalFile = workingFile,
encryptedFile = encryptedFile
)
- Timber.v("## FileService: cache storage updated")
+ Timber.v("## cache storage updated")
} catch (failure: Throwable) {
- Timber.e(failure, "## FileService: Failed to update file cache")
+ Timber.e(failure, "## Failed to update file cache")
}
+ val uploadThumbnailResult = dealWithThumbnail(params)
+
handleSuccess(params,
contentUploadResponse.contentUri,
uploadedFileEncryptedFileInfo,
@@ -224,12 +274,12 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
uploadThumbnailResult?.uploadedThumbnailEncryptedFileInfo,
newAttachmentAttributes)
} catch (t: Throwable) {
- Timber.e(t, "## FileService: ERROR ${t.localizedMessage}")
+ Timber.e(t, "## ERROR ${t.localizedMessage}")
handleFailure(params, t)
}
} catch (e: Exception) {
- Timber.e(e, "## FileService: ERROR")
- return handleFailure(params, e)
+ Timber.e(e, "## ERROR")
+ handleFailure(params, e)
} finally {
// Delete all temporary files
filesToDelete.forEach {
@@ -260,19 +310,23 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
Timber.v("Encrypt thumbnail")
notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType)
- val contentUploadResponse = fileUploader.uploadByteArray(encryptionResult.encryptedByteArray,
- "thumb_${params.attachment.name}",
- MimeTypes.OctetStream,
- thumbnailProgressListener)
+ val contentUploadResponse = fileUploader.uploadByteArray(
+ byteArray = encryptionResult.encryptedByteArray,
+ filename = "thumb_${params.attachment.name}",
+ mimeType = MimeTypes.OctetStream,
+ progressListener = thumbnailProgressListener
+ )
UploadThumbnailResult(
contentUploadResponse.contentUri,
encryptionResult.encryptedFileInfo
)
} else {
- val contentUploadResponse = fileUploader.uploadByteArray(thumbnailData.bytes,
- "thumb_${params.attachment.name}",
- thumbnailData.mimeType,
- thumbnailProgressListener)
+ val contentUploadResponse = fileUploader.uploadByteArray(
+ byteArray = thumbnailData.bytes,
+ filename = "thumb_${params.attachment.name}",
+ mimeType = thumbnailData.mimeType,
+ progressListener = thumbnailProgressListener
+ )
UploadThumbnailResult(
contentUploadResponse.contentUri,
null
@@ -291,7 +345,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
return Result.success(
WorkerParamsFactory.toData(
params.copy(
- lastFailureMessage = failure.localizedMessage
+ lastFailureMessage = failure.toMatrixErrorStr()
)
)
)
@@ -328,8 +382,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
val messageContent: MessageContent? = event.asDomain().content.toModel()
val updatedContent = when (messageContent) {
is MessageImageContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes)
- is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo,
- newAttachmentAttributes.newFileSize)
+ is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newAttachmentAttributes)
is MessageFileContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes.newFileSize)
is MessageAudioContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes.newFileSize)
else -> messageContent
@@ -351,7 +404,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
info = info?.copy(
width = newAttachmentAttributes?.newWidth ?: info.width,
height = newAttachmentAttributes?.newHeight ?: info.height,
- size = newAttachmentAttributes?.newFileSize?.toInt() ?: info.size
+ size = newAttachmentAttributes?.newFileSize ?: info.size
)
)
}
@@ -360,14 +413,16 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
encryptedFileInfo: EncryptedFileInfo?,
thumbnailUrl: String?,
thumbnailEncryptedFileInfo: EncryptedFileInfo?,
- size: Long): MessageVideoContent {
+ newAttachmentAttributes: NewAttachmentAttributes?): MessageVideoContent {
return copy(
url = if (encryptedFileInfo == null) url else null,
encryptedFileInfo = encryptedFileInfo?.copy(url = url),
videoInfo = videoInfo?.copy(
thumbnailUrl = if (thumbnailEncryptedFileInfo == null) thumbnailUrl else null,
thumbnailFile = thumbnailEncryptedFileInfo?.copy(url = thumbnailUrl),
- size = size
+ width = newAttachmentAttributes?.newWidth ?: videoInfo.width,
+ height = newAttachmentAttributes?.newHeight ?: videoInfo.height,
+ size = newAttachmentAttributes?.newFileSize ?: videoInfo.size
)
)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressionResult.kt
new file mode 100644
index 0000000000..87d5c7e6a3
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressionResult.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.content
+
+import java.io.File
+
+internal sealed class VideoCompressionResult {
+ data class Success(val compressedFile: File) : VideoCompressionResult()
+ object CompressionNotNeeded : VideoCompressionResult()
+ object CompressionCancelled : VideoCompressionResult()
+ data class CompressionFailed(val failure: Throwable) : VideoCompressionResult()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressor.kt
new file mode 100644
index 0000000000..05aaf4e9f1
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressor.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.content
+
+import com.otaliastudios.transcoder.Transcoder
+import com.otaliastudios.transcoder.TranscoderListener
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.listeners.ProgressListener
+import org.matrix.android.sdk.internal.util.TemporaryFileCreator
+import timber.log.Timber
+import java.io.File
+import javax.inject.Inject
+
+internal class VideoCompressor @Inject constructor(
+ private val temporaryFileCreator: TemporaryFileCreator
+) {
+
+ suspend fun compress(videoFile: File,
+ progressListener: ProgressListener?): VideoCompressionResult {
+ val destinationFile = temporaryFileCreator.create()
+
+ val job = Job()
+
+ Timber.d("Compressing: start")
+ progressListener?.onProgress(0, 100)
+
+ var result: Int = -1
+ var failure: Throwable? = null
+ Transcoder.into(destinationFile.path)
+ .addDataSource(videoFile.path)
+ .setListener(object : TranscoderListener {
+ override fun onTranscodeProgress(progress: Double) {
+ Timber.d("Compressing: $progress%")
+ progressListener?.onProgress((progress * 100).toInt(), 100)
+ }
+
+ override fun onTranscodeCompleted(successCode: Int) {
+ Timber.d("Compressing: success: $successCode")
+ result = successCode
+ job.complete()
+ }
+
+ override fun onTranscodeCanceled() {
+ Timber.d("Compressing: cancel")
+ job.cancel()
+ }
+
+ override fun onTranscodeFailed(exception: Throwable) {
+ Timber.w(exception, "Compressing: failure")
+ failure = exception
+ job.completeExceptionally(exception)
+ }
+ })
+ .transcode()
+
+ job.join()
+
+ // Note: job is also cancelled if completeExceptionally() was called
+ if (job.isCancelled) {
+ // Delete now the temporary file
+ deleteFile(destinationFile)
+ return when (val finalFailure = failure) {
+ null -> {
+ // We do not throw a CancellationException, because it's not critical, we will try to send the original file
+ // Anyway this should never occurs, since we never cancel the return value of transcode()
+ Timber.w("Compressing: A failure occurred")
+ VideoCompressionResult.CompressionCancelled
+ }
+ else -> {
+ // Compression failure can also be considered as not critical, but let the caller decide
+ Timber.w("Compressing: Job cancelled")
+ VideoCompressionResult.CompressionFailed(finalFailure)
+ }
+ }
+ }
+
+ progressListener?.onProgress(100, 100)
+
+ return when (result) {
+ Transcoder.SUCCESS_TRANSCODED -> {
+ VideoCompressionResult.Success(destinationFile)
+ }
+ Transcoder.SUCCESS_NOT_NEEDED -> {
+ // Delete now the temporary file
+ deleteFile(destinationFile)
+ VideoCompressionResult.CompressionNotNeeded
+ }
+ else -> {
+ // Should not happen...
+ // Delete now the temporary file
+ deleteFile(destinationFile)
+ Timber.w("Unknown result: $result")
+ VideoCompressionResult.CompressionFailed(IllegalStateException("Unknown result: $result"))
+ }
+ }
+ }
+
+ private suspend fun deleteFile(file: File) {
+ withContext(Dispatchers.IO) {
+ file.delete()
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
index f5391d6cdb..475781ef01 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
@@ -36,7 +36,7 @@ import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
import org.matrix.android.sdk.internal.extensions.observeNotNull
import org.matrix.android.sdk.internal.network.RetrofitFactory
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask
@@ -51,6 +51,7 @@ import org.matrix.android.sdk.internal.util.ensureProtocol
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.session.Session
import timber.log.Timber
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection
@@ -86,7 +87,7 @@ internal class DefaultIdentityService @Inject constructor(
private val listeners = mutableSetOf()
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
lifecycleRegistry.currentState = Lifecycle.State.STARTED
// Observe the account data change
accountDataDataSource
@@ -111,7 +112,7 @@ internal class DefaultIdentityService @Inject constructor(
}
}
- override fun onSessionStopped() {
+ override fun onSessionStopped(session: Session) {
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
index e34615d269..3df9a00cc1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
@@ -21,6 +21,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.MatrixConfiguration
+import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
@@ -29,7 +30,7 @@ import org.matrix.android.sdk.api.session.widgets.model.WidgetType
import org.matrix.android.sdk.internal.database.model.WellknownIntegrationManagerConfigEntity
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.extensions.observeNotNull
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
@@ -77,7 +78,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
currentConfigs.add(defaultConfig)
}
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
lifecycleRegistry.currentState = Lifecycle.State.STARTED
observeWellknownConfig()
accountDataDataSource
@@ -105,7 +106,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
}
}
- override fun onSessionStopped() {
+ override fun onSessionStopped(session: Session) {
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt
index 71a6e224bf..970752449a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt
@@ -18,19 +18,13 @@ package org.matrix.android.sdk.internal.session.permalinks
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE
-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.internal.di.UserId
-import org.matrix.android.sdk.internal.session.room.RoomGetter
-import java.net.URLEncoder
import javax.inject.Inject
-import javax.inject.Provider
internal class PermalinkFactory @Inject constructor(
@UserId
private val userId: String,
- // Use a provider to fix circular Dagger dependency
- private val roomGetterProvider: Provider
+ private val viaParameterFinder: ViaParameterFinder
) {
fun createPermalink(event: Event): String? {
@@ -50,12 +44,12 @@ internal class PermalinkFactory @Inject constructor(
return if (roomId.isEmpty()) {
null
} else {
- MATRIX_TO_URL_BASE + escape(roomId) + computeViaParams(userId, roomId)
+ MATRIX_TO_URL_BASE + escape(roomId) + viaParameterFinder.computeViaParams(userId, roomId)
}
}
fun createPermalink(roomId: String, eventId: String): String {
- return MATRIX_TO_URL_BASE + escape(roomId) + "/" + escape(eventId) + computeViaParams(userId, roomId)
+ return MATRIX_TO_URL_BASE + escape(roomId) + "/" + escape(eventId) + viaParameterFinder.computeViaParams(userId, roomId)
}
fun getLinkedId(url: String): String? {
@@ -66,25 +60,6 @@ internal class PermalinkFactory @Inject constructor(
} else null
}
- /**
- * Compute the via parameters.
- * Take up to 3 homeserver domains, taking the most representative one regarding room members and including the
- * current user one.
- */
- private fun computeViaParams(userId: String, roomId: String): String {
- val userHomeserver = userId.substringAfter(":")
- return getUserIdsOfJoinedMembers(roomId)
- .map { it.substringAfter(":") }
- .groupBy { it }
- .mapValues { it.value.size }
- .toMutableMap()
- // Ensure the user homeserver will be included
- .apply { this[userHomeserver] = Int.MAX_VALUE }
- .let { map -> map.keys.sortedByDescending { map[it] } }
- .take(3)
- .joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") }
- }
-
/**
* Escape '/' in id, because it is used as a separator
*
@@ -104,15 +79,4 @@ internal class PermalinkFactory @Inject constructor(
private fun unescape(id: String): String {
return id.replace("%2F", "/")
}
-
- /**
- * Get a set of userIds of joined members of a room
- */
- private fun getUserIdsOfJoinedMembers(roomId: String): Set {
- return roomGetterProvider.get().getRoom(roomId)
- ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
- ?.map { it.userId }
- .orEmpty()
- .toSet()
- }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
new file mode 100644
index 0000000000..0da60e9ba2
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.permalinks
+
+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.internal.di.UserId
+import org.matrix.android.sdk.internal.session.room.RoomGetter
+import java.net.URLEncoder
+import javax.inject.Inject
+import javax.inject.Provider
+
+internal class ViaParameterFinder @Inject constructor(
+ @UserId private val userId: String,
+ private val roomGetterProvider: Provider
+) {
+
+ fun computeViaParams(roomId: String, max: Int): List {
+ return computeViaParams(userId, roomId, max)
+ }
+
+ /**
+ * Compute the via parameters.
+ * Take up to 3 homeserver domains, taking the most representative one regarding room members and including the
+ * current user one.
+ */
+ fun computeViaParams(userId: String, roomId: String): String {
+ return computeViaParams(userId, roomId, 3)
+ .joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") }
+ }
+
+ fun computeViaParams(userId: String, roomId: String, max: Int): List {
+ val userHomeserver = userId.substringAfter(":")
+ return getUserIdsOfJoinedMembers(roomId)
+ .map { it.substringAfter(":") }
+ .groupBy { it }
+ .mapValues { it.value.size }
+ .toMutableMap()
+ // Ensure the user homeserver will be included
+ .apply { this[userHomeserver] = Int.MAX_VALUE }
+ .let { map -> map.keys.sortedByDescending { map[it] } }
+ .take(max)
+ }
+
+ /**
+ * Get a set of userIds of joined members of a room
+ */
+ private fun getUserIdsOfJoinedMembers(roomId: String): Set {
+ return roomGetterProvider.get().getRoom(roomId)
+ ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
+ ?.map { it.userId }
+ .orEmpty()
+ .toSet()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt
index 5113b821e8..485a4973ca 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt
@@ -33,6 +33,7 @@ internal interface ProfileAPI {
* Get the combined profile information for this user.
* This API may be used to fetch the user's own profile information or other users; either locally or on remote homeservers.
* This API may return keys which are not limited to displayname or avatar_url.
+ * If server is configured as limit_profile_requests_to_users_who_share_rooms: true then response can be HTTP 403.
* @param userId the user id to fetch profile info
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
index 1d8eb6c95e..a5e066dae8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.alias.AliasService
import org.matrix.android.sdk.api.session.room.call.RoomCallService
import org.matrix.android.sdk.api.session.room.members.MembershipService
import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
import org.matrix.android.sdk.api.session.room.read.ReadService
@@ -36,11 +37,14 @@ 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.uploads.UploadsService
import org.matrix.android.sdk.api.session.search.SearchResult
+import org.matrix.android.sdk.api.session.space.Space
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
import org.matrix.android.sdk.internal.session.search.SearchTask
+import org.matrix.android.sdk.internal.session.space.DefaultSpace
import org.matrix.android.sdk.internal.util.awaitCallback
import java.security.InvalidParameterException
import javax.inject.Inject
@@ -63,6 +67,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
private val roomMembersService: MembershipService,
private val roomPushRuleService: RoomPushRuleService,
private val sendStateTask: SendStateTask,
+ private val viaParameterFinder: ViaParameterFinder,
private val searchTask: SearchTask) :
Room,
TimelineService by timelineService,
@@ -148,4 +153,9 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
)
)
}
+
+ override fun asSpace(): Space? {
+ if (roomSummary()?.roomType != RoomType.SPACE) return null
+ return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder)
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
index bd63ba480e..d9fe1288e2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
@@ -20,19 +20,19 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import androidx.paging.PagedList
import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.RoomService
+import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
-import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult
+import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
+import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
-import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.mapper.asDomain
@@ -50,8 +50,6 @@ import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
-import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.util.fetchCopied
import javax.inject.Inject
@@ -67,16 +65,11 @@ internal class DefaultRoomService @Inject constructor(
private val peekRoomTask: PeekRoomTask,
private val roomGetter: RoomGetter,
private val roomSummaryDataSource: RoomSummaryDataSource,
- private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
- private val taskExecutor: TaskExecutor
+ private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource
) : RoomService {
- override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback): Cancelable {
- return createRoomTask
- .configureWith(createRoomParams) {
- this.callback = callback
- }
- .executeBy(taskExecutor)
+ override suspend fun createRoom(createRoomParams: CreateRoomParams): String {
+ return createRoomTask.executeRetry(createRoomParams, 3)
}
override fun getRoom(roomId: String): Room? {
@@ -99,14 +92,14 @@ internal class DefaultRoomService @Inject constructor(
return roomSummaryDataSource.getRoomSummariesLive(queryParams)
}
- override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
+ override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, sortOrder: RoomSortOrder)
: LiveData> {
- return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig)
+ return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
}
- override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
- : UpdatableFilterLivePageResult {
- return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams, pagedListConfig)
+ override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, sortOrder: RoomSortOrder)
+ : UpdatableLivePageResult {
+ return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
}
override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
@@ -121,34 +114,20 @@ internal class DefaultRoomService @Inject constructor(
return roomSummaryDataSource.getBreadcrumbsLive(queryParams)
}
- override fun onRoomDisplayed(roomId: String): Cancelable {
- return updateBreadcrumbsTask
- .configureWith(UpdateBreadcrumbsTask.Params(roomId))
- .executeBy(taskExecutor)
+ override suspend fun onRoomDisplayed(roomId: String) {
+ updateBreadcrumbsTask.execute(UpdateBreadcrumbsTask.Params(roomId))
}
- override fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List, callback: MatrixCallback): Cancelable {
- return joinRoomTask
- .configureWith(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers)) {
- this.callback = callback
- }
- .executeBy(taskExecutor)
+ override suspend fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List) {
+ joinRoomTask.execute(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers))
}
- override fun markAllAsRead(roomIds: List, callback: MatrixCallback): Cancelable {
- return markAllRoomsReadTask
- .configureWith(MarkAllRoomsReadTask.Params(roomIds)) {
- this.callback = callback
- }
- .executeBy(taskExecutor)
+ override suspend fun markAllAsRead(roomIds: List) {
+ markAllRoomsReadTask.execute(MarkAllRoomsReadTask.Params(roomIds))
}
- override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback>): Cancelable {
- return roomIdByAliasTask
- .configureWith(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer)) {
- this.callback = callback
- }
- .executeBy(taskExecutor)
+ override suspend fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean): Optional {
+ return roomIdByAliasTask.execute(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer))
}
override suspend fun deleteRoomAlias(roomAlias: String) {
@@ -179,19 +158,25 @@ internal class DefaultRoomService @Inject constructor(
}
}
- override fun getRoomState(roomId: String, callback: MatrixCallback>) {
- resolveRoomStateTask
- .configureWith(ResolveRoomStateTask.Params(roomId)) {
- this.callback = callback
- }
- .executeBy(taskExecutor)
+ override suspend fun getRoomState(roomId: String): List {
+ return resolveRoomStateTask.execute(ResolveRoomStateTask.Params(roomId))
}
- override fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback) {
- peekRoomTask
- .configureWith(PeekRoomTask.Params(roomIdOrAlias)) {
- this.callback = callback
- }
- .executeBy(taskExecutor)
+ override suspend fun peekRoom(roomIdOrAlias: String): PeekResult {
+ return peekRoomTask.execute(PeekRoomTask.Params(roomIdOrAlias))
+ }
+
+ override fun getFlattenRoomSummaryChildrenOf(spaceId: String?, memberships: List): List {
+ if (spaceId == null) {
+ return roomSummaryDataSource.getFlattenOrphanRooms()
+ }
+ return roomSummaryDataSource.getAllRoomSummaryChildOf(spaceId, memberships)
+ }
+
+ override fun getFlattenRoomSummaryChildrenOfLive(spaceId: String?, memberships: List): LiveData> {
+ if (spaceId == null) {
+ return roomSummaryDataSource.getFlattenOrphanRoomsLive()
+ }
+ return roomSummaryDataSource.getAllRoomSummaryChildOfLive(spaceId, memberships)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
index 90640b4700..3f743c2922 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService
import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService
import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService
@@ -60,6 +61,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
private val membershipServiceFactory: DefaultMembershipService.Factory,
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
private val sendStateTask: SendStateTask,
+ private val viaParameterFinder: ViaParameterFinder,
private val searchTask: SearchTask) :
RoomFactory {
@@ -83,7 +85,8 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
roomMembersService = membershipServiceFactory.create(roomId),
roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
sendStateTask = sendStateTask,
- searchTask = searchTask
+ searchTask = searchTask,
+ viaParameterFinder = viaParameterFinder
)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
index 5133f72932..8f3445bec3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
@@ -24,6 +24,7 @@ import org.commonmark.renderer.html.HtmlRenderer
import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.room.RoomDirectoryService
import org.matrix.android.sdk.api.session.room.RoomService
+import org.matrix.android.sdk.api.session.space.SpaceService
import org.matrix.android.sdk.internal.session.DefaultFileService
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
@@ -89,6 +90,7 @@ 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.uploads.DefaultGetUploadsTask
import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask
+import org.matrix.android.sdk.internal.session.space.DefaultSpaceService
import retrofit2.Retrofit
@Module
@@ -135,6 +137,9 @@ internal abstract class RoomModule {
@Binds
abstract fun bindRoomService(service: DefaultRoomService): RoomService
+ @Binds
+ abstract fun bindSpaceService(service: DefaultSpaceService): SpaceService
+
@Binds
abstract fun bindRoomDirectoryService(service: DefaultRoomDirectoryService): RoomDirectoryService
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt
new file mode 100644
index 0000000000..fed3ff542b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.room
+
+import org.matrix.android.sdk.api.session.space.Space
+import javax.inject.Inject
+
+internal interface SpaceGetter {
+ fun get(spaceId: String): Space?
+}
+
+internal class DefaultSpaceGetter @Inject constructor(
+ private val roomGetter: RoomGetter
+) : SpaceGetter {
+
+ override fun get(spaceId: String): Space? {
+ return roomGetter.getRoom(spaceId)?.asSpace()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
index 9faf50dd8b..b39cbaa582 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
@@ -36,7 +36,11 @@ internal class RoomAliasAvailabilityChecker @Inject constructor(
@Throws(RoomAliasError::class)
suspend fun check(aliasLocalPart: String?) {
if (aliasLocalPart.isNullOrEmpty()) {
- throw RoomAliasError.AliasEmpty
+ // don't check empty or not provided alias
+ return
+ }
+ if (aliasLocalPart.isBlank()) {
+ throw RoomAliasError.AliasIsBlank
}
// Check alias availability
val fullAlias = aliasLocalPart.toFullLocalAlias(userId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt
index 13d403e2e4..69352688e3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt
@@ -111,5 +111,12 @@ internal data class CreateRoomBody(
* The power level content to override in the default power level event
*/
@Json(name = "power_level_content_override")
- val powerLevelContentOverride: PowerLevelsContent?
+ val powerLevelContentOverride: PowerLevelsContent?,
+
+ /**
+ * The room version to set for the room. If not provided, the homeserver is to use its configured default.
+ * If provided, the homeserver will return a 400 error with the errcode M_UNSUPPORTED_ROOM_VERSION if it does not support the room version.
+ */
+ @Json(name = "room_version")
+ val roomVersion: String?
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
index 5e823fc87f..018b865388 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
@@ -20,13 +20,19 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.identity.toMedium
+import org.matrix.android.sdk.api.session.room.model.GuestAccess
+import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.util.MimeTypes
import org.matrix.android.sdk.internal.crypto.DeviceListManager
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
+import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
import org.matrix.android.sdk.internal.session.content.FileUploader
import org.matrix.android.sdk.internal.session.identity.EnsureIdentityTokenTask
@@ -43,6 +49,8 @@ internal class CreateRoomBodyBuilder @Inject constructor(
private val deviceListManager: DeviceListManager,
private val identityStore: IdentityStore,
private val fileUploader: FileUploader,
+ @UserId
+ private val userId: String,
@AuthenticatedIdentity
private val accessTokenProvider: AccessTokenProvider
) {
@@ -68,10 +76,17 @@ internal class CreateRoomBodyBuilder @Inject constructor(
}
}
+ if (params.joinRuleRestricted != null) {
+ params.roomVersion = "org.matrix.msc3083"
+ params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED
+ params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden
+ }
val initialStates = listOfNotNull(
buildEncryptionWithAlgorithmEvent(params),
buildHistoryVisibilityEvent(params),
- buildAvatarEvent(params)
+ buildAvatarEvent(params),
+ buildGuestAccess(params),
+ buildJoinRulesRestricted(params)
)
.takeIf { it.isNotEmpty() }
@@ -80,13 +95,15 @@ internal class CreateRoomBodyBuilder @Inject constructor(
roomAliasName = params.roomAliasName,
name = params.name,
topic = params.topic,
- invitedUserIds = params.invitedUserIds,
+ invitedUserIds = params.invitedUserIds.filter { it != userId },
invite3pids = invite3pids,
creationContent = params.creationContent.takeIf { it.isNotEmpty() },
initialStates = initialStates,
preset = params.preset,
isDirect = params.isDirect,
- powerLevelContentOverride = params.powerLevelContentOverride
+ powerLevelContentOverride = params.powerLevelContentOverride,
+ roomVersion = params.roomVersion
+
)
}
@@ -120,6 +137,31 @@ internal class CreateRoomBodyBuilder @Inject constructor(
}
}
+ private fun buildGuestAccess(params: CreateRoomParams): Event? {
+ return params.guestAccess
+ ?.let {
+ Event(
+ type = EventType.STATE_ROOM_GUEST_ACCESS,
+ stateKey = "",
+ content = mapOf("guest_access" to it.value)
+ )
+ }
+ }
+
+ private fun buildJoinRulesRestricted(params: CreateRoomParams): Event? {
+ return params.joinRuleRestricted
+ ?.let { allowList ->
+ Event(
+ type = EventType.STATE_ROOM_JOIN_RULES,
+ stateKey = "",
+ content = RoomJoinRulesContent(
+ _joinRules = RoomJoinRules.RESTRICTED.value,
+ allowList = allowList
+ ).toContent()
+ )
+ }
+ }
+
/**
* Add the crypto algorithm to the room creation parameters.
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
index bafe2b90ae..de6a71e581 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
@@ -102,7 +102,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
}
} catch (exception: TimeoutCancellationException) {
- throw CreateRoomFailure.CreatedWithTimeout
+ throw CreateRoomFailure.CreatedWithTimeout(roomId)
}
Realm.getInstance(realmConfiguration).executeTransactionAsync {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
index 5b211c505f..c6f4bbb4e1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
@@ -23,11 +23,14 @@ import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
+import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
+import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
+import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
@@ -100,7 +103,9 @@ internal class DefaultPeekRoomTask @Inject constructor(
name = publicRepoResult.name,
topic = publicRepoResult.topic,
numJoinedMembers = publicRepoResult.numJoinedMembers,
- viaServers = serverList
+ viaServers = serverList,
+ roomType = null, // would be nice to get that from directory...
+ someMembers = null
)
}
@@ -125,11 +130,25 @@ internal class DefaultPeekRoomTask @Inject constructor(
?.let { it.content?.toModel()?.canonicalAlias }
// not sure if it's the right way to do that :/
- val memberCount = stateEvents
+ val membersEvent = stateEvents
.filter { it.type == EventType.STATE_ROOM_MEMBER && it.stateKey?.isNotEmpty() == true }
+
+ val memberCount = membersEvent
.distinctBy { it.stateKey }
.count()
+ val someMembers = membersEvent.mapNotNull { ev ->
+ ev.content?.toModel()?.let {
+ MatrixItem.UserItem(ev.stateKey ?: "", it.displayName, it.avatarUrl)
+ }
+ }
+
+ val roomType = stateEvents
+ .lastOrNull { it.type == EventType.STATE_ROOM_CREATE }
+ ?.content
+ ?.toModel()
+ ?.type
+
return PeekResult.Success(
roomId = roomId,
alias = alias,
@@ -137,7 +156,9 @@ internal class DefaultPeekRoomTask @Inject constructor(
name = name,
topic = topic,
numJoinedMembers = memberCount,
- viaServers = serverList
+ roomType = roomType,
+ viaServers = serverList,
+ someMembers = someMembers
)
} catch (failure: Throwable) {
// Would be M_FORBIDDEN if cannot peek :/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt
index 5fe06287d2..a666d40fc3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt
@@ -99,6 +99,7 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor:
entity.age = editedEventEntity.age
entity.originServerTs = editedEventEntity.originServerTs
entity.sendState = editedEventEntity.sendState
+ entity.sendStateDetails = editedEventEntity.sendStateDetails
}
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt
new file mode 100644
index 0000000000..2efea7f118
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.room.relationship
+
+import io.realm.Realm
+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.space.model.SpaceChildContent
+import org.matrix.android.sdk.api.session.space.model.SpaceParentContent
+import org.matrix.android.sdk.internal.database.mapper.ContentMapper
+import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
+import org.matrix.android.sdk.internal.database.query.whereType
+
+/**
+ * Relationship between rooms and spaces
+ * The intention is that rooms and spaces form a hierarchy, which clients can use to structure the user's room list into a tree view.
+ * The parent/child relationship can be expressed in one of two ways:
+ * - The admins of a space can advertise rooms and subspaces for their space by setting m.space.child state events.
+ * The state_key is the ID of a child room or space, and the content should contain a via key which gives
+ * a list of candidate servers that can be used to join the room. present: true key is included to distinguish from a deleted state event.
+ *
+ * - Separately, rooms can claim parents via the m.room.parent state event.
+ */
+internal class RoomChildRelationInfo(
+ private val realm: Realm,
+ private val roomId: String
+) {
+
+ data class SpaceChildInfo(
+ val roomId: String,
+ val order: String?,
+ val autoJoin: Boolean,
+ val viaServers: List
+ )
+
+ data class SpaceParentInfo(
+ val roomId: String,
+ val canonical: Boolean,
+ val viaServers: List,
+ val stateEventSender: String
+ )
+
+ /**
+ * Gets the ordered list of valid child description.
+ */
+ fun getDirectChildrenDescriptions(): List {
+ return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD)
+ .findAll()
+// .also {
+// Timber.v("## Space: Found ${it.count()} m.space.child state events for $roomId")
+// }
+ .mapNotNull {
+ ContentMapper.map(it.root?.content).toModel()?.let { scc ->
+// Timber.v("## Space child desc state event $scc")
+ // Children where via is not present are ignored.
+ scc.via?.let { via ->
+ SpaceChildInfo(
+ roomId = it.stateKey,
+ order = scc.validOrder(),
+ autoJoin = scc.autoJoin ?: false,
+ viaServers = via
+ )
+ }
+ }
+ }
+ .sortedBy { it.order }
+ }
+
+ fun getParentDescriptions(): List {
+ return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_PARENT)
+ .findAll()
+// .also {
+// Timber.v("## Space: Found ${it.count()} m.space.parent state events for $roomId")
+// }
+ .mapNotNull {
+ ContentMapper.map(it.root?.content).toModel()?.let { scc ->
+// Timber.v("## Space parent desc state event $scc")
+ // Parent where via is not present are ignored.
+ scc.via?.let { via ->
+ SpaceParentInfo(
+ roomId = it.stateKey,
+ canonical = scc.canonical ?: false,
+ viaServers = via,
+ stateEventSender = it.root?.sender ?: ""
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt
index 26a87557ff..449189e6b5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt
@@ -109,14 +109,6 @@ internal class DefaultSendService @AssistedInject constructor(
.let { sendEvent(it) }
}
- override fun sendMedias(attachments: List,
- compressBeforeSending: Boolean,
- roomIds: Set): Cancelable {
- return attachments.mapTo(CancelableBag()) {
- sendMedia(it, compressBeforeSending, roomIds)
- }
- }
-
override fun redactEvent(event: Event, reason: String?): Cancelable {
// TODO manage media/attachements?
val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason)
@@ -149,7 +141,7 @@ internal class DefaultSendService @AssistedInject constructor(
is MessageImageContent -> {
// The image has not yet been sent
val attachmentData = ContentAttachmentData(
- size = messageContent.info!!.size.toLong(),
+ size = messageContent.info!!.size,
mimeType = messageContent.info.mimeType!!,
width = messageContent.info.width.toLong(),
height = messageContent.info.height.toLong(),
@@ -240,6 +232,14 @@ internal class DefaultSendService @AssistedInject constructor(
}
}
+ override fun sendMedias(attachments: List,
+ compressBeforeSending: Boolean,
+ roomIds: Set): Cancelable {
+ return attachments.mapTo(CancelableBag()) {
+ sendMedia(it, compressBeforeSending, roomIds)
+ }
+ }
+
override fun sendMedia(attachment: ContentAttachmentData,
compressBeforeSending: Boolean,
roomIds: Set): Cancelable {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
index 432a4af062..c1ad6205c3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
@@ -244,7 +244,7 @@ internal class LocalEchoEventFactory @Inject constructor(
mimeType = attachment.getSafeMimeType(),
width = width?.toInt() ?: 0,
height = height?.toInt() ?: 0,
- size = attachment.size.toInt()
+ size = attachment.size
),
url = attachment.queryUri.toString()
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
index 70245cbd5e..e98e5646af 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
@@ -87,7 +87,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
}
}
- fun updateSendState(eventId: String, roomId: String?, sendState: SendState) {
+ fun updateSendState(eventId: String, roomId: String?, sendState: SendState, sendStateDetails: String? = null) {
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Update local state of $eventId to ${sendState.name}")
timelineInput.onLocalEchoUpdated(roomId = roomId ?: "", eventId = eventId, sendState = sendState)
updateEchoAsync(eventId) { realm, sendingEventEntity ->
@@ -96,6 +96,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
} else {
sendingEventEntity.sendState = sendState
}
+ sendingEventEntity.sendStateDetails = sendStateDetails
roomSummaryUpdater.updateSendingInformation(realm, sendingEventEntity.roomId)
}
}
@@ -161,6 +162,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
val timelineEvents = TimelineEventEntity.where(realm, roomId, eventIds).findAll()
timelineEvents.forEach {
it.root?.sendState = sendState
+ it.root?.sendStateDetails = null
}
roomSummaryUpdater.updateSendingInformation(realm, roomId)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
index bc307bc74f..e889f1a61b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
@@ -55,7 +55,12 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
override fun doOnError(params: Params): Result {
params.localEchoIds.forEach { localEchoIds ->
- localEchoRepository.updateSendState(localEchoIds.eventId, localEchoIds.roomId, SendState.UNDELIVERED)
+ localEchoRepository.updateSendState(
+ eventId = localEchoIds.eventId,
+ roomId = localEchoIds.roomId,
+ sendState = SendState.UNDELIVERED,
+ sendStateDetails = params.lastFailureMessage
+ )
}
return super.doOnError(params)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
index d55dce57af..cd7911910d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.SessionComponent
+import org.matrix.android.sdk.internal.util.toMatrixErrorStr
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import timber.log.Timber
@@ -77,7 +78,12 @@ internal class SendEventWorker(context: Context,
}
if (params.lastFailureMessage != null) {
- localEchoRepository.updateSendState(event.eventId, event.roomId, SendState.UNDELIVERED)
+ localEchoRepository.updateSendState(
+ eventId = event.eventId,
+ roomId = event.roomId,
+ sendState = SendState.UNDELIVERED,
+ sendStateDetails = params.lastFailureMessage
+ )
// Transmit the error
return Result.success(inputData)
.also { Timber.e("Work cancelled due to input error from parent") }
@@ -90,7 +96,12 @@ internal class SendEventWorker(context: Context,
} catch (exception: Throwable) {
if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) {
Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}")
- localEchoRepository.updateSendState(event.eventId, event.roomId, SendState.UNDELIVERED)
+ localEchoRepository.updateSendState(
+ eventId = event.eventId,
+ roomId = event.roomId,
+ sendState = SendState.UNDELIVERED,
+ sendStateDetails = exception.toMatrixErrorStr()
+ )
Result.success()
} else {
Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt
index 8bafa5f882..cd5bf575db 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.room.send.queue
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.util.Cancelable
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
internal interface EventSenderProcessor: SessionLifecycleObserver {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
index a5c09f5ff6..80bfd02b0e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
@@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.getRetryDelay
+import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.util.Cancelable
@@ -72,7 +73,7 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
*/
private val cancelableBag = ConcurrentHashMap()
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
// We should check for sending events not handled because app was killed
// But we should be careful of only took those that was submitted to us, because if it's
// for example it's a media event it is handled by some worker and he will handle it
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt
index b79a86dd7e..9db7cc9039 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt
@@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.isTokenError
+import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.sync.SyncState
@@ -64,11 +65,11 @@ internal class EventSenderProcessorThread @Inject constructor(
memento.unTrack(task)
}
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
start()
}
- override fun onSessionStopped() {
+ override fun onSessionStopped(session: Session) {
interrupt()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
index 615bc99096..ff2afb5d61 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.MimeTypes
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.session.content.FileUploader
+import java.lang.UnsupportedOperationException
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
private val stateEventDataSource: StateEventDataSource,
@@ -73,7 +74,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
eventType = eventType,
body = body.toSafeJson(eventType)
)
- sendStateTask.execute(params)
+ sendStateTask.executeRetry(params, 3)
}
private fun JsonDict.toSafeJson(eventType: String): JsonDict {
@@ -127,6 +128,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) {
if (joinRules != null) {
+ if (joinRules == RoomJoinRules.RESTRICTED) throw UnsupportedOperationException("No yet supported")
sendStateEvent(
eventType = EventType.STATE_ROOM_JOIN_RULES,
body = mapOf("join_rule" to joinRules),
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
index a97709e38b..197b4f8688 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
@@ -21,22 +21,21 @@ import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
-import org.matrix.android.sdk.api.session.room.powerlevels.Role
import org.matrix.android.sdk.api.util.JsonDict
@JsonClass(generateAdapter = true)
internal data class SerializablePowerLevelsContent(
- @Json(name = "ban") val ban: Int = Role.Moderator.value,
- @Json(name = "kick") val kick: Int = Role.Moderator.value,
- @Json(name = "invite") val invite: Int = Role.Moderator.value,
- @Json(name = "redact") val redact: Int = Role.Moderator.value,
- @Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
- @Json(name = "events") val events: Map = emptyMap(),
- @Json(name = "users_default") val usersDefault: Int = Role.Default.value,
- @Json(name = "users") val users: Map = emptyMap(),
- @Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
+ @Json(name = "ban") val ban: Int?,
+ @Json(name = "kick") val kick: Int?,
+ @Json(name = "invite") val invite: Int?,
+ @Json(name = "redact") val redact: Int?,
+ @Json(name = "events_default") val eventsDefault: Int?,
+ @Json(name = "events") val events: Map?,
+ @Json(name = "users_default") val usersDefault: Int?,
+ @Json(name = "users") val users: Map?,
+ @Json(name = "state_default") val stateDefault: Int?,
// `Int` is the diff here (instead of `Any`)
- @Json(name = "notifications") val notifications: Map = emptyMap()
+ @Json(name = "notifications") val notifications: Map?
)
internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
@@ -52,7 +51,7 @@ internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
usersDefault = content.usersDefault,
users = content.users,
stateDefault = content.stateDefault,
- notifications = content.notifications.mapValues { content.notificationLevel(it.key) }
+ notifications = content.notifications?.mapValues { content.notificationLevel(it.key) }
)
}
?.toContent()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt
new file mode 100644
index 0000000000..b7e6548b54
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.room.summary
+
+import java.util.LinkedList
+
+data class GraphNode(
+ val name: String
+)
+
+data class GraphEdge(
+ val source: GraphNode,
+ val destination: GraphNode
+)
+
+class Graph {
+
+ private val adjacencyList: HashMap> = HashMap()
+
+ fun getOrCreateNode(name: String): GraphNode {
+ return adjacencyList.entries.firstOrNull { it.key.name == name }?.key
+ ?: GraphNode(name).also {
+ adjacencyList[it] = ArrayList()
+ }
+ }
+
+ fun addEdge(sourceName: String, destinationName: String) {
+ val source = getOrCreateNode(sourceName)
+ val destination = getOrCreateNode(destinationName)
+ adjacencyList.getOrPut(source) { ArrayList() }.add(
+ GraphEdge(source, destination)
+ )
+ }
+
+ fun addEdge(source: GraphNode, destination: GraphNode) {
+ adjacencyList.getOrPut(source) { ArrayList() }.add(
+ GraphEdge(source, destination)
+ )
+ }
+
+ fun edgesOf(node: GraphNode): List {
+ return adjacencyList[node]?.toList() ?: emptyList()
+ }
+
+ fun withoutEdges(edgesToPrune: List): Graph {
+ val output = Graph()
+ this.adjacencyList.forEach { (vertex, edges) ->
+ output.getOrCreateNode(vertex.name)
+ edges.forEach {
+ if (!edgesToPrune.contains(it)) {
+ // add it
+ output.addEdge(it.source, it.destination)
+ }
+ }
+ }
+ return output
+ }
+
+ /**
+ * Depending on the chosen starting point the background edge might change
+ */
+ fun findBackwardEdges(startFrom: GraphNode? = null): List {
+ val backwardEdges = mutableSetOf()
+ val visited = mutableMapOf()
+ val notVisited = -1
+ val inPath = 0
+ val completed = 1
+ adjacencyList.keys.forEach {
+ visited[it] = notVisited
+ }
+ val stack = LinkedList()
+
+ (startFrom ?: adjacencyList.entries.firstOrNull { visited[it.key] == notVisited }?.key)
+ ?.let {
+ stack.push(it)
+ visited[it] = inPath
+ }
+
+ while (stack.isNotEmpty()) {
+// Timber.w("VAL: current stack: ${stack.reversed().joinToString { it.name }}")
+ val vertex = stack.peek() ?: break
+ // peek a path to follow
+ var destination: GraphNode? = null
+ edgesOf(vertex).forEach {
+ when (visited[it.destination]) {
+ notVisited -> {
+ // it's a candidate
+ destination = it.destination
+ }
+ inPath -> {
+ // Cycle!!
+ backwardEdges.add(it)
+ }
+ completed -> {
+ // dead end
+ }
+ }
+ }
+ if (destination == null) {
+ // dead end, pop
+ stack.pop().let {
+ visited[it] = completed
+ }
+ } else {
+ // go down this path
+ stack.push(destination)
+ visited[destination!!] = inPath
+ }
+
+ if (stack.isEmpty()) {
+ // try to get another graph of forest?
+ adjacencyList.entries.firstOrNull { visited[it.key] == notVisited }?.key?.let {
+ stack.push(it)
+ visited[it] = inPath
+ }
+ }
+ }
+
+ return backwardEdges.toList()
+ }
+
+ /**
+ * Only call that on acyclic graph!
+ */
+ fun flattenDestination(): Map> {
+ val result = HashMap>()
+ adjacencyList.keys.forEach { vertex ->
+ result[vertex] = flattenOf(vertex)
+ }
+ return result
+ }
+
+ private fun flattenOf(node: GraphNode): Set {
+ val result = mutableSetOf()
+ val edgesOf = edgesOf(node)
+ result.addAll(edgesOf.map { it.destination })
+ edgesOf.forEach {
+ result.addAll(flattenOf(it.destination))
+ }
+ return result
+ }
+
+ override fun toString(): String {
+ return buildString {
+ adjacencyList.forEach { (node, edges) ->
+ append("${node.name} : [")
+ append(edges.joinToString(" ") { it.destination.name })
+ append("]\n")
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/HierarchyLiveDataHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/HierarchyLiveDataHelper.kt
new file mode 100644
index 0000000000..29db8431fd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/HierarchyLiveDataHelper.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.summary
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.util.Optional
+
+internal class HierarchyLiveDataHelper(
+ val spaceId: String,
+ val memberships: List,
+ val roomSummaryDataSource: RoomSummaryDataSource) {
+
+ private val sources = HashMap>>()
+ private val mediatorLiveData = MediatorLiveData>()
+
+ fun liveData(): LiveData> = mediatorLiveData
+
+ init {
+ onChange()
+ }
+
+ private fun parentsToCheck(): List {
+ val spaces = ArrayList()
+ roomSummaryDataSource.getSpaceSummary(spaceId)?.let {
+ roomSummaryDataSource.flattenSubSpace(it, emptyList(), spaces, memberships)
+ }
+ return spaces
+ }
+
+ private fun onChange() {
+ val existingSources = sources.keys.toList()
+ val newSources = parentsToCheck().map { it.roomId }
+ val addedSources = newSources.filter { !existingSources.contains(it) }
+ val removedSource = existingSources.filter { !newSources.contains(it) }
+ addedSources.forEach {
+ val liveData = roomSummaryDataSource.getSpaceSummaryLive(it)
+ mediatorLiveData.addSource(liveData) { onChange() }
+ sources[it] = liveData
+ }
+
+ removedSource.forEach {
+ sources[it]?.let { mediatorLiveData.removeSource(it) }
+ }
+
+ sources[spaceId]?.value?.getOrNull()?.let { spaceSummary ->
+ val results = ArrayList()
+ roomSummaryDataSource.flattenChild(spaceSummary, emptyList(), results, memberships)
+ mediatorLiveData.postValue(results.map { it.roomId })
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
index dd3fbe04b2..126458b082 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
@@ -1,5 +1,6 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
+ * 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.
@@ -17,6 +18,7 @@
package org.matrix.android.sdk.internal.session.room.summary
import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
@@ -24,12 +26,21 @@ import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.Sort
+import io.realm.kotlin.where
+import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter
+import org.matrix.android.sdk.api.session.room.ResultBoundaries
+import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
-import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult
+import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
+import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.model.VersioningState
+import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
+import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
+import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
@@ -79,11 +90,60 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> {
return monarchy.findAllMappedWithChanges(
- { roomSummariesQuery(it, queryParams) },
+ {
+ roomSummariesQuery(it, queryParams)
+ .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
+ },
{ roomSummaryMapper.map(it) }
)
}
+ fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> {
+ return getRoomSummariesLive(queryParams)
+ }
+
+ fun getSpaceSummary(roomIdOrAlias: String): RoomSummary? {
+ return getRoomSummary(roomIdOrAlias)
+ ?.takeIf { it.roomType == RoomType.SPACE }
+ }
+
+ fun getSpaceSummaryLive(roomId: String): LiveData> {
+ val liveData = monarchy.findAllMappedWithChanges(
+ { realm ->
+ RoomSummaryEntity.where(realm, roomId)
+ .isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
+ .equalTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
+ },
+ {
+ roomSummaryMapper.map(it)
+ }
+ )
+ return Transformations.map(liveData) { results ->
+ results.firstOrNull().toOptional()
+ }
+ }
+
+ fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List {
+ return getRoomSummaries(spaceSummaryQueryParams)
+ }
+
+ fun getRootSpaceSummaries(): List {
+ return getRoomSummaries(spaceSummaryQueryParams {
+ memberships = listOf(Membership.JOIN)
+ })
+ .let { allJoinedSpace ->
+ val allFlattenChildren = arrayListOf()
+ allJoinedSpace.forEach {
+ flattenSubSpace(it, emptyList(), allFlattenChildren, listOf(Membership.JOIN), false)
+ }
+ val knownNonOrphan = allFlattenChildren.map { it.roomId }.distinct()
+ // keep only root rooms
+ allJoinedSpace.filter { candidate ->
+ !knownNonOrphan.contains(candidate.roomId)
+ }
+ }
+ }
+
fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List {
return monarchy.fetchAllMappedSync(
{ breadcrumbsQuery(it, queryParams) },
@@ -105,10 +165,10 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
}
fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
- pagedListConfig: PagedList.Config): LiveData> {
+ pagedListConfig: PagedList.Config,
+ sortOrder: RoomSortOrder): LiveData> {
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
- roomSummariesQuery(realm, queryParams)
- .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
+ roomSummariesQuery(realm, queryParams).process(sortOrder)
}
val dataSourceFactory = realmDataSourceFactory.map {
roomSummaryMapper.map(it)
@@ -119,30 +179,48 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
)
}
- fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
- pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult {
+ fun getUpdatablePagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
+ pagedListConfig: PagedList.Config,
+ sortOrder: RoomSortOrder): UpdatableLivePageResult {
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
- roomSummariesQuery(realm, queryParams)
- .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
+ roomSummariesQuery(realm, queryParams).process(sortOrder)
}
val dataSourceFactory = realmDataSourceFactory.map {
roomSummaryMapper.map(it)
}
+ val boundaries = MutableLiveData(ResultBoundaries())
+
val mapped = monarchy.findAllPagedWithChanges(
realmDataSourceFactory,
- LivePagedListBuilder(dataSourceFactory, pagedListConfig)
+ LivePagedListBuilder(dataSourceFactory, pagedListConfig).also {
+ it.setBoundaryCallback(object : PagedList.BoundaryCallback() {
+ override fun onItemAtEndLoaded(itemAtEnd: RoomSummary) {
+ boundaries.postValue(boundaries.value?.copy(frontLoaded = true))
+ }
+
+ override fun onItemAtFrontLoaded(itemAtFront: RoomSummary) {
+ boundaries.postValue(boundaries.value?.copy(endLoaded = true))
+ }
+
+ override fun onZeroItemsLoaded() {
+ boundaries.postValue(boundaries.value?.copy(zeroItemLoaded = true))
+ }
+ })
+ }
)
- return object : UpdatableFilterLivePageResult {
+ return object : UpdatableLivePageResult {
override val livePagedList: LiveData> = mapped
- override fun updateQuery(queryParams: RoomSummaryQueryParams) {
+ override fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams) {
realmDataSourceFactory.updateQuery {
- roomSummariesQuery(it, queryParams)
- .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
+ roomSummariesQuery(it, builder.invoke(queryParams)).process(sortOrder)
}
}
+
+ override val liveBoundaries: LiveData
+ get() = boundaries
}
}
@@ -170,10 +248,10 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
queryParams.roomCategoryFilter?.let {
when (it) {
- RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
- RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
+ RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+ RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0)
- RoomCategoryFilter.ALL -> {
+ RoomCategoryFilter.ALL -> {
// nop
}
}
@@ -189,6 +267,160 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, sn)
}
}
+
+ queryParams.excludeType?.forEach {
+ query.notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, it)
+ }
+ queryParams.includeType?.forEach {
+ query.equalTo(RoomSummaryEntityFields.ROOM_TYPE, it)
+ }
+ when (queryParams.roomCategoryFilter) {
+ RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+ RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
+ RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0)
+ RoomCategoryFilter.ALL -> Unit // nop
+ }
+
+ // Timber.w("VAL: activeSpaceId : ${queryParams.activeSpaceId}")
+ when (queryParams.activeSpaceFilter) {
+ is ActiveSpaceFilter.ActiveSpace -> {
+ // It's annoying but for now realm java does not support querying in primitive list :/
+ // https://github.com/realm/realm-java/issues/5361
+ if (queryParams.activeSpaceFilter.currentSpaceId == null) {
+ // orphan rooms
+ query.isNull(RoomSummaryEntityFields.FLATTEN_PARENT_IDS)
+ } else {
+ query.contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, queryParams.activeSpaceFilter.currentSpaceId)
+ }
+ }
+ is ActiveSpaceFilter.ExcludeSpace -> {
+ query.not().contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, queryParams.activeSpaceFilter.spaceId)
+ }
+ else -> {
+ // nop
+ }
+ }
+
+ if (queryParams.activeGroupId != null) {
+ query.contains(RoomSummaryEntityFields.GROUP_IDS, queryParams.activeGroupId!!)
+ }
return query
}
+
+ fun getAllRoomSummaryChildOf(spaceAliasOrId: String, memberShips: List): List {
+ val space = getSpaceSummary(spaceAliasOrId) ?: return emptyList()
+ val result = ArrayList()
+ flattenChild(space, emptyList(), result, memberShips)
+ return result
+ }
+
+ fun getAllRoomSummaryChildOfLive(spaceId: String, memberShips: List): LiveData> {
+ // we want to listen to all spaces in hierarchy and on change compute back all childs
+ // and switch map to listen those?
+ val mediatorLiveData = HierarchyLiveDataHelper(spaceId, memberShips, this).liveData()
+
+ return Transformations.switchMap(mediatorLiveData) { allIds ->
+ monarchy.findAllMappedWithChanges(
+ {
+ it.where()
+ .`in`(RoomSummaryEntityFields.ROOM_ID, allIds.toTypedArray())
+ .`in`(RoomSummaryEntityFields.MEMBERSHIP_STR, memberShips.map { it.name }.toTypedArray())
+ .equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
+ },
+ {
+ roomSummaryMapper.map(it)
+ })
+ }
+ }
+
+ fun getFlattenOrphanRooms(): List {
+ return getRoomSummaries(
+ roomSummaryQueryParams {
+ memberships = Membership.activeMemberships()
+ excludeType = listOf(RoomType.SPACE)
+ roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
+ }
+ ).filter { isOrphan(it) }
+ }
+
+ fun getFlattenOrphanRoomsLive(): LiveData> {
+ return Transformations.map(
+ getRoomSummariesLive(roomSummaryQueryParams {
+ memberships = Membership.activeMemberships()
+ excludeType = listOf(RoomType.SPACE)
+ roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
+ })
+ ) {
+ it.filter { isOrphan(it) }
+ }
+ }
+
+ private fun isOrphan(roomSummary: RoomSummary): Boolean {
+ if (roomSummary.roomType == RoomType.SPACE && roomSummary.membership.isActive()) {
+ return false
+ }
+ // all parents line should be orphan
+ roomSummary.spaceParents?.forEach { info ->
+ if (info.roomSummary != null && !info.roomSummary.membership.isLeft()) {
+ if (!isOrphan(info.roomSummary)) {
+ return false
+ }
+ }
+ }
+
+ // it may not have a parent relation but could be a child of some other....
+ for (spaceSummary in getSpaceSummaries(spaceSummaryQueryParams { memberships = Membership.activeMemberships() })) {
+ if (spaceSummary.spaceChildren?.any { it.childRoomId == roomSummary.roomId } == true) {
+ return false
+ }
+ }
+
+ return true
+ }
+
+ fun flattenChild(current: RoomSummary, parenting: List, output: MutableList, memberShips: List) {
+ current.spaceChildren?.sortedBy { it.order ?: it.name }?.forEach { childInfo ->
+ if (childInfo.roomType == RoomType.SPACE) {
+ // Add recursive
+ if (!parenting.contains(childInfo.childRoomId)) { // avoid cycles!
+ getSpaceSummary(childInfo.childRoomId)?.let { subSpace ->
+ if (memberShips.isEmpty() || memberShips.contains(subSpace.membership)) {
+ flattenChild(subSpace, parenting + listOf(current.roomId), output, memberShips)
+ }
+ }
+ }
+ } else if (childInfo.isKnown) {
+ getRoomSummary(childInfo.childRoomId)?.let {
+ if (memberShips.isEmpty() || memberShips.contains(it.membership)) {
+ if (!it.isDirect) {
+ output.add(it)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun flattenSubSpace(current: RoomSummary,
+ parenting: List,
+ output: MutableList,
+ memberShips: List,
+ includeCurrent: Boolean = true) {
+ if (includeCurrent) {
+ output.add(current)
+ }
+ current.spaceChildren?.sortedBy { it.order ?: it.name }?.forEach {
+ if (it.roomType == RoomType.SPACE) {
+ // Add recursive
+ if (!parenting.contains(it.childRoomId)) { // avoid cycles!
+ getSpaceSummary(it.childRoomId)?.let { subSpace ->
+ if (memberShips.isEmpty() || memberShips.contains(subSpace.membership)) {
+ output.add(subSpace)
+ flattenSubSpace(subSpace, parenting + listOf(current.roomId), output, memberShips)
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
index 7913bf71a2..cac0d715f5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
@@ -17,14 +17,18 @@
package org.matrix.android.sdk.internal.session.room.summary
import io.realm.Realm
+import io.realm.kotlin.createObject
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
+import org.matrix.android.sdk.api.session.room.model.RoomType
+import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.EventDecryptor
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
@@ -34,29 +38,40 @@ import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.model.EventEntityFields
+import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntity
+import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.database.query.getOrNull
import org.matrix.android.sdk.internal.database.query.isEventRead
+import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.database.query.whereType
import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.extensions.clearWith
+import org.matrix.android.sdk.internal.query.process
import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver
import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
+import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo
+import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncSummary
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications
import timber.log.Timber
import javax.inject.Inject
+import kotlin.system.measureTimeMillis
internal class RoomSummaryUpdater @Inject constructor(
@UserId private val userId: String,
private val roomDisplayNameResolver: RoomDisplayNameResolver,
private val roomAvatarResolver: RoomAvatarResolver,
private val eventDecryptor: EventDecryptor,
- private val crossSigningService: DefaultCrossSigningService) {
+ private val crossSigningService: DefaultCrossSigningService,
+ private val stateEventDataSource: StateEventDataSource) {
fun update(realm: Realm,
roomId: String,
@@ -89,6 +104,12 @@ internal class RoomSummaryUpdater @Inject constructor(
val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
val lastAliasesEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root
+ val roomCreateEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CREATE, stateKey = "")?.root
+ val joinRulesEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_JOIN_RULES, stateKey = "")?.root
+
+ val roomType = ContentMapper.map(roomCreateEvent?.content).toModel()?.type
+ roomSummaryEntity.roomType = roomType
+ Timber.v("## Space: Updating summary room [$roomId] roomType: [$roomType]")
// Don't use current state for this one as we are only interested in having MXCRYPTO_ALGORITHM_MEGOLM event in the room
val encryptionEvent = EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
@@ -111,6 +132,7 @@ internal class RoomSummaryUpdater @Inject constructor(
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)
roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel()?.name
roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel()?.topic
+ roomSummaryEntity.joinRules = ContentMapper.map(joinRulesEvent?.content).toModel()?.joinRules
roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent
roomSummaryEntity.canonicalAlias = ContentMapper.map(lastCanonicalAliasEvent?.content).toModel()
?.canonicalAlias
@@ -163,4 +185,235 @@ internal class RoomSummaryUpdater @Inject constructor(
roomSummaryEntity.updateHasFailedSending()
roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
}
+
+ /**
+ * Should be called at the end of the room sync, to check and validate all parent/child relations
+ */
+ fun validateSpaceRelationship(realm: Realm) {
+ measureTimeMillis {
+ val lookupMap = realm.where(RoomSummaryEntity::class.java)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
+ // we order by roomID to be consistent when breaking parent/child cycles
+ .sort(RoomSummaryEntityFields.ROOM_ID)
+ .findAll().map {
+ it.flattenParentIds = null
+ it to emptyList().toMutableSet()
+ }
+ .toMap()
+
+ lookupMap.keys.forEach { lookedUp ->
+ if (lookedUp.roomType == RoomType.SPACE) {
+ // get childrens
+
+ lookedUp.children.clearWith { it.deleteFromRealm() }
+
+ RoomChildRelationInfo(realm, lookedUp.roomId).getDirectChildrenDescriptions().forEach { child ->
+
+ lookedUp.children.add(
+ realm.createObject().apply {
+ this.childRoomId = child.roomId
+ this.childSummaryEntity = RoomSummaryEntity.where(realm, child.roomId).findFirst()
+ this.order = child.order
+ this.autoJoin = child.autoJoin
+ this.viaServers.addAll(child.viaServers)
+ }
+ )
+
+ RoomSummaryEntity.where(realm, child.roomId)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .findFirst()
+ ?.let { childSum ->
+ lookupMap.entries.firstOrNull { it.key.roomId == lookedUp.roomId }?.let { entry ->
+ if (entry.value.indexOfFirst { it.roomId == childSum.roomId } == -1) {
+ // add looked up as a parent
+ entry.value.add(childSum)
+ }
+ }
+ }
+ }
+ } else {
+ lookedUp.parents.clearWith { it.deleteFromRealm() }
+ // can we check parent relations here??
+ RoomChildRelationInfo(realm, lookedUp.roomId).getParentDescriptions()
+ .map { parentInfo ->
+
+ lookedUp.parents.add(
+ realm.createObject().apply {
+ this.parentRoomId = parentInfo.roomId
+ this.parentSummaryEntity = RoomSummaryEntity.where(realm, parentInfo.roomId).findFirst()
+ this.canonical = parentInfo.canonical
+ this.viaServers.addAll(parentInfo.viaServers)
+ }
+ )
+
+ RoomSummaryEntity.where(realm, parentInfo.roomId)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .findFirst()
+ ?.let { parentSum ->
+ if (lookupMap[parentSum]?.indexOfFirst { it.roomId == lookedUp.roomId } == -1) {
+ // add lookedup as a parent
+ lookupMap[parentSum]?.add(lookedUp)
+ }
+ }
+ }
+ }
+ }
+
+ // Simple algorithm to break cycles
+ // Need more work to decide how to break, probably need to be as consistent as possible
+ // and also find best way to root the tree
+
+ val graph = Graph()
+ lookupMap
+ // focus only on joined spaces, as room are just leaf
+ .filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN }
+ .forEach { (sum, children) ->
+ graph.getOrCreateNode(sum.roomId)
+ children.forEach {
+ graph.addEdge(it.roomId, sum.roomId)
+ }
+ }
+
+ val backEdges = graph.findBackwardEdges()
+ Timber.v("## SPACES: Cycle detected = ${backEdges.isNotEmpty()}")
+
+ // break cycles
+ backEdges.forEach { edge ->
+ lookupMap.entries.find { it.key.roomId == edge.source.name }?.let {
+ it.value.removeAll { it.roomId == edge.destination.name }
+ }
+ }
+
+ val acyclicGraph = graph.withoutEdges(backEdges)
+// Timber.v("## SPACES: acyclicGraph $acyclicGraph")
+ val flattenSpaceParents = acyclicGraph.flattenDestination().map {
+ it.key.name to it.value.map { it.name }
+ }.toMap()
+// Timber.v("## SPACES: flattenSpaceParents ${flattenSpaceParents.map { it.key.name to it.value.map { it.name } }.joinToString("\n") {
+// it.first + ": [" + it.second.joinToString(",") + "]"
+// }}")
+
+// Timber.v("## SPACES: lookup map ${lookupMap.map { it.key.name to it.value.map { it.name } }.toMap()}")
+
+ lookupMap.entries
+ .filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN }
+ .forEach { entry ->
+ val parent = RoomSummaryEntity.where(realm, entry.key.roomId).findFirst()
+ if (parent != null) {
+// Timber.v("## SPACES: check hierarchy of ${parent.name} id ${parent.roomId}")
+// Timber.v("## SPACES: flat known parents of ${parent.name} are ${flattenSpaceParents[parent.roomId]}")
+ val flattenParentsIds = (flattenSpaceParents[parent.roomId] ?: emptyList()) + listOf(parent.roomId)
+// Timber.v("## SPACES: flatten known parents of children of ${parent.name} are ${flattenParentsIds}")
+ entry.value.forEach { child ->
+ RoomSummaryEntity.where(realm, child.roomId).findFirst()?.let { childSum ->
+
+// Timber.w("## SPACES: ${childSum.name} is ${childSum.roomId} fc: ${childSum.flattenParentIds}")
+// var allParents = childSum.flattenParentIds ?: ""
+ if (childSum.flattenParentIds == null) childSum.flattenParentIds = ""
+ flattenParentsIds.forEach {
+ if (childSum.flattenParentIds?.contains(it) != true) {
+ childSum.flattenParentIds += "|$it"
+ }
+ }
+// childSum.flattenParentIds = "$allParents|"
+
+// Timber.v("## SPACES: flatten of ${childSum.name} is ${childSum.flattenParentIds}")
+ }
+ }
+ }
+ }
+
+ // we need also to filter DMs...
+ // it's more annoying as based on if the other members belong the space or not
+ RoomSummaryEntity.where(realm)
+ .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .findAll()
+ .forEach { dmRoom ->
+ val relatedSpaces = lookupMap.keys
+ .filter { it.roomType == RoomType.SPACE }
+ .filter {
+ dmRoom.otherMemberIds.toList().intersect(it.otherMemberIds.toList()).isNotEmpty()
+ }
+ .map { it.roomId }
+ .distinct()
+ val flattenRelated = mutableListOf().apply {
+ addAll(relatedSpaces)
+ relatedSpaces.map { flattenSpaceParents[it] }.forEach {
+ if (it != null) addAll(it)
+ }
+ }.distinct()
+ if (flattenRelated.isEmpty()) {
+ dmRoom.flattenParentIds = null
+ } else {
+ dmRoom.flattenParentIds = "|${flattenRelated.joinToString("|")}|"
+ }
+// Timber.v("## SPACES: flatten of ${dmRoom.otherMemberIds.joinToString(",")} is ${dmRoom.flattenParentIds}")
+ }
+
+ // Maybe a good place to count the number of notifications for spaces?
+
+ realm.where(RoomSummaryEntity::class.java)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .equalTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
+ .findAll().forEach { space ->
+ // get all children
+ var highlightCount = 0
+ var notificationCount = 0
+ realm.where(RoomSummaryEntity::class.java)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, listOf(Membership.JOIN))
+ .notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
+ // also we do not count DM in here, because home space will already show them
+ .equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
+ .contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, space.roomId)
+ .findAll().forEach {
+ highlightCount += it.highlightCount
+ notificationCount += it.notificationCount
+ }
+
+ space.highlightCount = highlightCount
+ space.notificationCount = notificationCount
+ }
+ // xxx invites??
+
+ // LEGACY GROUPS
+ // lets mark rooms that belongs to groups
+ val existingGroups = GroupSummaryEntity.where(realm).findAll()
+
+ // For rooms
+ realm.where(RoomSummaryEntity::class.java)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
+ .findAll().forEach { room ->
+ val belongsTo = existingGroups.filter { it.roomIds.contains(room.roomId) }
+ room.groupIds = if (belongsTo.isEmpty()) {
+ null
+ } else {
+ "|${belongsTo.joinToString("|")}|"
+ }
+ }
+
+ // For DMS
+ realm.where(RoomSummaryEntity::class.java)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+ .findAll().forEach { room ->
+ val belongsTo = existingGroups.filter {
+ it.userIds.intersect(room.otherMemberIds).isNotEmpty()
+ }
+ room.groupIds = if (belongsTo.isEmpty()) {
+ null
+ } else {
+ "|${belongsTo.joinToString("|")}|"
+ }
+ }
+ }.also {
+ Timber.v("## SPACES: Finish checking room hierarchy in $it ms")
+ }
+ }
+
+// private fun isValidCanonical() : Boolean {
+//
+// }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
index c38dcd00a7..a7cba2fe99 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
@@ -149,7 +149,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
}
?: ChunkEntity.create(realm, prevToken, nextToken)
- if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) {
+ if (receivedChunk.events.isNullOrEmpty() && !receivedChunk.hasMore()) {
handleReachEnd(realm, roomId, direction, currentChunk)
} else {
handlePagination(realm, roomId, direction, receivedChunk, currentChunk)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
new file mode 100644
index 0000000000..70c52bf4ae
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.space
+
+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.toContent
+import org.matrix.android.sdk.api.session.events.model.toModel
+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.space.Space
+import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
+import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
+import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
+
+internal class DefaultSpace(
+ private val room: Room,
+ private val spaceSummaryDataSource: RoomSummaryDataSource,
+ private val viaParameterFinder: ViaParameterFinder
+) : Space {
+
+ override fun asRoom(): Room {
+ return room
+ }
+
+ override val spaceId = room.roomId
+
+ override suspend fun leave(reason: String?) {
+ return room.leave(reason)
+ }
+
+ override fun spaceSummary(): RoomSummary? {
+ return spaceSummaryDataSource.getSpaceSummary(room.roomId)
+ }
+
+ override suspend fun addChildren(roomId: String,
+ viaServers: List?,
+ order: String?,
+ autoJoin: Boolean,
+ suggested: Boolean?) {
+ // Find best via
+
+ room.sendStateEvent(
+ eventType = EventType.STATE_SPACE_CHILD,
+ stateKey = roomId,
+ body = SpaceChildContent(
+ via = viaServers ?: viaParameterFinder.computeViaParams(roomId, 3),
+ autoJoin = autoJoin,
+ order = order,
+ suggested = suggested
+ ).toContent()
+ )
+ }
+
+ override suspend fun removeChildren(roomId: String) {
+// val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+// .firstOrNull()
+// ?.content.toModel()
+// ?: // should we throw here?
+// return
+
+ // edit state event and set via to null
+ room.sendStateEvent(
+ eventType = EventType.STATE_SPACE_CHILD,
+ stateKey = roomId,
+ body = SpaceChildContent(
+ order = null,
+ via = null,
+ autoJoin = null,
+ suggested = null
+ ).toContent()
+ )
+ }
+
+ override suspend fun setChildrenOrder(roomId: String, order: String?) {
+ val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+ .firstOrNull()
+ ?.content.toModel()
+ ?: throw IllegalArgumentException("$roomId is not a child of this space")
+
+ // edit state event and set via to null
+ room.sendStateEvent(
+ eventType = EventType.STATE_SPACE_CHILD,
+ stateKey = roomId,
+ body = SpaceChildContent(
+ order = order,
+ via = existing.via,
+ autoJoin = existing.autoJoin,
+ suggested = existing.suggested
+ ).toContent()
+ )
+ }
+
+ override suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) {
+ val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+ .firstOrNull()
+ ?.content.toModel()
+ ?: throw IllegalArgumentException("$roomId is not a child of this space")
+
+ if (existing.autoJoin == autoJoin) {
+ // nothing to do?
+ return
+ }
+
+ // edit state event and set via to null
+ room.sendStateEvent(
+ eventType = EventType.STATE_SPACE_CHILD,
+ stateKey = roomId,
+ body = SpaceChildContent(
+ order = existing.order,
+ via = existing.via,
+ autoJoin = autoJoin,
+ suggested = existing.suggested
+ ).toContent()
+ )
+ }
+
+ override suspend fun setChildrenSuggested(roomId: String, suggested: Boolean) {
+ val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+ .firstOrNull()
+ ?.content.toModel()
+ ?: throw IllegalArgumentException("$roomId is not a child of this space")
+
+ if (existing.suggested == suggested) {
+ // nothing to do?
+ return
+ }
+ // edit state event and set via to null
+ room.sendStateEvent(
+ eventType = EventType.STATE_SPACE_CHILD,
+ stateKey = roomId,
+ body = SpaceChildContent(
+ order = existing.order,
+ via = existing.via,
+ autoJoin = existing.autoJoin,
+ suggested = suggested
+ ).toContent()
+ )
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
new file mode 100644
index 0000000000..f7fd77c528
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.space
+
+import android.net.Uri
+import androidx.lifecycle.LiveData
+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.toContent
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.GuestAccess
+import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
+import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
+import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
+import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
+import org.matrix.android.sdk.api.session.space.CreateSpaceParams
+import org.matrix.android.sdk.api.session.space.JoinSpaceResult
+import org.matrix.android.sdk.api.session.space.Space
+import org.matrix.android.sdk.api.session.space.SpaceService
+import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
+import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
+import org.matrix.android.sdk.api.session.space.model.SpaceParentContent
+import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.session.room.RoomGetter
+import org.matrix.android.sdk.internal.session.room.SpaceGetter
+import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
+import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask
+import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
+import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
+import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
+import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
+import javax.inject.Inject
+
+internal class DefaultSpaceService @Inject constructor(
+ @UserId private val userId: String,
+ private val createRoomTask: CreateRoomTask,
+ private val joinSpaceTask: JoinSpaceTask,
+ private val spaceGetter: SpaceGetter,
+ private val roomGetter: RoomGetter,
+ private val roomSummaryDataSource: RoomSummaryDataSource,
+ private val stateEventDataSource: StateEventDataSource,
+ private val peekSpaceTask: PeekSpaceTask,
+ private val resolveSpaceInfoTask: ResolveSpaceInfoTask,
+ private val leaveRoomTask: LeaveRoomTask
+) : SpaceService {
+
+ override suspend fun createSpace(params: CreateSpaceParams): String {
+ return createRoomTask.executeRetry(params, 3)
+ }
+
+ override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String {
+ return createSpace(CreateSpaceParams().apply {
+ this.name = name
+ this.topic = topic
+ this.avatarUri = avatarUri
+ if (isPublic) {
+ this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy(
+ invite = 0
+ )
+ this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
+ this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE
+ this.guestAccess = GuestAccess.CanJoin
+ } else {
+ this.preset = CreateRoomPreset.PRESET_PRIVATE_CHAT
+ visibility = RoomDirectoryVisibility.PRIVATE
+ enableEncryption()
+ }
+ })
+ }
+
+ override fun getSpace(spaceId: String): Space? {
+ return spaceGetter.get(spaceId)
+ }
+
+ override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> {
+ return roomSummaryDataSource.getSpaceSummariesLive(queryParams)
+ }
+
+ override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List {
+ return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams)
+ }
+
+ override fun getRootSpaceSummaries(): List {
+ return roomSummaryDataSource.getRootSpaceSummaries()
+ }
+
+ override suspend fun peekSpace(spaceId: String): SpacePeekResult {
+ return peekSpaceTask.execute(PeekSpaceTask.Params(spaceId))
+ }
+
+ override suspend fun querySpaceChildren(spaceId: String,
+ suggestedOnly: Boolean?,
+ autoJoinedOnly: Boolean?): Pair> {
+ return resolveSpaceInfoTask.execute(ResolveSpaceInfoTask.Params.withId(spaceId, suggestedOnly, autoJoinedOnly)).let { response ->
+ val spaceDesc = response.rooms?.firstOrNull { it.roomId == spaceId }
+ Pair(
+ first = RoomSummary(
+ roomId = spaceDesc?.roomId ?: spaceId,
+ roomType = spaceDesc?.roomType,
+ name = spaceDesc?.name ?: "",
+ displayName = spaceDesc?.name ?: "",
+ topic = spaceDesc?.topic ?: "",
+ joinedMembersCount = spaceDesc?.numJoinedMembers,
+ avatarUrl = spaceDesc?.avatarUrl ?: "",
+ encryptionEventTs = null,
+ typingUsers = emptyList(),
+ isEncrypted = false,
+ flattenParentIds = emptyList()
+ ),
+ second = response.rooms
+ ?.filter { it.roomId != spaceId }
+ ?.flatMap { childSummary ->
+ response.events
+ ?.filter { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD }
+ ?.mapNotNull { childStateEv ->
+ // create a child entry for everytime this room is the child of a space
+ // beware that a room could appear then twice in this list
+ childStateEv.content.toModel()?.let { childStateEvContent ->
+ SpaceChildInfo(
+ childRoomId = childSummary.roomId,
+ isKnown = true,
+ roomType = childSummary.roomType,
+ name = childSummary.name,
+ topic = childSummary.topic,
+ avatarUrl = childSummary.avatarUrl,
+ order = childStateEvContent.order,
+ autoJoin = childStateEvContent.autoJoin ?: false,
+ viaServers = childStateEvContent.via.orEmpty(),
+ activeMemberCount = childSummary.numJoinedMembers,
+ parentRoomId = childStateEv.roomId,
+ suggested = childStateEvContent.suggested
+ )
+ }
+ }.orEmpty()
+ }
+ .orEmpty()
+ )
+ }
+ }
+
+ override suspend fun joinSpace(spaceIdOrAlias: String,
+ reason: String?,
+ viaServers: List): JoinSpaceResult {
+ return joinSpaceTask.execute(JoinSpaceTask.Params(spaceIdOrAlias, reason, viaServers))
+ }
+
+ override suspend fun rejectInvite(spaceId: String, reason: String?) {
+ leaveRoomTask.execute(LeaveRoomTask.Params(spaceId, reason))
+ }
+
+// override fun getSpaceParentsOfRoom(roomId: String): List {
+// return spaceSummaryDataSource.getParentsOfRoom(roomId)
+// }
+
+ override suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List) {
+ // Should we perform some validation here?,
+ // and if client want to bypass, it could use sendStateEvent directly?
+ if (canonical) {
+ // check that we can send m.child in the parent room
+ if (roomSummaryDataSource.getRoomSummary(parentSpaceId)?.membership != Membership.JOIN) {
+ throw UnsupportedOperationException("Cannot add canonical child if not member of parent")
+ }
+ val powerLevelsEvent = stateEventDataSource.getStateEvent(
+ roomId = parentSpaceId,
+ eventType = EventType.STATE_ROOM_POWER_LEVELS,
+ stateKey = QueryStringValue.NoCondition
+ )
+ val powerLevelsContent = powerLevelsEvent?.content?.toModel()
+ ?: throw UnsupportedOperationException("Cannot add canonical child, missing powerlevel")
+ val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent)
+ if (!powerLevelsHelper.isUserAllowedToSend(userId, true, EventType.STATE_SPACE_CHILD)) {
+ throw UnsupportedOperationException("Cannot add canonical child, not enough power level")
+ }
+ }
+
+ val room = roomGetter.getRoom(childRoomId)
+ ?: throw IllegalArgumentException("Unknown Room $childRoomId")
+
+ room.sendStateEvent(
+ eventType = EventType.STATE_SPACE_PARENT,
+ stateKey = parentSpaceId,
+ body = SpaceParentContent(
+ via = viaServers,
+ canonical = canonical
+ ).toContent()
+ )
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt
new file mode 100644
index 0000000000..5e1b829249
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.space
+
+import io.realm.RealmConfiguration
+import kotlinx.coroutines.TimeoutCancellationException
+import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.RoomType
+import org.matrix.android.sdk.api.session.space.JoinSpaceResult
+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.session.room.membership.joining.JoinRoomTask
+import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
+import org.matrix.android.sdk.internal.task.Task
+import timber.log.Timber
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+internal interface JoinSpaceTask : Task {
+ data class Params(
+ val roomIdOrAlias: String,
+ val reason: String?,
+ val viaServers: List = emptyList()
+ )
+}
+
+internal class DefaultJoinSpaceTask @Inject constructor(
+ private val joinRoomTask: JoinRoomTask,
+ @SessionDatabase
+ private val realmConfiguration: RealmConfiguration,
+ private val roomSummaryDataSource: RoomSummaryDataSource
+) : JoinSpaceTask {
+
+ override suspend fun execute(params: JoinSpaceTask.Params): JoinSpaceResult {
+ Timber.v("## Space: > Joining root space ${params.roomIdOrAlias} ...")
+ try {
+ joinRoomTask.execute(JoinRoomTask.Params(
+ params.roomIdOrAlias,
+ params.reason,
+ params.viaServers
+ ))
+ } catch (failure: Throwable) {
+ return JoinSpaceResult.Fail(failure)
+ }
+ Timber.v("## Space: < Joining root space done for ${params.roomIdOrAlias}")
+ // we want to wait for sync result to check for auto join rooms
+
+ Timber.v("## Space: > Wait for post joined sync ${params.roomIdOrAlias} ...")
+ try {
+ awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(2L)) { realm ->
+ realm.where(RoomSummaryEntity::class.java)
+ .apply {
+ if (params.roomIdOrAlias.startsWith("!")) {
+ equalTo(RoomSummaryEntityFields.ROOM_ID, params.roomIdOrAlias)
+ } else {
+ equalTo(RoomSummaryEntityFields.CANONICAL_ALIAS, params.roomIdOrAlias)
+ }
+ }
+ .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
+ }
+ } catch (exception: TimeoutCancellationException) {
+ Timber.w("## Space: > Error created with timeout")
+ return JoinSpaceResult.PartialSuccess(emptyMap())
+ }
+
+ val errors = mutableMapOf()
+ Timber.v("## Space: > Sync done ...")
+ // after that i should have the children (? do I need to paginate to get state)
+ val summary = roomSummaryDataSource.getSpaceSummary(params.roomIdOrAlias)
+ Timber.v("## Space: Found space summary Name:[${summary?.name}] children: ${summary?.spaceChildren?.size}")
+ summary?.spaceChildren?.forEach {
+// val childRoomSummary = it.roomSummary ?: return@forEach
+ Timber.v("## Space: Processing child :[${it.childRoomId}] autoJoin:${it.autoJoin}")
+ if (it.autoJoin) {
+ // I should try to join as well
+ if (it.roomType == RoomType.SPACE) {
+ // recursively join auto-joined child of this space?
+ when (val subspaceJoinResult = execute(JoinSpaceTask.Params(it.childRoomId, null, it.viaServers))) {
+ JoinSpaceResult.Success -> {
+ // nop
+ }
+ is JoinSpaceResult.Fail -> {
+ errors[it.childRoomId] = subspaceJoinResult.error
+ }
+ is JoinSpaceResult.PartialSuccess -> {
+ errors.putAll(subspaceJoinResult.failedRooms)
+ }
+ }
+ } else {
+ try {
+ Timber.v("## Space: Joining room child ${it.childRoomId}")
+ joinRoomTask.execute(JoinRoomTask.Params(
+ roomIdOrAlias = it.childRoomId,
+ reason = "Auto-join parent space",
+ viaServers = it.viaServers
+ ))
+ } catch (failure: Throwable) {
+ errors[it.childRoomId] = failure
+ Timber.e("## Space: Failed to join room child ${it.childRoomId}")
+ }
+ }
+ }
+ }
+
+ return if (errors.isEmpty()) {
+ JoinSpaceResult.Success
+ } else {
+ JoinSpaceResult.PartialSuccess(errors)
+ }
+ }
+}
+
+// try {
+// awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
+// realm.where(RoomEntity::class.java)
+// .equalTo(RoomEntityFields.ROOM_ID, roomId)
+// }
+// } catch (exception: TimeoutCancellationException) {
+// throw CreateRoomFailure.CreatedWithTimeout
+// }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt
new file mode 100644
index 0000000000..d2be49b70b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.space
+
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal interface ResolveSpaceInfoTask : Task {
+ data class Params(
+ val spaceId: String,
+ val maxRoomPerSpace: Int?,
+ val limit: Int,
+ val batchToken: String?,
+ val suggestedOnly: Boolean?,
+ val autoJoinOnly: Boolean?
+ ) {
+ companion object {
+ fun withId(spaceId: String, suggestedOnly: Boolean?, autoJoinOnly: Boolean?) =
+ Params(
+ spaceId = spaceId,
+ maxRoomPerSpace = 10,
+ limit = 20,
+ batchToken = null,
+ suggestedOnly = suggestedOnly,
+ autoJoinOnly = autoJoinOnly
+ )
+ }
+ }
+}
+
+internal class DefaultResolveSpaceInfoTask @Inject constructor(
+ private val spaceApi: SpaceApi,
+ private val globalErrorReceiver: GlobalErrorReceiver
+) : ResolveSpaceInfoTask {
+ override suspend fun execute(params: ResolveSpaceInfoTask.Params): SpacesResponse {
+ val body = SpaceSummaryParams(
+ maxRoomPerSpace = params.maxRoomPerSpace,
+ limit = params.limit,
+ batch = params.batchToken ?: "",
+ autoJoinedOnly = params.autoJoinOnly,
+ suggestedOnly = params.suggestedOnly
+ )
+ return executeRequest(globalErrorReceiver) {
+ spaceApi.getSpaces(params.spaceId, body)
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt
new file mode 100644
index 0000000000..0fcc95fdb3
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.space
+
+import org.matrix.android.sdk.internal.network.NetworkConstants
+import retrofit2.http.Body
+import retrofit2.http.POST
+import retrofit2.http.Path
+
+internal interface SpaceApi {
+
+ /**
+ *
+ * POST /_matrix/client/r0/rooms/{roomID}/spaces
+ * {
+ * "max_rooms_per_space": 5, // The maximum number of rooms/subspaces to return for a given space, if negative unbounded. default: -1.
+ * "auto_join_only": true, // If true, only return m.space.child events with auto_join:true, default: false, which returns all events.
+ * "limit": 100, // The maximum number of rooms/subspaces to return, server can override this, default: 100.
+ * "batch": "opaque_string" // A token to use if this is a subsequent HTTP hit, default: "".
+ * }
+ *
+ * Ref:
+ * - MSC 2946 https://github.com/matrix-org/matrix-doc/blob/kegan/spaces-summary/proposals/2946-spaces-summary.md
+ * - https://hackmd.io/fNYh4tjUT5mQfR1uuRzWDA
+ */
+ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/spaces")
+ suspend fun getSpaces(@Path("roomId") spaceId: String,
+ @Body params: SpaceSummaryParams): SpacesResponse
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt
new file mode 100644
index 0000000000..5021ff638f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.space
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class SpaceChildSummaryResponse(
+ /**
+ * The total number of state events which point to or from this room (inbound/outbound edges).
+ * This includes all m.space.child events in the room, in addition to m.room.parent events which point to this room as a parent.
+ */
+ @Json(name = "num_refs") val numRefs: Int? = null,
+
+ /**
+ * The room type, which is m.space for subspaces.
+ * It can be omitted if there is no room type in which case it should be interpreted as a normal room.
+ */
+ @Json(name = "room_type") val roomType: String? = null,
+
+ /**
+ * Aliases of the room. May be empty.
+ */
+ @Json(name = "aliases")
+ val aliases: List? = null,
+
+ /**
+ * The canonical alias of the room, if any.
+ */
+ @Json(name = "canonical_alias")
+ val canonicalAlias: String? = null,
+
+ /**
+ * The name of the room, if any.
+ */
+ @Json(name = "name")
+ val name: String? = null,
+
+ /**
+ * Required. The number of members joined to the room.
+ */
+ @Json(name = "num_joined_members")
+ val numJoinedMembers: Int = 0,
+
+ /**
+ * Required. The ID of the room.
+ */
+ @Json(name = "room_id")
+ val roomId: String,
+
+ /**
+ * The topic of the room, if any.
+ */
+ @Json(name = "topic")
+ val topic: String? = null,
+
+ /**
+ * Required. Whether the room may be viewed by guest users without joining.
+ */
+ @Json(name = "world_readable")
+ val worldReadable: Boolean = false,
+
+ /**
+ * Required. Whether guest users may join the room and participate in it. If they can,
+ * they will be subject to ordinary power level rules like any other user.
+ */
+ @Json(name = "guest_can_join")
+ val guestCanJoin: Boolean = false,
+
+ /**
+ * The URL for the room's avatar, if one is set.
+ */
+ @Json(name = "avatar_url")
+ val avatarUrl: String? = null,
+
+ /**
+ * Undocumented item
+ */
+ @Json(name = "m.federate")
+ val isFederated: Boolean = false
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt
new file mode 100644
index 0000000000..87425f4af2
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.space
+
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.session.room.DefaultSpaceGetter
+import org.matrix.android.sdk.internal.session.room.SpaceGetter
+import org.matrix.android.sdk.internal.session.space.peeking.DefaultPeekSpaceTask
+import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
+import retrofit2.Retrofit
+
+@Module
+internal abstract class SpaceModule {
+
+ @Module
+ companion object {
+ @Provides
+ @JvmStatic
+ @SessionScope
+ fun providesSpacesAPI(retrofit: Retrofit): SpaceApi {
+ return retrofit.create(SpaceApi::class.java)
+ }
+ }
+
+ @Binds
+ abstract fun bindResolveSpaceTask(task: DefaultResolveSpaceInfoTask): ResolveSpaceInfoTask
+
+ @Binds
+ abstract fun bindPeekSpaceTask(task: DefaultPeekSpaceTask): PeekSpaceTask
+
+ @Binds
+ abstract fun bindJoinSpaceTask(task: DefaultJoinSpaceTask): JoinSpaceTask
+
+ @Binds
+ abstract fun bindSpaceGetter(getter: DefaultSpaceGetter): SpaceGetter
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt
new file mode 100644
index 0000000000..013db1c286
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.space
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class SpaceSummaryParams(
+ /** The maximum number of rooms/subspaces to return for a given space, if negative unbounded. default: -1 */
+ @Json(name = "max_rooms_per_space") val maxRoomPerSpace: Int?,
+ /** The maximum number of rooms/subspaces to return, server can override this, default: 100 */
+ @Json(name = "limit") val limit: Int?,
+ /** A token to use if this is a subsequent HTTP hit, default: "". */
+ @Json(name = "batch") val batch: String = "",
+ /** whether we should only return children with the "suggested" flag set. */
+ @Json(name = "suggested_only") val suggestedOnly: Boolean?,
+ /** whether we should only return children with the "suggested" flag set. */
+ @Json(name = "auto_join_only") val autoJoinedOnly: Boolean?
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt
new file mode 100644
index 0000000000..20d63c8814
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.space
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.session.events.model.Event
+
+@JsonClass(generateAdapter = true)
+internal data class SpacesResponse(
+ /** Its presence indicates that there are more results to return. */
+ @Json(name = "next_batch") val nextBatch: String? = null,
+ /** Rooms information like name/avatar/type ... */
+ @Json(name = "rooms") val rooms: List? = null,
+ /** These are the edges of the graph. The objects in the array are complete (or stripped?) m.room.parent or m.space.child events. */
+ @Json(name = "events") val events: List? = null
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt
new file mode 100644
index 0000000000..f6b156a6fb
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.space.peeking
+
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.RoomType
+import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
+import org.matrix.android.sdk.api.session.room.peeking.PeekResult
+import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
+import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
+import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
+import org.matrix.android.sdk.internal.task.Task
+import timber.log.Timber
+import javax.inject.Inject
+
+internal interface PeekSpaceTask : Task {
+ data class Params(
+ val roomIdOrAlias: String,
+ // A depth limit as a simple protection against cycles
+ val maxDepth: Int = 4
+ )
+}
+
+internal class DefaultPeekSpaceTask @Inject constructor(
+ private val peekRoomTask: PeekRoomTask,
+ private val resolveRoomStateTask: ResolveRoomStateTask
+) : PeekSpaceTask {
+
+ override suspend fun execute(params: PeekSpaceTask.Params): SpacePeekResult {
+ val peekResult = peekRoomTask.execute(PeekRoomTask.Params(params.roomIdOrAlias))
+ val roomResult = peekResult as? PeekResult.Success ?: return SpacePeekResult.FailedToResolve(params.roomIdOrAlias, peekResult)
+
+ // check the room type
+ // kind of duplicate cause we already did it in Peek? could we pass on the result??
+ val stateEvents = try {
+ resolveRoomStateTask.execute(ResolveRoomStateTask.Params(roomResult.roomId))
+ } catch (failure: Throwable) {
+ return SpacePeekResult.FailedToResolve(params.roomIdOrAlias, peekResult)
+ }
+ val isSpace = stateEvents
+ .lastOrNull { it.type == EventType.STATE_ROOM_CREATE && it.stateKey == "" }
+ ?.content
+ ?.toModel()
+ ?.type == RoomType.SPACE
+
+ if (!isSpace) return SpacePeekResult.NotSpaceType(params.roomIdOrAlias)
+
+ val children = peekChildren(stateEvents, 0, params.maxDepth)
+
+ return SpacePeekResult.Success(
+ SpacePeekSummary(
+ params.roomIdOrAlias,
+ peekResult,
+ children
+ )
+ )
+ }
+
+ private suspend fun peekChildren(stateEvents: List, depth: Int, maxDepth: Int): List {
+ if (depth >= maxDepth) return emptyList()
+ val childRoomsIds = stateEvents
+ .filter {
+ it.type == EventType.STATE_SPACE_CHILD && !it.stateKey.isNullOrEmpty()
+ // Children where via is not present are ignored.
+ && it.content?.toModel()?.via != null
+ }
+ .map { it.stateKey to it.content?.toModel() }
+
+ Timber.v("## SPACE_PEEK: found ${childRoomsIds.size} present children")
+
+ val spaceChildResults = mutableListOf()
+ childRoomsIds.forEach { entry ->
+
+ Timber.v("## SPACE_PEEK: peeking child $entry")
+ // peek each child
+ val childId = entry.first ?: return@forEach
+ try {
+ val childPeek = peekRoomTask.execute(PeekRoomTask.Params(childId))
+
+ val childStateEvents = resolveRoomStateTask.execute(ResolveRoomStateTask.Params(childId))
+ val createContent = childStateEvents
+ .lastOrNull { it.type == EventType.STATE_ROOM_CREATE && it.stateKey == "" }
+ ?.let { it.content?.toModel() }
+
+ if (!childPeek.isSuccess() || createContent == null) {
+ Timber.v("## SPACE_PEEK: cannot peek child $entry")
+ // can't peek :/
+ spaceChildResults.add(
+ SpaceChildPeekResult(
+ childId, childPeek, entry.second?.autoJoin, entry.second?.order
+ )
+ )
+ // continue to next child
+ return@forEach
+ }
+ val type = createContent.type
+ if (type == RoomType.SPACE) {
+ Timber.v("## SPACE_PEEK: subspace child $entry")
+ spaceChildResults.add(
+ SpaceSubChildPeekResult(
+ childId,
+ childPeek,
+ entry.second?.autoJoin,
+ entry.second?.order,
+ peekChildren(childStateEvents, depth + 1, maxDepth)
+ )
+ )
+ } else
+ /** if (type == RoomType.MESSAGING || type == null)*/
+ {
+ Timber.v("## SPACE_PEEK: room child $entry")
+ spaceChildResults.add(
+ SpaceChildPeekResult(
+ childId, childPeek, entry.second?.autoJoin, entry.second?.order
+ )
+ )
+ }
+
+ // let's check child info
+ } catch (failure: Throwable) {
+ // can this happen?
+ Timber.e(failure, "## Failed to resolve space child")
+ }
+ }
+ return spaceChildResults
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt
new file mode 100644
index 0000000000..1df62e94e8
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.space.peeking
+
+import org.matrix.android.sdk.api.session.room.peeking.PeekResult
+
+// TODO Move to api package
+data class SpacePeekSummary(
+ val idOrAlias: String,
+ val roomPeekResult: PeekResult.Success,
+ val children: List
+)
+
+interface ISpaceChild {
+ val id: String
+ val roomPeekResult: PeekResult
+ val default: Boolean?
+ val order: String?
+}
+
+data class SpaceChildPeekResult(
+ override val id: String,
+ override val roomPeekResult: PeekResult,
+ override val default: Boolean? = null,
+ override val order: String? = null
+) : ISpaceChild
+
+data class SpaceSubChildPeekResult(
+ override val id: String,
+ override val roomPeekResult: PeekResult,
+ override val default: Boolean?,
+ override val order: String?,
+ val children: List
+) : ISpaceChild
+
+sealed class SpacePeekResult {
+ abstract class SpacePeekError : SpacePeekResult()
+ data class FailedToResolve(val spaceId: String, val roomPeekResult: PeekResult) : SpacePeekError()
+ data class NotSpaceType(val spaceId: String) : SpacePeekError()
+
+ data class Success(val summary: SpacePeekSummary): SpacePeekResult()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt
index e5d9217db7..fc1a2c3870 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt
@@ -143,7 +143,7 @@ internal class ReadReceiptHandler @Inject constructor(
@Suppress("UNCHECKED_CAST")
val content = dataFromFile
.events
- .firstOrNull { it.type == EventType.RECEIPT }
+ ?.firstOrNull { it.type == EventType.RECEIPT }
?.content as? ReadReceiptContent
if (content == null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
index 2bb606e921..7cebbb0192 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
@@ -95,8 +95,14 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, aggregator, reporter)
handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, aggregator, reporter)
handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, aggregator, reporter)
+
+ // post room sync validation
+// roomSummaryUpdater.validateSpaceRelationship(realm)
}
+ fun postSyncSpaceHierarchyHandle(realm: Realm) {
+ roomSummaryUpdater.validateSpaceRelationship(realm)
+ }
// PRIVATE METHODS *****************************************************************************
private fun handleRoomSync(realm: Realm,
@@ -212,6 +218,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
+ // Timber.v("## Space state event: $eventEntity")
eventId = event.eventId
root = eventEntity
}
@@ -455,7 +462,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
}
private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
- for (event in accountData.events) {
+ accountData.events?.forEach { event ->
val eventType = event.getClearType()
if (eventType == EventType.TAG) {
val content = event.getClearContent().toModel