Merge tag 'v1.1.7' into sc

Change-Id: Icefe721894c7cd3d0702d07efbd0452f95775d32

Conflicts:
	.idea/codeStyles/Project.xml
	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
	vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt
	vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt
	vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt
	vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt
	vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
	vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
	vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt
	vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt
	vector/src/main/res/layout/item_timeline_event_base.xml
	vector/src/main/res/values-ca/strings.xml
	vector/src/main/res/values-cs/strings.xml
	vector/src/main/res/values-de/strings.xml
	vector/src/main/res/values-et/strings.xml
	vector/src/main/res/values-fa/strings.xml
	vector/src/main/res/values-fr/strings.xml
	vector/src/main/res/values-it/strings.xml
	vector/src/main/res/values-sq/strings.xml
	vector/src/main/res/values-sv/strings.xml
	vector/src/main/res/values-vi/strings.xml
	vector/src/main/res/values-zh-rTW/strings.xml
	vector/src/main/res/values/strings.xml
	vector/src/main/res/xml/vector_settings_labs.xml
This commit is contained in:
SpiritCroc 2021-05-12 16:01:36 +02:00
commit db6cfd3054
314 changed files with 13596 additions and 1747 deletions

View File

@ -4,7 +4,7 @@
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="kotlinx.android.synthetic" withSubpackages="true" static="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
</value>
</option>
<option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" />
@ -153,4 +153,4 @@
<option name="FIELD_ANNOTATION_WRAP" value="1" />
</codeStyleSettings>
</code_scheme>
</component>
</component>

View File

@ -1,4 +1,4 @@
Changes in Element 1.1.7 (2021-XX-XX)
Changes in Element 1.1.7 (2021-05-12)
===================================================
Features ✨:
@ -7,6 +7,11 @@ Features ✨:
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)
@ -17,18 +22,19 @@ Bugfix 🐛:
- Fix issue when opening encrypted files (#3186)
- Fix wording issue (#3242)
- Fix missing sender information after edits (#3184)
Translations 🗣:
-
- 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
Test:
-
- https://github.com/Piasy/BigImageViewer is now hosted on mavenCentral()
- Upgrade Realm to version 10.4.0
Other changes:
- New store descriptions

View File

@ -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

View File

@ -45,17 +45,11 @@ allprojects {
// PFLockScreen-Android
includeGroupByRegex 'com\\.github\\.vector-im'
//Chat effects
// Chat effects
includeGroupByRegex 'com\\.github\\.jetradarmobile'
includeGroupByRegex 'nl\\.dionsegijn'
}
}
maven {
url "https://dl.bintray.com/piasy/maven"
content {
includeGroupByRegex "com\\.github\\.piasy"
}
}
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
// Jitsi repo
maven {
@ -64,6 +58,7 @@ allprojects {
// url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.1.0"
}
google()
mavenCentral()
jcenter()
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1 +1 @@
Zabezpečený decentralizovaný chat a VoIP. Uchovejte svá data v bezpečí.
Skupinový messenger - šifrovaná komunikace, skupinový chat a video hovory

View File

@ -1 +1 @@
Element (dříve Riot.im)
Element - bezpečný messenger

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
<b>Element enthält folgende Funktionen:</b>
- 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.
<b>Privatsphäre/Datenschutz und verschlüsselte Kommunikation</b>
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:
<b>Element kann man selber betreiben</b>
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.
<b>Besitzen Sie Ihre Daten</b>
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)
<b>Wieso Element nutzen?</b>
<b>Offene Kommunikation und Zusammenarbeit</b>
Sie können mit jeden auf dem Matrix-Netzwerk chatten, egal ob sie Element, eine Matrix-App oder sogar eine andere Kommunikationsapp nutzen.
<b>BESITZE DEINE DATEN</b>: 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.
<b>Super sicher</b>
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.
<b>OFFENE KOMMUNIKATION UND KOLLABORATION</b>: 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.
<b>Vollständige Kommunikation und Integration</b>
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.
<b>SUPER SICHER</b>: 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.
<b>VOLLSTÄNDIGE KOMMUNIKATION</b>: 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.
<b>ÜBERALL WO DU BIST</b>: 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.
<b>Das Stehengelassene später wieder aufgreifen</b>
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

View File

@ -1 +1 @@
Sicherer dezentraler Chat und Telefonie. Schütze deine Daten vor Dritten.
Gruppen-Messenger - verschlüsselte Kommunikation, Gruppenchat und Videoanrufe

View File

@ -1 +1 @@
Element (zuvor Riot.im)
Element - Sicherer Messenger

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
<b>Element pakub muu hulgas selliseid võimalusi</b>
- 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.
<b>Privaatsus ja krüptitud sõnumivahetus</b>
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.
<b>Võid kasutada Element'i jaoks oma serverid</b>
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
<b>Andmed on Sinu omad</b>
Sina otsustad seda, kus hoiad oma sõnumeid ja andmeid. Ning seejuures puudub andmekaevandamise risk ja ligipääs kolmandatele osapooltele.
<b> Miks valida element? </b>
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
<b> KONTROLL ANDMETE ÜLE</b>: 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.
<b>Avatud suhtlus ja koostöö</b>
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.
<b> AVATUD SUHTLUS JA KOOSTÖÖ </b>: 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.
<b>Üliturvaline</b>
Tõeline läbiv krüptimine (ainult vestluses osalejad saavad sõnumeid lugeda) ja risttunnustamine vestluses osalejate tuvastamiseks.
<b> ÜLITURVALINE </b>: tõeline läbiv krüptimine (ainult vestluses osalejad saavad sõnumeid lugeda) ja risttunnustamine vestluses osalejate tuvastamiseks.
<b>Kõik suhtlusvõimalused</b>
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.
<b> KÕIK SUHTLUSVÕIMALUSED</b>: 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.
<b> KÕIKJAL, KUS VIIBITE</b>: saad suhelda kõigis oma seadmetes ja ka veebis aadressil https://app.element.io ning sealjuures täielikult sünkroonitud sõnumite ajalooga.
<b>Jätka sealt, kus pooleli jäid</b>
Saad suhelda kõigis oma seadmetes ja ka veebis aadressil https://app.element.io ning sealjuures täielikult sünkroonitud sõnumite ajalooga.

View File

@ -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

View File

@ -1 +1 @@
Element (varem Riot.im)
Element - turvaline sõnumiklient

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1 +1 @@
Element (anciennement Riot.im)
Element - Messagerie sécurisée

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,2 @@
Haadferoaring yn disse ferzje: Breksoplossings!
Folsleine feroaringslist: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
Groepsberjochtetsjinst - fersifere berjochten, groeps petearen en fideo skilje

View File

@ -0,0 +1 @@
Element - Feilige Berjochtetsjinst

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
<b>Tra le caratteristiche di Element ci sono:</b>
- 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.
<b>Privacy e messaggi privati</b>
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:
<b>Element può essere installato in locale</b>
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.
<b>Possiedi i tuoi dati</b>
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
<b>Perchè scegliere Element?</b>
<b>Messaggistica e collaborazioni aperte</b>
Puoi chattare con chiunque nella rete Matrix, sia che stiano usando Element, un'altra app Matrix, o anche un'app di messaggistica diversa.
<b>POSSIEDI I TUOI DATI</b>: 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.
<b>Super sicuro</b>
Vera crittografia end-to-end (solo chi è nella conversazione può decifrare i messaggi) e verifica di dispositivi a firma incrociata.
<b>MESSAGGISTICA E COLLABORAZIONE APERTE</b>: 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.
<b>Comunicazioni ed integrazioni complete</b>
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.
<b>SUPER SICURO</b>: vera crittografia end-to-end (solo chi è nella conversazione può decifrare i messaggi) e firma incrociata per verificare i dispositivi dei partecipanti.
<b>COMUNICAZIONE COMPLETA</b>: 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.
<b>OVUNQUE TU SIA</b>: 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.
<b>Riprendi da dove ti eri fermato</b>
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

View File

@ -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

View File

@ -1 +1 @@
Element (ex Riot.im)
Element - Messaggi sicuri

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
<b>Elements funktioner inkluderar:</b>
- 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.
<b>Sekretess och krypterade meddelanden</b>
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:
<b>Du kan driva Element själv</b>
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
<b>Äg din data</b>
Du bestämmer vart du vill lagra din data och dina meddelanden, utan rist för datainsamling eller åtkomst av tredje parter.
<b>Varför välja Element?</b>
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
<b>ÄG DIN DATA</b>: 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.
<b>Öppen meddelandehantering och kommunikation</b>
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.
<b>ÖPPEN KOMMUNIKATION OCH ÖPPET SAMARBETE</b>: 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.
<b>Supersäker</b>
Riktig totalsträckskryptering (bara de i konversationen kan avkryptera meddelanden), och korssignerad enhetsverifiering.
<b>SUPERSÄKER</b>: Riktig totalsträckskryptering (bara de in konversationen kan avkryptera meddelandena), och korssingering för att verifiera konversationsmedlemmars enheter.
<b>Komplett kommunikation och integration</b>
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.
<b>KOMPLETT KOMMUNIKATION</b>: 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.
<b>ÖVERALLT DÄR DU ÄR</b>: 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.
<b>Fortsätt där du lämnade</b>
Håll kontakten vart du än är med fullt synkroniserad meddelandehistorik på alla dina enheter och på webben på https://app.element.io

View File

@ -1 +1 @@
Säker decentraliserad chatt och VoIP. Håll din data säker från tredje parter.
Gruppmeddelandeapp - krypterade meddelanden, gruppchatt och videosamtal

View File

@ -1 +1 @@
Element (före detta Riot.im)
Element - Säker meddelandeapp

View File

@ -0,0 +1,2 @@
此版本中的主要變動:效能改善與錯誤修復!
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.1.4

View File

@ -0,0 +1,2 @@
此版本中的主要變動1.1.4 的快速修補
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.1.5

View File

@ -0,0 +1,2 @@
此版本中的主要變動1.1.5 的快速修補
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.1.6

View File

@ -1,30 +1,39 @@
Element 是一種新型態的即時通訊軟體與協作應用程式:
Element 同時是安全的通訊軟體,也是生產力團隊協作應用程式,非常適合在遠端工作時進行群組聊天。此聊天應用程式使用了端到端加密來提供強大的視訊會議、檔案分享與語音通話。
1. 自己的隱私自己掌控
2. 讓您與任何在 Matrix 網路中的人通訊,甚至可與如 Slack 等的應用程式整合
3. 保護您免受廣告、資料採礦與圍牆花園的侵害
4. 透過端到端加密保護您,並使用交叉簽章來驗證其他人
<b>Element 的功能包含了:</b>
- 進階線上通訊工具
- 完全加密的訊息,即使對於遠端工作者,也可以有更安全的公司通訊
- 以 Matrix 開放原始碼框架為基礎的去中心化的聊天
- 在管理專案時透過加密資料安全地分享檔案
- 包含了 VoIP 與畫面分享的視訊聊天
- 與您最喜歡的協作工具、專案管理工具、VoIP 服務與其他團隊訊息應用程式輕鬆整合
Element 是去中心化且開放原始碼的應用程式,因此與其他即時通訊與協作軟體完全不同。
Element 與其他訊息傳遞與協作應用程式完全不同。它在 Matrix一個用於安全傳遞訊息與去中心化通訊的開放網路上執行。其可以自架讓使用者對他們的資料與訊息有最大的所有權與控制權
Element 讓您可以自架(或是自行選擇服務提供者)所以您擁有您資料與對話的隱私、所有權與控制權。它讓您可以存取開放的網路;因此,您不僅可以與其他 Matrix 使用者聊天。而且非常安全。
<b>隱私與加密訊息傳遞</b>
Element 保護您不受不想要的廣告、資料挖礦與圍牆花園侵擾。其也透過端到端加密與交叉簽章裝置驗證保護了您所有的資料,並提供一對一視訊以及語音通訊。
Element 能作到這些事情是因為它在 Matrix 上執行,這是一個開放的去中心化通訊的標準
Element 透過與其他商業協作工具,如 Slack 等應用程式整合,讓您可以在控制您的隱私的同時,也可以與 Matrix 網路上的任何人安全地通訊
Element 讓您選擇您要在哪裡託管您的對話來將控制權還給您。在 Element 應用程式中,您可以選擇其他方式來託管:
<b>Element 可以自架</b>
為了可以完整控制您的敏感資料與對話Element 可以自架,您也可以選擇任何以 Matrix 為基礎的服務提供商開放原始碼、去中心化的通訊標準。Element 為您提供隱私、安全合規與整合活性。
1. 在由 Matrix 開發者架設的 matrix.org 公開伺服器上取得免費的帳號,或是從數千個由志願者所架設的公開伺服器中選擇
2. 在您自己的硬體上自行架設伺服器並建立帳號
3. 訂閱 Element Matrix 服務託管平台並在自訂伺服氣上註冊帳號
<b>擁有您的資料</b>
您可以決定將您的資料與訊息儲存在何處。沒有資料挖礦或被第三方存取的風險。
<b>為何選擇 Element</b>
Element 透過不同的方式讓您掌控一切:
1. 在 Matrix 開發者架設的 matrix.org 公開伺服器上取得免費帳號,或是從數千個由志願者架設的公開伺服器中選擇
2. 在您自己的 IT 基礎架構上執行伺服器來自行託管您的帳號
3. 只要訂閱 Element Matrix Services 託管平台就可以在自訂的伺服器上註冊帳號
<b>擁有您的資料</b>:您決定您的資料與訊息要放在哪裡。您擁有並控制它,而非某些科技巨頭會挖掘您的資料並將其售予第三方。
<b>開放訊息傳遞與協作</b>
您可以與 Matrix 網路上的任何人聊天,不論他們是使用 Element、其他 Matrix 應用程式或其他通訊應用程式。
<b>開放的即時通訊與協作</b>:您可以與 Matrix 網路中的任何人聊天,不管他們是使用 Element 或其他 Matrix 應用程式都可以,或甚至是其他的訊息系統,如 Slack、IRC 或 XMPP 也都可以。
<b>超級安全</b>
真的端到端加密(僅有那些在對話中的可以解密訊息)以及交叉簽章裝置驗證。
<b>超級安全</b>:即時的端到端加密(僅有參與對話的人可以解密訊息),以及交叉簽章以驗證對話參與者的裝置。
<b>完整的通訊與整合Complete communication and integration</b>
訊息傳遞、語音與視訊通話、檔案分享、畫面分享與超多的整合、機器人與小工具。建構聊天室、社群、保持聯絡並完成工作。
<b>完整通訊</b>:即時通訊、語音與視訊通話、檔案分享、畫面分享與超多的整合、機器人與小工具。建立聊天室、保持聯繫並完成工作。
<b>無論您身在何處</b>:無論您身在何處,都可以透過 https://app.element.io 來在所有裝置與網路上保持訊息歷史同步。
<b>從上次離開的地方開始</b>
無論您身在何處,都可以透過在您所有裝置與網頁 https://app.element.io 間完全同步的訊息歷史保持聯絡

View File

@ -1 +1 @@
安全的去中心化聊天與 VoIP。確保您的資料不受第三方的影響。
群組通訊軟體 - 訊息加密、群組聊天與視訊通話

View File

@ -1 +1 @@
Element(曾名為 Riot.im
Element - 安全的通訊軟體

View File

@ -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"
}
}
@ -168,6 +165,9 @@ 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.22'
@ -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"

View File

@ -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<RoomKeyContent>()
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()

View File

@ -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
}

View File

@ -16,15 +16,19 @@
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.QueryStringValue
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
@ -42,8 +46,6 @@ 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
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@ -56,22 +58,29 @@ class SpaceCreationTest : InstrumentedTest {
val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true))
val roomName = "My Space"
val topic = "A public space for test"
val spaceId: String
runBlocking {
spaceId = session.spaceService().createSpace(roomName, topic, null, true)
// wait a bit to let the summary update it self :/
delay(400)
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)
assertEquals(roomName, syncedSpace?.asRoom()?.roomSummary()?.name, "Room name should be set")
assertEquals(topic, syncedSpace?.asRoom()?.roomSummary()?.topic, "Room topic should be set")
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(syncedSpace, "Space should be found by Id")
val creationEvent = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_CREATE)
assertNotNull("Space should be found by Id", syncedSpace)
val creationEvent = syncedSpace!!.asRoom().getStateEvent(EventType.STATE_ROOM_CREATE)
val createContent = creationEvent?.content.toModel<RoomCreateContent>()
assertEquals(RoomType.SPACE, createContent?.type, "Room type should be space")
assertEquals("Room type should be space", RoomType.SPACE, createContent?.type)
var powerLevelsContent: PowerLevelsContent? = null
commonTestHelper.waitWithLatch { latch ->
@ -81,17 +90,17 @@ class SpaceCreationTest : InstrumentedTest {
toModel != null
}
}
assertEquals(100, powerLevelsContent?.eventsDefault, "Space-rooms should be created with a power level for events_default of 100")
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<RoomGuestAccessContent>()?.guestAccess
assertEquals(GuestAccess.CanJoin, guestAccess, "Public space room should be peekable by guest")
assertEquals("Public space room should be peekable by guest", GuestAccess.CanJoin, guestAccess)
val historyVisibility = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY)?.content
?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility
assertEquals(RoomHistoryVisibility.WORLD_READABLE, historyVisibility, "Public space room should be world readable")
assertEquals("Public space room should be world readable", RoomHistoryVisibility.WORLD_READABLE, historyVisibility)
commonTestHelper.signOutAndClose(session)
}
@ -120,8 +129,8 @@ class SpaceCreationTest : InstrumentedTest {
assertEquals(JoinSpaceResult.Success, joinResult)
val spaceBobPov = bobSession.spaceService().getSpace(spaceId)
assertEquals(roomName, spaceBobPov?.asRoom()?.roomSummary()?.name, "Room name should be set")
assertEquals(topic, spaceBobPov?.asRoom()?.roomSummary()?.topic, "Room topic should be set")
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)
@ -139,54 +148,73 @@ class SpaceCreationTest : InstrumentedTest {
val syncedSpace = aliceSession.spaceService().getSpace(spaceId)
// create a room
val firstChild: String = runBlocking {
aliceSession.createRoom(CreateRoomParams().apply {
this.name = "FirstRoom"
this.topic = "Description of first room"
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
})
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()
}
}
runBlocking {
syncedSpace?.addChildren(firstChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", true)
commonTestHelper.waitWithLatch {
GlobalScope.launch {
syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", true, suggested = true)
it.countDown()
}
}
val secondChild: String = runBlocking {
aliceSession.createRoom(CreateRoomParams().apply {
this.name = "SecondRoom"
this.topic = "Description of second room"
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
})
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()
}
}
runBlocking {
syncedSpace?.addChildren(secondChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", false)
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
val joinResult = runBlocking {
bobSession.spaceService().joinSpace(spaceId)
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(roomName, spaceBobPov?.asRoom()?.roomSummary()?.name, "Room name should be set")
assertEquals(topic, spaceBobPov?.asRoom()?.roomSummary()?.topic, "Room topic should be set")
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.getRoom(firstChild)?.roomSummary()?.membership
assertEquals(Membership.JOIN, bobMembershipFirstRoom, "Bob should have joined this room")
val bobMembershipFirstRoom = bobSession.getRoomSummary(firstChild!!)?.membership
assertEquals("Bob should have joined this room", Membership.JOIN, bobMembershipFirstRoom)
RoomSummaryQueryParams.Builder()
val spaceSummaryBobPov = bobSession.spaceService().getSpaceSummaries(roomSummaryQueryParams {
this.roomId = QueryStringValue.Equals(spaceId)
this.memberships = listOf(Membership.JOIN)
}).firstOrNull()
val childCount = bobSession.getRoomSummaries(
roomSummaryQueryParams {
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(spaceId)
}
).size
assertEquals(2, spaceSummaryBobPov?.spaceChildren?.size ?: -1, "Unexpected number of children")
assertEquals("Unexpected number of joined children", 1, childCount)
commonTestHelper.signOutAndClose(aliceSession)
commonTestHelper.signOutAndClose(bobSession)

View File

@ -20,15 +20,18 @@ import android.util.Log
import androidx.lifecycle.Observer
import kotlinx.coroutines.Dispatchers
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.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
@ -36,9 +39,6 @@ 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
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@ -51,27 +51,38 @@ class SpaceHierarchyTest : InstrumentedTest {
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
val spaceName = "My Space"
val topic = "A public space for test"
val spaceId: String
runBlocking {
spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
// wait a bit to let the summary update it self :/
delay(400)
var spaceId: String = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
it.countDown()
}
}
val syncedSpace = session.spaceService().getSpace(spaceId)
val roomId = runBlocking {
session.createRoom(CreateRoomParams().apply { name = "General" })
var roomId: String = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
roomId = session.createRoom(CreateRoomParams().apply { name = "General" })
it.countDown()
}
}
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
runBlocking {
syncedSpace!!.addChildren(roomId, viaServers, null, true)
commonTestHelper.waitWithLatch {
GlobalScope.launch {
syncedSpace!!.addChildren(roomId, viaServers, null, true)
it.countDown()
}
}
runBlocking {
session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers)
commonTestHelper.waitWithLatch {
GlobalScope.launch {
session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers)
it.countDown()
}
}
Thread.sleep(9000)
@ -84,60 +95,80 @@ class SpaceHierarchyTest : InstrumentedTest {
}
assertNotNull(parents)
assertEquals(1, parents.size)
assertEquals(1, parents!!.size)
assertEquals(spaceName, parents.first().roomSummary?.name)
assertNotNull(canonicalParents)
assertEquals(1, canonicalParents.size)
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")
val spaceId = runBlocking {
session.spaceService().createSpace(spaceName, topic, null, true)
}
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 viaServers = listOf(session.sessionParams.homeServerHost ?: "")
runBlocking {
roomIdList.forEach { entry ->
syncedSpace!!.addChildren(entry.first, viaServers, null, true)
}
}
runBlocking {
roomIdList.forEach {
session.spaceService().setSpaceParent(it.first, spaceId, it.second, viaServers)
}
delay(400)
}
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(1, parents.size, "Unexpected number of parent")
assertEquals(spaceName, parents.first().roomSummary?.name, "Unexpected parent name ")
assertEquals(if (it.second) 1 else 0, canonicalParents?.size ?: 0, "Parent of ${it.first} should be canonical ${it.second}")
}
}
// @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<Pair<String, Boolean>>()
// 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() {
@ -162,50 +193,64 @@ class SpaceHierarchyTest : InstrumentedTest {
// add C as a subspace of A
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
runBlocking {
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
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
val orphan1 = runBlocking {
session.createRoom(CreateRoomParams().apply { name = "O1" })
var orphan1 = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
orphan1 = session.createRoom(CreateRoomParams().apply { name = "O1" })
it.countDown()
}
}
val orphan2 = runBlocking {
session.createRoom(CreateRoomParams().apply { name = "O2" })
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(9, allRooms.size, "Unexpected number of rooms")
assertEquals("Unexpected number of rooms", 9, allRooms.size)
val orphans = session.getFlattenRoomSummaryChildrenOf(null)
assertEquals(2, orphans.size, "Unexpected number of orphan rooms")
assertTrue(orphans.indexOfFirst { it.roomId == orphan1 } != -1, "O1 should be an orphan")
assertTrue(orphans.indexOfFirst { it.roomId == orphan2 } != -1, "O2 should be an orphan ${orphans.map { it.name }}")
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(4, aChildren.size, "Unexpected number of flatten child rooms")
assertTrue(aChildren.indexOfFirst { it.name == "A1" } != -1, "A1 should be a child of A")
assertTrue(aChildren.indexOfFirst { it.name == "A2" } != -1, "A2 should be a child of A")
assertTrue(aChildren.indexOfFirst { it.name == "C1" } != -1, "CA should be a grand child of A")
assertTrue(aChildren.indexOfFirst { it.name == "C2" } != -1, "A1 should be a grand child of A")
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
val a3 = runBlocking {
session.createRoom(CreateRoomParams().apply { name = "A3" })
}
runBlocking {
spaceA!!.addChildren(a3, viaServers, null, false)
delay(400)
// here we do not set the parent!!
commonTestHelper.waitWithLatch {
GlobalScope.launch {
val a3 = session.createRoom(CreateRoomParams().apply { name = "A3" })
spaceA!!.addChildren(a3, viaServers, null, false)
it.countDown()
}
}
val orphansUpdate = session.getFlattenRoomSummaryChildrenOf(null)
assertEquals(2, orphansUpdate.size, "Unexpected number of orphan rooms ${orphansUpdate.map { it.name }}")
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
@ -225,15 +270,21 @@ class SpaceHierarchyTest : InstrumentedTest {
// add C as a subspace of A
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
runBlocking {
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
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
runBlocking {
val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId)
spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true)
commonTestHelper.waitWithLatch {
GlobalScope.launch {
val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId)
spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true)
it.countDown()
}
}
Thread.sleep(1000)
@ -242,11 +293,11 @@ class SpaceHierarchyTest : InstrumentedTest {
val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
assertEquals(4, aChildren.size, "Unexpected number of flatten child rooms ${aChildren.map { it.name }}")
assertTrue(aChildren.indexOfFirst { it.name == "A1" } != -1, "A1 should be a child of A")
assertTrue(aChildren.indexOfFirst { it.name == "A2" } != -1, "A2 should be a child of A")
assertTrue(aChildren.indexOfFirst { it.name == "C1" } != -1, "CA should be a grand child of A")
assertTrue(aChildren.indexOfFirst { it.name == "C2" } != -1, "A1 should be a grand child of A")
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
@ -282,9 +333,7 @@ class SpaceHierarchyTest : InstrumentedTest {
override fun onChanged(children: List<RoomSummary>?) {
// 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?.indexOfFirst { it.name == "C1" } != -1
&& children?.indexOfFirst { it.name == "C2" } != -1
) {
if (children?.any { it.name == "C1" } == true && children.any { it.name == "C2" }) {
// B1 has been added live!
latch.countDown()
flatAChildren.removeObserver(this)
@ -345,8 +394,12 @@ class SpaceHierarchyTest : InstrumentedTest {
childInfo: List<Triple<String, Boolean, Boolean?>>
/** Name, auto-join, canonical*/
): TestSpaceCreationResult {
val spaceId = runBlocking {
session.spaceService().createSpace(spaceName, "Test Topic", null, true)
var spaceId = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true)
it.countDown()
}
}
val syncedSpace = session.spaceService().getSpace(spaceId)
@ -354,9 +407,14 @@ class SpaceHierarchyTest : InstrumentedTest {
val roomIds =
childInfo.map { entry ->
runBlocking {
session.createRoom(CreateRoomParams().apply { name = entry.first })
var roomId = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
roomId = session.createRoom(CreateRoomParams().apply { name = entry.first })
it.countDown()
}
}
roomId
}
roomIds.forEachIndexed { index, roomId ->
@ -403,12 +461,12 @@ class SpaceHierarchyTest : InstrumentedTest {
// + A
// a1, a2
// + B
// b1, b2, b3
// b1, b2, b3
// + C
// + c1, c2
val rootSpaces = session.spaceService().getRootSpaceSummaries()
assertEquals(2, rootSpaces.size, "Unexpected number of root spaces ${rootSpaces.map { it.name }}")
assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size)
}
}

View File

@ -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,

View File

@ -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()

View File

@ -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.

View File

@ -53,11 +53,9 @@ 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_CHILD = "org.matrix.msc1772.space.child"
const val STATE_SPACE_CHILD = "m.space.child"
// const val STATE_SPACE_PARENT = "m.space.parent"
const val STATE_SPACE_PARENT = "org.matrix.msc1772.space.parent"
const val STATE_SPACE_PARENT = "m.space.parent"
/**
* Note that this Event has been deprecated, see

View File

@ -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
*
*/

View File

@ -58,7 +58,7 @@ data class RoomSummaryQueryParams(
val roomTagQueryFilter: RoomTagQueryFilter?,
val excludeType: List<String?>?,
val includeType: List<String?>?,
val activeSpaceId: ActiveSpaceFilter?,
val activeSpaceFilter: ActiveSpaceFilter?,
var activeGroupId: String? = null
) {
@ -72,7 +72,7 @@ data class RoomSummaryQueryParams(
var roomTagQueryFilter: RoomTagQueryFilter? = null
var excludeType: List<String?>? = listOf(RoomType.SPACE)
var includeType: List<String?>? = null
var activeSpaceId: ActiveSpaceFilter = ActiveSpaceFilter.None
var activeSpaceFilter: ActiveSpaceFilter = ActiveSpaceFilter.None
var activeGroupId: String? = null
fun build() = RoomSummaryQueryParams(
@ -84,7 +84,7 @@ data class RoomSummaryQueryParams(
roomTagQueryFilter = roomTagQueryFilter,
excludeType = excludeType,
includeType = includeType,
activeSpaceId = activeSpaceId,
activeSpaceFilter = activeSpaceFilter,
activeGroupId = activeGroupId
)
}

View File

@ -16,31 +16,35 @@
package org.matrix.android.sdk.api.session.room.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility
*/
@JsonClass(generateAdapter = false)
enum class RoomHistoryVisibility {
/**
* All events while this is the m.room.history_visibility value may be shared by any
* participating homeserver with anyone, regardless of whether they have ever joined the room.
*/
WORLD_READABLE,
@Json(name = "world_readable") WORLD_READABLE,
/**
* Previous events are always accessible to newly joined members. All events in the
* room are accessible, even those sent when the member was not a part of the room.
*/
SHARED,
@Json(name = "shared") SHARED,
/**
* Events are accessible to newly joined members from the point they were invited onwards.
* Events stop being accessible when the member's state changes to something other than invite or join.
*/
INVITED,
@Json(name = "invited") INVITED,
/**
* Events are accessible to newly joined members from the point they joined the room onwards.
* Events stop being accessible when the member's state changes to something other than join.
*/
JOINED
@Json(name = "joined") JOINED
}

View File

@ -27,7 +27,7 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
* 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 = "",
@ -36,6 +36,7 @@ data class RoomSummary constructor(
val avatarUrl: String = "",
val canonicalAlias: String? = null,
val aliases: List<String> = emptyList(),
val joinRules: RoomJoinRules? = null,
val isDirect: Boolean = false,
val directUserId: String? = null,
val joinedMembersCount: Int? = 0,
@ -81,6 +82,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

View File

@ -18,6 +18,5 @@ package org.matrix.android.sdk.api.session.room.model
object RoomType {
const val SPACE = "org.matrix.msc1772.space" // "m.space"
// const val MESSAGING = "org.matrix.msc1840.messaging"
const val SPACE = "m.space"
}

View File

@ -29,5 +29,6 @@ data class SpaceChildInfo(
val activeMemberCount: Int?,
val autoJoin: Boolean,
val viaServers: List<String>,
val parentRoomId: String?
val parentRoomId: String?,
val suggested: Boolean?
)

View File

@ -160,6 +160,6 @@ open class CreateRoomParams {
companion object {
private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate"
private const val CREATION_CONTENT_KEY_ROOM_TYPE = "org.matrix.msc1772.type"
private const val CREATION_CONTENT_KEY_ROOM_TYPE = "type"
}
}

View File

@ -28,5 +28,5 @@ data class RoomCreateContent(
@Json(name = "room_version") val roomVersion: String? = null,
@Json(name = "predecessor") val predecessor: Predecessor? = null,
// Defines the room type, see #RoomType (user extensible)
@Json(name = "org.matrix.msc1772.type") val type: String? = null
@Json(name = "type") val type: String? = null
)

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -33,7 +33,7 @@ interface Space {
fun spaceSummary(): RoomSummary?
suspend fun addChildren(roomId: String,
viaServers: List<String>,
viaServers: List<String>?,
order: String?,
autoJoin: Boolean = false,
suggested: Boolean? = false)
@ -46,5 +46,8 @@ interface Space {
@Throws
suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean)
@Throws
suspend fun setChildrenSuggested(roomId: String, suggested: Boolean)
// fun getChildren() : List<IRoomSummary>
}

View File

@ -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<SendVerificationMessageTask.Params, String> {
@ -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
}
}

View File

@ -20,6 +20,7 @@ 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
@ -33,9 +34,9 @@ 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.TimelineEventEntityFields
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
@ -48,7 +49,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
const val SESSION_STORE_SCHEMA_SC_VERSION = 2L
const val SESSION_STORE_SCHEMA_SC_VERSION_OFFSET = (1L shl 12)
const val SESSION_STORE_SCHEMA_VERSION = 10L +
const val SESSION_STORE_SCHEMA_VERSION = 12L +
SESSION_STORE_SCHEMA_SC_VERSION * SESSION_STORE_SCHEMA_SC_VERSION_OFFSET
}
@ -68,6 +69,8 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
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)
if (oldScVersion <= 0) migrateToSc1(realm)
if (oldScVersion <= 1) migrateToSc2(realm)
@ -171,10 +174,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)
@ -189,9 +188,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")
@ -229,7 +232,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
}
}
fun migrateTo10(realm: DynamicRealm) {
private fun migrateTo10(realm: DynamicRealm) {
Timber.d("Step 9 -> 10")
realm.schema.create("SpaceChildSummaryEntity")
?.addField(SpaceChildSummaryEntityFields.ORDER, String::class.java)
@ -268,4 +271,35 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
?.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)
}
}

View File

@ -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)

View File

@ -50,6 +50,7 @@ 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,
@ -99,7 +100,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
order = it.order,
autoJoin = it.autoJoin ?: false,
viaServers = it.viaServers.toList(),
parentRoomId = roomSummaryEntity.roomId
parentRoomId = roomSummaryEntity.roomId,
suggested = it.suggested
)
},
flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList()

View File

@ -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,

View File

@ -21,7 +21,9 @@ 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
@ -278,6 +280,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 {

View File

@ -29,6 +29,8 @@ internal open class SpaceChildSummaryEntity(
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,

View File

@ -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)

View File

@ -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)

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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<File>()
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
)
)
}

View File

@ -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()
}

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