diff --git a/.github/workflows/sanity_test.yml b/.github/workflows/sanity_test.yml
index 53b70276c5..213c43b716 100644
--- a/.github/workflows/sanity_test.yml
+++ b/.github/workflows/sanity_test.yml
@@ -56,10 +56,10 @@ jobs:
java-version: '11'
- name: Run sanity tests on API ${{ matrix.api-level }}
uses: reactivecircus/android-emulator-runner@v2
- continue-on-error: true # allow pipeline to upload failure results
with:
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
api-level: ${{ matrix.api-level }}
+ profile: 24 # Pixel 5
emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
script: |
adb root
@@ -67,13 +67,12 @@ jobs:
touch emulator.log
chmod 777 emulator.log
adb logcat >> emulator.log &
- ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots
-
+ ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots && exit 1
- name: Upload Failing Test Report Log
- if: failure()
uses: actions/upload-artifact@v2
+ if: failure()
with:
name: sanity-error-results
path: |
emulator.log
- failure_screenshots/
+ failure_screenshots/
\ No newline at end of file
diff --git a/.github/workflows/sync-from-external-sources.yml b/.github/workflows/sync-from-external-sources.yml
index 6a4f8ef147..2da8e10542 100644
--- a/.github/workflows/sync-from-external-sources.yml
+++ b/.github/workflows/sync-from-external-sources.yml
@@ -7,6 +7,8 @@ on:
jobs:
sync-emojis:
runs-on: ubuntu-latest
+ # Skip in forks
+ if: github.repository == 'vector-im/element-android'
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
@@ -39,6 +41,8 @@ jobs:
sync-sas-strings:
runs-on: ubuntu-latest
+ # Skip in forks
+ if: github.repository == 'vector-im/element-android'
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
diff --git a/CHANGES.md b/CHANGES.md
index 7021877ced..19400a38ba 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,8 +1,41 @@
+Changes in Element v1.3.9 (2021-12-01)
+======================================
+
+Features ✨
+----------
+ - Voice messages: Persist drafts of voice messages when navigating between rooms ([#3922](https://github.com/vector-im/element-android/issues/3922))
+ - Make Element Android Thread aware ([#4246](https://github.com/vector-im/element-android/issues/4246))
+ - Iterate on the consent dialog of the identity server. ([#4577](https://github.com/vector-im/element-android/issues/4577))
+
+Bugfixes 🐛
+----------
+ - Fixes left over text when inserting emojis via the ':' menu and replaces the last typed ':' rather than the one at the end of the message ([#3449](https://github.com/vector-im/element-android/issues/3449))
+ - Fixing queued voice message failing to send or retry ([#3833](https://github.com/vector-im/element-android/issues/3833))
+ - Keeping device screen on whilst recording and playing back voice messages ([#4022](https://github.com/vector-im/element-android/issues/4022))
+ - Allow voice messages to continue recording during device rotation ([#4067](https://github.com/vector-im/element-android/issues/4067))
+ - Allowing users to hang up VOIP calls during the initialisation phase (avoids getting stuck in the call screen if something goes wrong) ([#4144](https://github.com/vector-im/element-android/issues/4144))
+ - Make the verification shields the same in Element Web and Element Android ([#4338](https://github.com/vector-im/element-android/issues/4338))
+ - Fix a display issue in the composer when the replied message is changed. ([#4343](https://github.com/vector-im/element-android/issues/4343))
+ - Dismissing the Fdroid variant Listening for notifications on sign out, fixes crash when tapping the notification when signed out ([#4488](https://github.com/vector-im/element-android/issues/4488))
+ - Fix a crash when displaying the bootstrap bottom sheet ([#4520](https://github.com/vector-im/element-android/issues/4520))
+ - Remove duplicated settings declaration ([#4539](https://github.com/vector-im/element-android/issues/4539))
+ - Fixes .ogg files failing to upload to rooms ([#4552](https://github.com/vector-im/element-android/issues/4552))
+ - Add robustness when getting data from cursors ([#4605](https://github.com/vector-im/element-android/issues/4605))
+
+Other changes
+-------------
+ - Upgrade Jitsi lib (and so webrtc) from Jitsi android-sdk-3.1.0 to android-sdk-3.10.0 ([#4504](https://github.com/vector-im/element-android/issues/4504))
+ - Improve crypto logs to help debug decryption failures ([#4507](https://github.com/vector-im/element-android/issues/4507))
+ - Voice recording mic button refactor with small animation tweaks in preparation for voice drafts ([#4515](https://github.com/vector-im/element-android/issues/4515))
+ - Remove requestModelBuild() from epoxy Controllers init{} block ([#4591](https://github.com/vector-im/element-android/issues/4591))
+
+
Changes in Element v1.3.8 (2021-11-17)
======================================
Features ✨
----------
+ - Android 12 support ([#4433](https://github.com/vector-im/element-android/issues/4433))
- Make notification text spoiler aware ([#3477](https://github.com/vector-im/element-android/issues/3477))
- Poll Feature - Create Poll Screen (Disabled for now) ([#4367](https://github.com/vector-im/element-android/issues/4367))
- Adds support for images inside message notifications ([#4402](https://github.com/vector-im/element-android/issues/4402))
diff --git a/README.md b/README.md
index e89fb15010..a085bf7da1 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,20 @@ At each Element release, the SDK module is copied to a dedicated repository: htt
The version 1.0.0 of Element still misses some features which was previously included in Riot-Android.
The team will work to add them on a regular basis.
+# Releases to app stores
+
+There is some delay between when a release is created and when it appears in the app stores (Google Play Store and F-Droid). Here are some of the reasons:
+
+* Not all versioned releases that appear on GitHub are considered stable. Each release is first considered beta: this continues for at least two days. If the release is stable (no serious issues or crashes are reported), then it is released as a production release in Google Play Store, and a request is sent to F-Droid too.
+* Each release on the Google Play Store undergoes review by Google before it comes out. This can take an unpredictable amount of time. In some cases it has taken several weeks.
+* In order for F-Droid to guarantee that the app you receive exactly matches the public source code, they build releases themselves. When a release is considered stable, Element staff inform the F-Droid maintainers and it is added to the build queue. Depending on the load on F-Droid's infrastructure, it can take some time for releases to be built. This always takes at least 24 hours, and can take several days.
+
+If you would like to receive releases more quickly (bearing in mind that they may not be stable) you have a number of options:
+
+1. [Sign up to receive beta releases](https://play.google.com/apps/testing/im.vector.app) via the Google Play Store.
+2. Install a [release APK](https://github.com/vector-im/element-android/releases) directly - download the relevant .apk file and allow installing from untrusted sources in your device settings. Note: these releases are the Google Play version, which depend on some Google services. If you prefer to avoid that, try the latest dev builds, and choose the F-Droid version.
+3. If you're really brave, install the [very latest dev build](https://buildkite.com/matrix-dot-org/element-android/builds/latest?branch=develop&state=passed) - click on *Assemble (GPlay or FDroid) Debug version* then on *Artifacts*.
+
## Contributing
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md) if you want to contribute on Matrix Android projects!
diff --git a/build.gradle b/build.gradle
index 6c6aab57f5..51fd357e67 100644
--- a/build.gradle
+++ b/build.gradle
@@ -74,9 +74,9 @@ allprojects {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
// Jitsi repo
maven {
- url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.1.0"
+ url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.10.0"
// Note: to test Jitsi release you can use a local file like this:
- // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.1.0"
+ // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0"
}
google()
mavenCentral()
diff --git a/dependencies.gradle b/dependencies.gradle
index bf8b6aaeb1..0a806fe840 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -11,7 +11,7 @@ def gradle = "7.0.3"
// Ref: https://kotlinlang.org/releases.html
def kotlin = "1.5.31"
def kotlinCoroutines = "1.5.2"
-def dagger = "2.40.1"
+def dagger = "2.40.3"
def retrofit = "2.9.0"
def arrow = "0.8.2"
def markwon = "4.6.2"
@@ -46,13 +46,13 @@ ext.libs = [
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
],
androidx : [
- 'appCompat' : "androidx.appcompat:appcompat:1.3.1",
+ 'appCompat' : "androidx.appcompat:appcompat:1.4.0",
'core' : "androidx.core:core-ktx:1.7.0",
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3",
- 'fragmentKtx' : "androidx.fragment:fragment-ktx:1.3.6",
- 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.1",
- 'work' : "androidx.work:work-runtime-ktx:2.7.0",
+ 'fragmentKtx' : "androidx.fragment:fragment-ktx:1.4.0",
+ 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.2",
+ 'work' : "androidx.work:work-runtime-ktx:2.7.1",
'autoFill' : "androidx.autofill:autofill:1.1.0",
'preferenceKtx' : "androidx.preference:preference-ktx:1.1.1",
'junit' : "androidx.test.ext:junit:1.1.3",
diff --git a/docs/jitsi.md b/docs/jitsi.md
index 389e7d71ec..55cedaedb1 100644
--- a/docs/jitsi.md
+++ b/docs/jitsi.md
@@ -18,7 +18,7 @@ The generated maven repository is then host in the project https://github.com/ve
Update the script `./tools/jitsi/build_jisti_libs.sh` with the tag of the project `https://github.com/jitsi/jitsi-meet`.
-Currently we are building the version with the tag `android-sdk-3.1.0`.
+Currently we are building the version with the tag `android-sdk-3.10.0`.
### Run the build script
@@ -35,7 +35,7 @@ It will build the Jitsi Meet Android library and put every generated files in th
- Update the file `./build.gradle` to use the previously created local Maven repository. Currently we have this line:
```groovy
-url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.1.0"
+url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.10.0"
```
You can uncomment and update the line starting with `// url "file://...` and comment the line starting with `url`, to test the library using the locally generated Maven repository.
@@ -43,13 +43,13 @@ You can uncomment and update the line starting with `// url "file://...` and com
- Update the dependency of the Jitsi Meet library in the file `./vector/build.gradle`. Currently we have this line:
```groovy
-implementation('org.jitsi.react:jitsi-meet-sdk:3.1.0')
+implementation('org.jitsi.react:jitsi-meet-sdk:3.10.0')
```
- Update the dependency of the WebRTC library in the file `./vector/build.gradle`. Currently we have this line:
```groovy
-implementation('com.facebook.react:react-native-webrtc:1.87.3-jitsi-6624067@aar')
+implementation('com.facebook.react:react-native-webrtc:1.92.1-jitsi-9093212@aar')
```
- Perform a gradle sync and build the project
@@ -74,7 +74,7 @@ If all the tests are passed, you can export the generated Jitsi library to our M
- Update the file `./build.gradle` to use the previously created Maven repository. Currently we have this line:
```groovy
-url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.1.0"
+url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.10.0"
```
- Build the project and perform the sanity tests again.
diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40103070.txt b/fastlane/metadata/android/cs-CZ/changelogs/40103070.txt
new file mode 100644
index 0000000000..d2fc874132
--- /dev/null
+++ b/fastlane/metadata/android/cs-CZ/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: Opravy chyb týkající se především oznámení.
+Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/fastlane/metadata/android/de-DE/changelogs/40103070.txt b/fastlane/metadata/android/de-DE/changelogs/40103070.txt
new file mode 100644
index 0000000000..28e2ca3d7b
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+Hauptänderungen: Fehler bei Benachrichtigungen gefixt
+Ganze Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/fastlane/metadata/android/en-US/changelogs/40103090.txt b/fastlane/metadata/android/en-US/changelogs/40103090.txt
new file mode 100644
index 0000000000..908f7e7359
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40103090.txt
@@ -0,0 +1,2 @@
+Main changes in this version: Add support for voice message draft. Many bugfixes!
+Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.9
\ No newline at end of file
diff --git a/fastlane/metadata/android/et/changelogs/40103070.txt b/fastlane/metadata/android/et/changelogs/40103070.txt
new file mode 100644
index 0000000000..5bb183d918
--- /dev/null
+++ b/fastlane/metadata/android/et/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+Põhilised muutused selles versioonis: erinevad veaparandused, neist enamus on seotud teavitustega.
+Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/fastlane/metadata/android/fa/changelogs/40103050.txt b/fastlane/metadata/android/fa/changelogs/40103050.txt
new file mode 100644
index 0000000000..fa905b14cb
--- /dev/null
+++ b/fastlane/metadata/android/fa/changelogs/40103050.txt
@@ -0,0 +1,2 @@
+تغییرات اصلی در این نگارش: افزودن پشتیبانی حضور برای اتاقهای پیام مستقیم (یادداشت: حضور روی matrix.org از کار افتاده است). افزودن دوبارهٔ پشتیبانی اندروید خودرو.
+گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/fa/changelogs/40103060.txt b/fastlane/metadata/android/fa/changelogs/40103060.txt
new file mode 100644
index 0000000000..f4e997dc88
--- /dev/null
+++ b/fastlane/metadata/android/fa/changelogs/40103060.txt
@@ -0,0 +1,2 @@
+تغییرات اصلی در این نگارش: افزودن پشتیبانی حضور برای اتاقهای پیام مستقیم (یادداشت: حضور روی matrix.org از کار افتاده است). افزودن دوبارهٔ پشتیبانی اندروید خودرو.
+گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/fastlane/metadata/android/fa/changelogs/40103070.txt b/fastlane/metadata/android/fa/changelogs/40103070.txt
new file mode 100644
index 0000000000..da717e2ac2
--- /dev/null
+++ b/fastlane/metadata/android/fa/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+تغییرات اصلی در این نگارش: رفع اشکالهایی عمدتاً مربوط به آگاهیها.
+گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40103070.txt b/fastlane/metadata/android/fr-FR/changelogs/40103070.txt
new file mode 100644
index 0000000000..5fbcb12201
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : corrections de problèmes, principalement sur les notifications
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/fastlane/metadata/android/hu-HU/changelogs/40103070.txt b/fastlane/metadata/android/hu-HU/changelogs/40103070.txt
new file mode 100644
index 0000000000..4348b7a0fe
--- /dev/null
+++ b/fastlane/metadata/android/hu-HU/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+Fő változás ebben a verzióban: Értesítési hibajavítások
+Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/fastlane/metadata/android/id/changelogs/40103070.txt b/fastlane/metadata/android/id/changelogs/40103070.txt
new file mode 100644
index 0000000000..b28649e684
--- /dev/null
+++ b/fastlane/metadata/android/id/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+Perubahan utama di versi ini: Perbaikan bug terutama untuk notifikasinya.
+Changelog lengkap: https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/fastlane/metadata/android/it-IT/changelogs/40103070.txt b/fastlane/metadata/android/it-IT/changelogs/40103070.txt
new file mode 100644
index 0000000000..fbf4a1b19f
--- /dev/null
+++ b/fastlane/metadata/android/it-IT/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+Modifiche principali in questa versione: correzioni riguardo le notifiche.
+Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/fastlane/metadata/android/pl/changelogs/40103070.txt b/fastlane/metadata/android/pl/changelogs/40103070.txt
new file mode 100644
index 0000000000..2cb20a9570
--- /dev/null
+++ b/fastlane/metadata/android/pl/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+Główne zmiany w tej wersji: Poprawki błędów dotyczące głównie powiadomień
+Pełny changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/fastlane/metadata/android/pl/full_description.txt b/fastlane/metadata/android/pl/full_description.txt
new file mode 100644
index 0000000000..77c13cebc1
--- /dev/null
+++ b/fastlane/metadata/android/pl/full_description.txt
@@ -0,0 +1,42 @@
+Element jest bezpiecznym komunikatorem oraz narzędziem do komunikacji w zespole które jest idealna do pracy zdalnej. Nasza aplikacja korzysta z szyfrowania end-to-end aby rozmowy wideo, udostępnianie plików oraz rozmowy głosowe były bezpieczne.
+
+Funkcje Element'a:
+- Zaawansowane narzędzia komunikacji online
+- W pełni szyfrowane wiadomości które umożliwiają bezpieczniejszą komunikacje dla firm, a nawet dla pracowników zdalnych.
+- Zdecentralizowany czat bazowany na otwartym protokole Matrix
+- Bezpieczne udostępnianie plików wraz z szyfrowaniem danych podczas zarządzania projektami
+- Rozmowy z Voice over IP wraz z udostępnianiem ekranu
+- Prosta konfiguracja z ulubionymi narzędziami do kolaboracji, narzędziami do zarządzania projektami usługami VoIP oraz innymi aplikacjami do komunikacji w grupie
+
+Element jest całkowicie inny od innych komunikatorów i aplikacji do kolaboracji. Pracuje na protokole Matrix, otwarto źródłowej sieci stworzonej dla bezpiecznych wiadomości i zdecentralizowanej komunikacji. Pozwala ona na własny hosting serwera dla maksymalnej własności i kontroli nad danymi oraz wiadomościami.
+
+Prywatność i szyfrowane wiadomości
+Element broni cie przez niechcianymi wiadomościami, kopaniem informacji oraz cenzurą. Zabezpiecza wszystkie twoje dane, wideo które pozostaje wiadome tylko dla rozmawiających przez szyfrowanie end-to-end i weryfikacją krzyżową urządzeń.
+
+Element daje kontrole nad twoją prywatnością i umożliwia bezpieczną komunikacje z kimkolwiek w sieci Matrix, lub z innymi firmami przez narzędzia do komunikacji integrując aplikacje takie jak Slack.
+
+Element może być hostowany samemu
+Pozwala to na kontrolę nad twoimi wrażliwymi danymi oraz rozmowami, Element może być hostowany samemu lub pozwala wybrać dowolnego hosta bazowanego na Matrix'ie - otwarto-źródłowym standardzie, dla zdecentralizowanej komunikacji. Element daje tobie prywatność, bezpieczeństwo oraz elastyczność w integracji.
+
+Posiadaj naprawdę swoje dane
+Ty decydujesz gdzie trzymasz swoje dane i wiadomości. Bez ryzyka wycieku lub dostępu firm trzecich.
+
+Element daje ci kontrolę na wiele sposobów:
+1. Utwórz darmowe konto na publicznym serwerze matrix.org hostowanym przez twórców Matrix'a lub wybierz którykolwiek z tysięcy serwerów hostowanych przez wolontariuszy
+2. Hostuj samemu swoje konto przez własny serwer na twojej infrastrukturze
+3. Zarejestruj się na specjalnym serwerze poprzez subskrybowanie hostingu na platformie Element Martix Services
+
+Otwarte wiadomości i kolaboracja
+Możesz rozmawiać z kimkolwiek w sieci Matrix, nie ważne czy korzystają z Element'a, czy z innej aplikacji wspierającej protokół Matrix, a nawet z osobami korzystającymi z innych komunikatorów.
+
+Niesamowicie bezpieczny
+Prawdziwe szyfrowanie end-to-end (tylko osoby w konwersacji mogą odszyfrować wiadomości), a także krzyżowa weryfikacja urządzeń.
+
+Pełna komunikacja i integracja
+Wiadomości, rozmowy głosowe i wideo, udostępnianie plików, ekranu, a nawet integracja z botami i widżetami. Twórz pokoje, społeczności, pozostań w kontakcie i załatwiaj to co chcesz.
+
+Kontynuuj gdzie skończyłeś
+Pozostań zawsze w kontakcie poprzez pełną synchornizację między urządzeniami oraz w sieci na https://app.element.io
+
+Otwarto źródłowy
+Element Android jest otwarto-źródłowym projektem, hostowanym na platformie GitHub. Prosimy o zgłaszanie wszelkich błędów i/lub wsparcie w tworzeniu naszego projektu na https://github.com/vector-im/element-android
diff --git a/fastlane/metadata/android/pl/short_description.txt b/fastlane/metadata/android/pl/short_description.txt
new file mode 100644
index 0000000000..ce473af2a9
--- /dev/null
+++ b/fastlane/metadata/android/pl/short_description.txt
@@ -0,0 +1 @@
+Grupowy komunikator - szyfrowane wiadomosci, grupowe czaty oraz rozmowy wideo
diff --git a/fastlane/metadata/android/pl/title.txt b/fastlane/metadata/android/pl/title.txt
new file mode 100644
index 0000000000..3f4f1ba418
--- /dev/null
+++ b/fastlane/metadata/android/pl/title.txt
@@ -0,0 +1 @@
+Element - Bezpieczny Komunikator
diff --git a/fastlane/metadata/android/pt-BR/changelogs/40103070.txt b/fastlane/metadata/android/pt-BR/changelogs/40103070.txt
new file mode 100644
index 0000000000..216e363f24
--- /dev/null
+++ b/fastlane/metadata/android/pt-BR/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+Principais mudanças nesta versão: Consertos de bugs principalmente quanto às notificações.
+Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/fastlane/metadata/android/sv-SE/changelogs/40103070.txt b/fastlane/metadata/android/sv-SE/changelogs/40103070.txt
new file mode 100644
index 0000000000..1931cbfd82
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: Buggfixar som huvudsakligen rör aviseringar.
+Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/fastlane/metadata/android/uk/changelogs/40103070.txt b/fastlane/metadata/android/uk/changelogs/40103070.txt
new file mode 100644
index 0000000000..ec17d4934b
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+Основні зміни в цій версії: виправлення помилок в основному у повідомленнях.
+Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/fastlane/metadata/android/zh-CN/changelogs/40103070.txt b/fastlane/metadata/android/zh-CN/changelogs/40103070.txt
new file mode 100644
index 0000000000..f2fcf4df92
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+此版本的主要变化:主要关于通知的错误修复。
+完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/fastlane/metadata/android/zh-TW/changelogs/40103070.txt b/fastlane/metadata/android/zh-TW/changelogs/40103070.txt
new file mode 100644
index 0000000000..02b58cc956
--- /dev/null
+++ b/fastlane/metadata/android/zh-TW/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+此版本中的主要變動:主要關於通知的臭蟲修復。
+完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.7
diff --git a/library/ui-styles/src/debug/res/layout/debug_social_login.xml b/library/ui-styles/src/debug/res/layout/debug_social_login.xml
new file mode 100644
index 0000000000..895ecddad4
--- /dev/null
+++ b/library/ui-styles/src/debug/res/layout/debug_social_login.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/ui-styles/src/main/res/values/styles_social_login.xml b/library/ui-styles/src/main/res/values/styles_social_login.xml
index b527d5bfdc..f601f36480 100644
--- a/library/ui-styles/src/main/res/values/styles_social_login.xml
+++ b/library/ui-styles/src/main/res/values/styles_social_login.xml
@@ -9,7 +9,7 @@
start14spcenter
- 2dp
+ 4dp38dpfalse
diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml
index 3f256c0170..4e8abb4560 100644
--- a/library/ui-styles/src/main/res/values/theme_dark.xml
+++ b/library/ui-styles/src/main/res/values/theme_dark.xml
@@ -165,7 +165,9 @@
@transition/image_preview_transition@transition/image_preview_transition
- @style/Widget.Vector.Button.Outlined.SocialLogin.Google.Dark
+
+ @style/Widget.Vector.Button.Outlined.SocialLogin.Google.Light@style/Widget.Vector.Button.Outlined.SocialLogin.Github.Dark@style/Widget.Vector.Button.Outlined.SocialLogin.Facebook.Dark@style/Widget.Vector.Button.Outlined.SocialLogin.Twitter.Dark
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index d92da9688b..eff972a271 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -31,7 +31,7 @@ android {
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
- buildConfigField "String", "SDK_VERSION", "\"1.3.8\""
+ buildConfigField "String", "SDK_VERSION", "\"1.3.9\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
@@ -115,7 +115,7 @@ dependencies {
implementation libs.squareup.retrofit
implementation libs.squareup.retrofitMoshi
- implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.2"))
+ implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3"))
implementation 'com.squareup.okhttp3:okhttp'
implementation 'com.squareup.okhttp3:logging-interceptor'
implementation 'com.squareup.okhttp3:okhttp-urlconnection'
@@ -158,10 +158,10 @@ dependencies {
implementation libs.apache.commonsImaging
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.37'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.38'
testImplementation libs.tests.junit
- testImplementation 'org.robolectric:robolectric:4.7'
+ testImplementation 'org.robolectric:robolectric:4.7.2'
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation libs.mockk.mockk
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
index 0d204edcee..44ac439d7b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
@@ -26,6 +26,7 @@ open class LoggerTag(_value: String, parentTag: LoggerTag? = null) {
object SYNC : LoggerTag("SYNC")
object VOIP : LoggerTag("VOIP")
+ object CRYPTO : LoggerTag("CRYPTO")
val value: String = if (parentTag == null) {
_value
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
index 7ee26de8db..8b1df23903 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
@@ -22,6 +22,7 @@ import androidx.exifinterface.media.ExifInterface
import com.squareup.moshi.JsonClass
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.util.MimeTypes.normalizeMimeType
+import org.matrix.android.sdk.internal.di.MoshiProvider
@Parcelize
@JsonClass(generateAdapter = true)
@@ -44,8 +45,19 @@ data class ContentAttachmentData(
FILE,
IMAGE,
AUDIO,
- VIDEO
+ VIDEO,
+ VOICE_MESSAGE
}
fun getSafeMimeType() = mimeType?.normalizeMimeType()
+
+ fun toJsonString(): String {
+ return MoshiProvider.providesMoshi().adapter(ContentAttachmentData::class.java).toJson(this)
+ }
+
+ companion object {
+ fun fromJsonString(json: String): ContentAttachmentData? {
+ return MoshiProvider.providesMoshi().adapter(ContentAttachmentData::class.java).fromJson(json)
+ }
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt
index 7d827f871b..f67efc50ba 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt
@@ -28,6 +28,10 @@ object RelationType {
/** Lets you define an event which references an existing event.*/
const val REFERENCE = "m.reference"
+ /** Lets you define an thread event that belongs to another existing event.*/
+// const val THREAD = "m.thread" // m.thread is not yet released in the backend
+ const val THREAD = "io.element.thread" // io.element.thread will be replaced by m.thread when it is released
+
/** Lets you define an event which adds a response to an existing event.*/
const val RESPONSE = "org.matrix.response"
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt
index 9471b3dbcb..a8c0de2fa5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt
@@ -23,15 +23,16 @@ package org.matrix.android.sdk.api.session.room.send
* EDIT: draft of an edition of a message
* REPLY: draft of a reply of another message
*/
-sealed class UserDraft(open val text: String) {
- data class REGULAR(override val text: String) : UserDraft(text)
- data class QUOTE(val linkedEventId: String, override val text: String) : UserDraft(text)
- data class EDIT(val linkedEventId: String, override val text: String) : UserDraft(text)
- data class REPLY(val linkedEventId: String, override val text: String) : UserDraft(text)
+sealed interface UserDraft {
+ data class Regular(val content: String) : UserDraft
+ data class Quote(val linkedEventId: String, val content: String) : UserDraft
+ data class Edit(val linkedEventId: String, val content: String) : UserDraft
+ data class Reply(val linkedEventId: String, val content: String) : UserDraft
+ data class Voice(val content: String) : UserDraft
fun isValid(): Boolean {
return when (this) {
- is REGULAR -> text.isNotBlank()
+ is Regular -> content.isNotBlank()
else -> true
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
index 7b96148e2e..18f344882c 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
@@ -36,6 +36,7 @@ import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.listeners.ProgressListener
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
@@ -110,6 +111,9 @@ import kotlin.math.max
* CryptoService maintains all necessary keys and their sharing with other devices required for the crypto.
* Specially, it tracks all room membership changes events in order to do keys updates.
*/
+
+private val loggerTag = LoggerTag("DefaultCryptoService", LoggerTag.CRYPTO)
+
@SessionScope
internal class DefaultCryptoService @Inject constructor(
// Olm Manager
@@ -346,7 +350,7 @@ internal class DefaultCryptoService @Inject constructor(
deviceListManager.startTrackingDeviceList(listOf(userId))
deviceListManager.refreshOutdatedDeviceLists()
} catch (failure: Throwable) {
- Timber.e(failure, "## CRYPTO onSyncWillProcess ")
+ Timber.tag(loggerTag.value).e(failure, "onSyncWillProcess ")
}
}
}
@@ -379,7 +383,7 @@ internal class DefaultCryptoService @Inject constructor(
{
isStarting.set(false)
isStarted.set(false)
- Timber.e(it, "Start failed")
+ Timber.tag(loggerTag.value).e(it, "Start failed")
}
)
}
@@ -551,14 +555,14 @@ internal class DefaultCryptoService @Inject constructor(
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
if (!existingAlgorithm.isNullOrEmpty() && existingAlgorithm != algorithm) {
- Timber.e("## CRYPTO | setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId")
+ Timber.tag(loggerTag.value).e("setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId")
return false
}
val encryptingClass = MXCryptoAlgorithms.hasEncryptorClassForAlgorithm(algorithm)
if (!encryptingClass) {
- Timber.e("## CRYPTO | setEncryptionInRoom() : Unable to encrypt room $roomId with $algorithm")
+ Timber.tag(loggerTag.value).e("setEncryptionInRoom() : Unable to encrypt room $roomId with $algorithm")
return false
}
@@ -577,7 +581,7 @@ internal class DefaultCryptoService @Inject constructor(
// e2e rooms with them, so there is room for optimisation here, but for now
// we just invalidate everyone in the room.
if (null == existingAlgorithm) {
- Timber.v("Enabling encryption in $roomId for the first time; invalidating device lists for all users therein")
+ Timber.tag(loggerTag.value).d("Enabling encryption in $roomId for the first time; invalidating device lists for all users therein")
val userIds = ArrayList(membersId)
@@ -655,17 +659,17 @@ internal class DefaultCryptoService @Inject constructor(
val safeAlgorithm = alg
if (safeAlgorithm != null) {
val t0 = System.currentTimeMillis()
- Timber.v("## CRYPTO | encryptEventContent() starts")
+ Timber.tag(loggerTag.value).v("encryptEventContent() starts")
runCatching {
val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
- Timber.v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
+ Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
}.foldToCallback(callback)
} else {
val algorithm = getEncryptionAlgorithm(roomId)
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON)
- Timber.e("## CRYPTO | encryptEventContent() : $reason")
+ Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason")
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
}
}
@@ -677,7 +681,7 @@ internal class DefaultCryptoService @Inject constructor(
if (roomEncryptor is IMXGroupEncryption) {
roomEncryptor.discardSessionKey()
} else {
- Timber.e("## CRYPTO | discardOutboundSession() for:$roomId: Unable to handle IMXGroupEncryption")
+ Timber.tag(loggerTag.value).e("discardOutboundSession() for:$roomId: Unable to handle IMXGroupEncryption")
}
}
}
@@ -768,14 +772,14 @@ internal class DefaultCryptoService @Inject constructor(
*/
private fun onRoomKeyEvent(event: Event) {
val roomKeyContent = event.getClearContent().toModel() ?: return
- Timber.i("## CRYPTO | onRoomKeyEvent() from: ${event.senderId} type<${event.getClearType()}> , sessionId<${roomKeyContent.sessionId}>")
+ Timber.tag(loggerTag.value).i("onRoomKeyEvent() from: ${event.senderId} type<${event.getClearType()}> , sessionId<${roomKeyContent.sessionId}>")
if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) {
- Timber.e("## CRYPTO | onRoomKeyEvent() : missing fields")
+ Timber.tag(loggerTag.value).e("onRoomKeyEvent() : missing fields")
return
}
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm)
if (alg == null) {
- Timber.e("## CRYPTO | GOSSIP onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
+ Timber.tag(loggerTag.value).e("GOSSIP onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
return
}
alg.onRoomKeyEvent(event, keysBackupService)
@@ -783,29 +787,29 @@ internal class DefaultCryptoService @Inject constructor(
private fun onKeyWithHeldReceived(event: Event) {
val withHeldContent = event.getClearContent().toModel() ?: return Unit.also {
- Timber.i("## CRYPTO | Malformed onKeyWithHeldReceived() : missing fields")
+ Timber.tag(loggerTag.value).i("Malformed onKeyWithHeldReceived() : missing fields")
}
- Timber.i("## CRYPTO | onKeyWithHeldReceived() received from:${event.senderId}, content <$withHeldContent>")
+ Timber.tag(loggerTag.value).i("onKeyWithHeldReceived() received from:${event.senderId}, content <$withHeldContent>")
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(withHeldContent.roomId, withHeldContent.algorithm)
if (alg is IMXWithHeldExtension) {
alg.onRoomKeyWithHeldEvent(withHeldContent)
} else {
- Timber.e("## CRYPTO | onKeyWithHeldReceived() from:${event.senderId}: Unable to handle WithHeldContent for ${withHeldContent.algorithm}")
+ Timber.tag(loggerTag.value).e("onKeyWithHeldReceived() from:${event.senderId}: Unable to handle WithHeldContent for ${withHeldContent.algorithm}")
return
}
}
private fun onSecretSendReceived(event: Event) {
- Timber.i("## CRYPTO | GOSSIP onSecretSend() from ${event.senderId} : onSecretSendReceived ${event.content?.get("sender_key")}")
+ Timber.tag(loggerTag.value).i("GOSSIP onSecretSend() from ${event.senderId} : onSecretSendReceived ${event.content?.get("sender_key")}")
if (!event.isEncrypted()) {
// secret send messages must be encrypted
- Timber.e("## CRYPTO | GOSSIP onSecretSend() :Received unencrypted secret send event")
+ Timber.tag(loggerTag.value).e("GOSSIP onSecretSend() :Received unencrypted secret send event")
return
}
// Was that sent by us?
if (event.senderId != userId) {
- Timber.e("## CRYPTO | GOSSIP onSecretSend() : Ignore secret from other user ${event.senderId}")
+ Timber.tag(loggerTag.value).e("GOSSIP onSecretSend() : Ignore secret from other user ${event.senderId}")
return
}
@@ -815,13 +819,13 @@ internal class DefaultCryptoService @Inject constructor(
.getOutgoingSecretKeyRequests().firstOrNull { it.requestId == secretContent.requestId }
if (existingRequest == null) {
- Timber.i("## CRYPTO | GOSSIP onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
+ Timber.tag(loggerTag.value).i("GOSSIP onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
return
}
if (!handleSDKLevelGossip(existingRequest.secretName, secretContent.secretValue)) {
// TODO Ask to application layer?
- Timber.v("## CRYPTO | onSecretSend() : secret not handled by SDK")
+ Timber.tag(loggerTag.value).v("onSecretSend() : secret not handled by SDK")
}
}
@@ -858,7 +862,7 @@ internal class DefaultCryptoService @Inject constructor(
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
if (!event.isStateEvent()) {
// Ignore
- Timber.w("Invalid encryption event")
+ Timber.tag(loggerTag.value).w("Invalid encryption event")
return
}
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
@@ -912,7 +916,7 @@ internal class DefaultCryptoService @Inject constructor(
*/
private suspend fun uploadDeviceKeys() {
if (cryptoStore.areDeviceKeysUploaded()) {
- Timber.d("Keys already uploaded, nothing to do")
+ Timber.tag(loggerTag.value).d("Keys already uploaded, nothing to do")
return
}
// Prepare the device keys data to send
@@ -971,13 +975,13 @@ internal class DefaultCryptoService @Inject constructor(
password: String,
progressListener: ProgressListener?): ImportRoomKeysResult {
return withContext(coroutineDispatchers.crypto) {
- Timber.v("## CRYPTO | importRoomKeys starts")
+ Timber.tag(loggerTag.value).v("importRoomKeys starts")
val t0 = System.currentTimeMillis()
val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
val t1 = System.currentTimeMillis()
- Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
+ Timber.tag(loggerTag.value).v("importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
val importedSessions = MoshiProvider.providesMoshi()
.adapter>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
@@ -985,7 +989,7 @@ internal class DefaultCryptoService @Inject constructor(
val t2 = System.currentTimeMillis()
- Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms")
+ Timber.tag(loggerTag.value).v("importRoomKeys : JSON parsing ${t2 - t1} ms")
if (importedSessions == null) {
throw Exception("Error")
@@ -1122,7 +1126,7 @@ internal class DefaultCryptoService @Inject constructor(
*/
override fun reRequestRoomKeyForEvent(event: Event) {
val wireContent = event.content.toModel() ?: return Unit.also {
- Timber.e("## CRYPTO | reRequestRoomKeyForEvent Failed to re-request key, null content")
+ Timber.tag(loggerTag.value).e("reRequestRoomKeyForEvent Failed to re-request key, null content")
}
val requestBody = RoomKeyRequestBody(
@@ -1137,7 +1141,7 @@ internal class DefaultCryptoService @Inject constructor(
override fun requestRoomKeyForEvent(event: Event) {
val wireContent = event.content.toModel() ?: return Unit.also {
- Timber.e("## CRYPTO | requestRoomKeyForEvent Failed to request key, null content eventId: ${event.eventId}")
+ Timber.tag(loggerTag.value).e("requestRoomKeyForEvent Failed to request key, null content eventId: ${event.eventId}")
}
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
@@ -1148,7 +1152,7 @@ internal class DefaultCryptoService @Inject constructor(
roomDecryptorProvider
.getOrCreateRoomDecryptor(event.roomId, wireContent.algorithm)
?.requestKeysForEvent(event, false) ?: run {
- Timber.v("## CRYPTO | requestRoomKeyForEvent() : No room decryptor for roomId:${event.roomId} algorithm:${wireContent.algorithm}")
+ Timber.tag(loggerTag.value).v("requestRoomKeyForEvent() : No room decryptor for roomId:${event.roomId} algorithm:${wireContent.algorithm}")
}
}
}
@@ -1287,12 +1291,12 @@ internal class DefaultCryptoService @Inject constructor(
override fun prepareToEncrypt(roomId: String, callback: MatrixCallback) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
- Timber.d("## CRYPTO | prepareToEncrypt() : Check room members up to date")
+ Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
// Ensure to load all room members
try {
loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
} catch (failure: Throwable) {
- Timber.e("## CRYPTO | prepareToEncrypt() : Failed to load room members")
+ Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to load room members")
callback.onFailure(failure)
return@launch
}
@@ -1305,7 +1309,7 @@ internal class DefaultCryptoService @Inject constructor(
if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
- Timber.e("## CRYPTO | prepareToEncrypt() : $reason")
+ Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
callback.onFailure(IllegalArgumentException("Missing algorithm"))
return@launch
}
@@ -1315,7 +1319,7 @@ internal class DefaultCryptoService @Inject constructor(
}.fold(
{ callback.onSuccess(Unit) },
{
- Timber.e("## CRYPTO | prepareToEncrypt() failed.")
+ Timber.tag(loggerTag.value).e(it, "prepareToEncrypt() failed.")
callback.onFailure(it)
}
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
index 3979ff1fb4..ab2ed04dfb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
@@ -16,17 +16,21 @@
package org.matrix.android.sdk.internal.crypto.actions
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXKey
import org.matrix.android.sdk.internal.crypto.model.MXOlmSessionResult
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
+import org.matrix.android.sdk.internal.crypto.model.toDebugString
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import timber.log.Timber
import javax.inject.Inject
private const val ONE_TIME_KEYS_RETRY_COUNT = 3
+private val loggerTag = LoggerTag("EnsureOlmSessionsForDevicesAction", LoggerTag.CRYPTO)
+
internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
private val olmDevice: MXOlmDevice,
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
@@ -36,15 +40,22 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
val results = MXUsersDevicesMap()
- for ((userId, deviceInfos) in devicesByUser) {
- for (deviceInfo in deviceInfos) {
+ for ((userId, deviceList) in devicesByUser) {
+ for (deviceInfo in deviceList) {
val deviceId = deviceInfo.deviceId
val key = deviceInfo.identityKey()
+ if (key == null) {
+ Timber.w("## CRYPTO | Ignoring device (${deviceInfo.userId}|$deviceId) without identity key")
+ continue
+ }
- val sessionId = olmDevice.getSessionId(key!!)
+ val sessionId = olmDevice.getSessionId(key)
if (sessionId.isNullOrEmpty() || force) {
+ Timber.tag(loggerTag.value).d("Found no existing olm session (${deviceInfo.userId}|$deviceId) (force=$force)")
devicesWithoutSession.add(deviceInfo)
+ } else {
+ Timber.tag(loggerTag.value).d("using olm session $sessionId for (${deviceInfo.userId}|$deviceId)")
}
val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId)
@@ -52,6 +63,8 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
}
}
+ Timber.tag(loggerTag.value).d("Devices without olm session (count:${devicesWithoutSession.size}) :" +
+ " ${devicesWithoutSession.joinToString { "${it.userId}|${it.deviceId}" }}")
if (devicesWithoutSession.size == 0) {
return results
}
@@ -71,11 +84,11 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
//
// That should eventually resolve itself, but it's poor form.
- Timber.i("## CRYPTO | claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim")
+ Timber.tag(loggerTag.value).i("claimOneTimeKeysForUsersDevices() : ${usersDevicesToClaim.toDebugString()}")
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
val oneTimeKeys = oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, remainingRetry = ONE_TIME_KEYS_RETRY_COUNT)
- Timber.v("## CRYPTO | claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
+ Timber.tag(loggerTag.value).v("claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
for ((userId, deviceInfos) in devicesByUser) {
for (deviceInfo in deviceInfos) {
var oneTimeKey: MXKey? = null
@@ -83,7 +96,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
if (null != deviceIds) {
for (deviceId in deviceIds) {
val olmSessionResult = results.getObject(userId, deviceId)
- if (olmSessionResult!!.sessionId != null && !force) {
+ if (olmSessionResult?.sessionId != null && !force) {
// We already have a result for this device
continue
}
@@ -92,12 +105,11 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
oneTimeKey = key
}
if (oneTimeKey == null) {
- Timber.w("## CRYPTO | ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm +
- " for device " + userId + " : " + deviceId)
+ Timber.tag(loggerTag.value).d("No one time key for $userId|$deviceId")
continue
}
// Update the result for this device in results
- olmSessionResult.sessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
+ olmSessionResult?.sessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
}
}
}
@@ -112,31 +124,36 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
val signKeyId = "ed25519:$deviceId"
val signature = oneTimeKey.signatureForUserId(userId, signKeyId)
- if (!signature.isNullOrEmpty() && !deviceInfo.fingerprint().isNullOrEmpty()) {
+ val fingerprint = deviceInfo.fingerprint()
+ if (!signature.isNullOrEmpty() && !fingerprint.isNullOrEmpty()) {
var isVerified = false
var errorMessage: String? = null
try {
- olmDevice.verifySignature(deviceInfo.fingerprint()!!, oneTimeKey.signalableJSONDictionary(), signature)
+ olmDevice.verifySignature(fingerprint, oneTimeKey.signalableJSONDictionary(), signature)
isVerified = true
} catch (e: Exception) {
+ Timber.tag(loggerTag.value).d(e, "verifyKeyAndStartSession() : Verify error for otk: ${oneTimeKey.signalableJSONDictionary()}," +
+ " signature:$signature fingerprint:$fingerprint")
+ Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Verify error for ${deviceInfo.userId}|${deviceInfo.deviceId} " +
+ " - signable json ${oneTimeKey.signalableJSONDictionary()}")
errorMessage = e.message
}
// Check one-time key signature
if (isVerified) {
- sessionId = olmDevice.createOutboundSession(deviceInfo.identityKey()!!, oneTimeKey.value)
+ sessionId = deviceInfo.identityKey()?.let { identityKey ->
+ olmDevice.createOutboundSession(identityKey, oneTimeKey.value)
+ }
- if (!sessionId.isNullOrEmpty()) {
- Timber.v("## CRYPTO | verifyKeyAndStartSession() : Started new sessionid " + sessionId +
- " for device " + deviceInfo + "(theirOneTimeKey: " + oneTimeKey.value + ")")
- } else {
+ if (sessionId.isNullOrEmpty()) {
// Possibly a bad key
- Timber.e("## CRYPTO | verifyKeyAndStartSession() : Error starting session with device $userId:$deviceId")
+ Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Error starting session with device $userId:$deviceId")
+ } else {
+ Timber.tag(loggerTag.value).d("verifyKeyAndStartSession() : Started new sessionId $sessionId for device $userId:$deviceId")
}
} else {
- Timber.e("## CRYPTO | verifyKeyAndStartSession() : Unable to verify signature on one-time key for device " + userId +
- ":" + deviceId + " Error " + errorMessage)
+ Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Unable to verify otk signature for $userId:$deviceId: $errorMessage")
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
index d7411ad0be..8bbc71543c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
@@ -44,6 +45,8 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import timber.log.Timber
+private val loggerTag = LoggerTag("MXMegolmDecryption", LoggerTag.CRYPTO)
+
internal class MXMegolmDecryption(private val userId: String,
private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager,
@@ -74,7 +77,7 @@ internal class MXMegolmDecryption(private val userId: String,
@Throws(MXCryptoError::class)
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
- Timber.v("## CRYPTO | decryptEvent ${event.eventId}, requestKeysOnFail:$requestKeysOnFail")
+ Timber.tag(loggerTag.value).v("decryptEvent ${event.eventId}, requestKeysOnFail:$requestKeysOnFail")
if (event.roomId.isNullOrBlank()) {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
@@ -230,7 +233,7 @@ internal class MXMegolmDecryption(private val userId: String,
* @param event the key event.
*/
override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {
- Timber.v("## CRYPTO | onRoomKeyEvent()")
+ Timber.tag(loggerTag.value).v("onRoomKeyEvent()")
var exportFormat = false
val roomKeyContent = event.getClearContent().toModel() ?: return
@@ -239,11 +242,11 @@ internal class MXMegolmDecryption(private val userId: String,
val forwardingCurve25519KeyChain: MutableList = ArrayList()
if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.sessionId.isNullOrEmpty() || roomKeyContent.sessionKey.isNullOrEmpty()) {
- Timber.e("## CRYPTO | onRoomKeyEvent() : Key event is missing fields")
+ Timber.tag(loggerTag.value).e("onRoomKeyEvent() : Key event is missing fields")
return
}
if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
- Timber.i("## CRYPTO | onRoomKeyEvent(), forward adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
+ Timber.tag(loggerTag.value).i("onRoomKeyEvent(), forward adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
val forwardedRoomKeyContent = event.getClearContent().toModel()
?: return
@@ -252,7 +255,7 @@ internal class MXMegolmDecryption(private val userId: String,
}
if (senderKey == null) {
- Timber.e("## CRYPTO | onRoomKeyEvent() : event is missing sender_key field")
+ Timber.tag(loggerTag.value).e("onRoomKeyEvent() : event is missing sender_key field")
return
}
@@ -261,20 +264,20 @@ internal class MXMegolmDecryption(private val userId: String,
exportFormat = true
senderKey = forwardedRoomKeyContent.senderKey
if (null == senderKey) {
- Timber.e("## CRYPTO | onRoomKeyEvent() : forwarded_room_key event is missing sender_key field")
+ Timber.tag(loggerTag.value).e("onRoomKeyEvent() : forwarded_room_key event is missing sender_key field")
return
}
if (null == forwardedRoomKeyContent.senderClaimedEd25519Key) {
- Timber.e("## CRYPTO | forwarded_room_key_event is missing sender_claimed_ed25519_key field")
+ Timber.tag(loggerTag.value).e("forwarded_room_key_event is missing sender_claimed_ed25519_key field")
return
}
keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key
} else {
- Timber.i("## CRYPTO | onRoomKeyEvent(), Adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
+ Timber.tag(loggerTag.value).i("onRoomKeyEvent(), Adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
if (null == senderKey) {
- Timber.e("## onRoomKeyEvent() : key event has no sender key (not encrypted?)")
+ Timber.tag(loggerTag.value).e("## onRoomKeyEvent() : key event has no sender key (not encrypted?)")
return
}
@@ -282,7 +285,7 @@ internal class MXMegolmDecryption(private val userId: String,
keysClaimed = event.getKeysClaimed().toMutableMap()
}
- Timber.i("## CRYPTO | onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}")
+ Timber.tag(loggerTag.value).i("onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}")
val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId,
roomKeyContent.sessionKey,
roomKeyContent.roomId,
@@ -314,7 +317,7 @@ internal class MXMegolmDecryption(private val userId: String,
* @param sessionId the session id
*/
override fun onNewSession(senderKey: String, sessionId: String) {
- Timber.v(" CRYPTO | ON NEW SESSION $sessionId - $senderKey")
+ Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey")
newSessionListener?.onNewSession(null, senderKey, sessionId)
}
@@ -346,10 +349,10 @@ internal class MXMegolmDecryption(private val userId: String,
if (olmSessionResult?.sessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
- Timber.e("no session with this device $deviceId, probably because there were no one-time keys.")
+ Timber.tag(loggerTag.value).e("no session with this device $deviceId, probably because there were no one-time keys.")
return@mapCatching
}
- Timber.i("## CRYPTO | shareKeysWithDevice() : sharing session ${body.sessionId} with device $userId:$deviceId")
+ Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sharing session ${body.sessionId} with device $userId:$deviceId")
val payloadJson = mutableMapOf("type" to EventType.FORWARDED_ROOM_KEY)
runCatching { olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId) }
@@ -360,7 +363,7 @@ internal class MXMegolmDecryption(private val userId: String,
},
{
// TODO
- Timber.e(it, "## CRYPTO | shareKeysWithDevice: failed to get session for request $body")
+ Timber.tag(loggerTag.value).e(it, "shareKeysWithDevice: failed to get session for request $body")
}
)
@@ -368,12 +371,12 @@ internal class MXMegolmDecryption(private val userId: String,
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap()
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
- Timber.i("## CRYPTO | shareKeysWithDevice() : sending ${body.sessionId} to $userId:$deviceId")
+ Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sending ${body.sessionId} to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
try {
sendToDeviceTask.execute(sendToDeviceParams)
} catch (failure: Throwable) {
- Timber.e(failure, "## CRYPTO | shareKeysWithDevice() : Failed to send ${body.sessionId} to $userId:$deviceId")
+ Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice() : Failed to send ${body.sessionId} to $userId:$deviceId")
}
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
index 031bb4e194..389036a1f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
@@ -36,6 +37,8 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
import org.matrix.android.sdk.internal.crypto.model.forEach
+import org.matrix.android.sdk.internal.crypto.model.toDebugCount
+import org.matrix.android.sdk.internal.crypto.model.toDebugString
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
@@ -43,6 +46,8 @@ import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.android.sdk.internal.util.convertToUTF8
import timber.log.Timber
+private val loggerTag = LoggerTag("MXMegolmEncryption", LoggerTag.CRYPTO)
+
internal class MXMegolmEncryption(
// The id of the room we will be sending to.
private val roomId: String,
@@ -51,8 +56,8 @@ internal class MXMegolmEncryption(
private val cryptoStore: IMXCryptoStore,
private val deviceListManager: DeviceListManager,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
- private val userId: String,
- private val deviceId: String,
+ private val myUserId: String,
+ private val myDeviceId: String,
private val sendToDeviceTask: SendToDeviceTask,
private val messageEncrypter: MessageEncrypter,
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
@@ -80,9 +85,10 @@ internal class MXMegolmEncryption(
eventType: String,
userIds: List): Content {
val ts = System.currentTimeMillis()
- Timber.v("## CRYPTO | encryptEventContent : getDevicesInRoom")
+ Timber.tag(loggerTag.value).v("encryptEventContent : getDevicesInRoom")
val devices = getDevicesInRoom(userIds)
- Timber.v("## CRYPTO | encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.map}")
+ Timber.tag(loggerTag.value).d("encrypt event in room=$roomId - devices count in room ${devices.allowedDevices.toDebugCount()}")
+ Timber.tag(loggerTag.value).v("encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.map}")
val outboundSession = ensureOutboundSession(devices.allowedDevices)
return encryptContent(outboundSession, eventType, eventContent)
@@ -91,7 +97,7 @@ internal class MXMegolmEncryption(
// annoyingly we have to serialize again the saved outbound session to store message index :/
// if not we would see duplicate message index errors
olmDevice.storeOutboundGroupSessionForRoom(roomId, outboundSession.sessionId)
- Timber.v("## CRYPTO | encryptEventContent: Finished in ${System.currentTimeMillis() - ts} millis")
+ Timber.tag(loggerTag.value).d("encrypt event in room=$roomId Finished in ${System.currentTimeMillis() - ts} millis")
}
}
@@ -118,13 +124,13 @@ internal class MXMegolmEncryption(
override suspend fun preshareKey(userIds: List) {
val ts = System.currentTimeMillis()
- Timber.v("## CRYPTO | preshareKey : getDevicesInRoom")
+ Timber.tag(loggerTag.value).d("preshareKey started in $roomId ...")
val devices = getDevicesInRoom(userIds)
val outboundSession = ensureOutboundSession(devices.allowedDevices)
notifyWithheldForSession(devices.withHeldDevices, outboundSession)
- Timber.v("## CRYPTO | preshareKey ${System.currentTimeMillis() - ts} millis")
+ Timber.tag(loggerTag.value).d("preshareKey in $roomId done in ${System.currentTimeMillis() - ts} millis")
}
/**
@@ -133,7 +139,7 @@ internal class MXMegolmEncryption(
* @return the session description
*/
private fun prepareNewSessionInRoom(): MXOutboundSessionInfo {
- Timber.v("## CRYPTO | prepareNewSessionInRoom() ")
+ Timber.tag(loggerTag.value).v("prepareNewSessionInRoom() ")
val sessionId = olmDevice.createOutboundGroupSessionForRoom(roomId)
val keysClaimedMap = HashMap()
@@ -153,13 +159,14 @@ internal class MXMegolmEncryption(
* @param devicesInRoom the devices list
*/
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap): MXOutboundSessionInfo {
- Timber.v("## CRYPTO | ensureOutboundSession start")
+ Timber.tag(loggerTag.value).v("ensureOutboundSession roomId:$roomId")
var session = outboundSession
if (session == null ||
// Need to make a brand new session?
session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) ||
// Determine if we have shared with anyone we shouldn't have
session.sharedWithTooManyDevices(devicesInRoom)) {
+ Timber.tag(loggerTag.value).d("roomId:$roomId Starting new megolm session because we need to rotate.")
session = prepareNewSessionInRoom()
outboundSession = session
}
@@ -176,6 +183,8 @@ internal class MXMegolmEncryption(
}
}
}
+ val devicesCount = shareMap.entries.fold(0) { acc, new -> acc + new.value.size }
+ Timber.tag(loggerTag.value).d("roomId:$roomId found $devicesCount devices without megolm session(${session.sessionId})")
shareKey(safeSession, shareMap)
return safeSession
}
@@ -190,7 +199,7 @@ internal class MXMegolmEncryption(
devicesByUsers: Map>) {
// nothing to send, the task is done
if (devicesByUsers.isEmpty()) {
- Timber.v("## CRYPTO | shareKey() : nothing more to do")
+ Timber.tag(loggerTag.value).v("shareKey() : nothing more to do")
return
}
// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
@@ -203,7 +212,7 @@ internal class MXMegolmEncryption(
break
}
}
- Timber.v("## CRYPTO | shareKey() ; sessionId<${session.sessionId}> userId ${subMap.keys}")
+ Timber.tag(loggerTag.value).v("shareKey() ; sessionId<${session.sessionId}> userId ${subMap.keys}")
shareUserDevicesKey(session, subMap)
val remainingDevices = devicesByUsers - subMap.keys
shareKey(session, remainingDevices)
@@ -232,11 +241,11 @@ internal class MXMegolmEncryption(
payload["content"] = submap
var t0 = System.currentTimeMillis()
- Timber.v("## CRYPTO | shareUserDevicesKey() : starts")
+ Timber.tag(loggerTag.value).v("shareUserDevicesKey() : starts")
val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
- Timber.v(
- """## CRYPTO | shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${System.currentTimeMillis() - t0} ms"""
+ Timber.tag(loggerTag.value).v(
+ """shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${System.currentTimeMillis() - t0} ms"""
.trimMargin()
)
val contentMap = MXUsersDevicesMap()
@@ -254,10 +263,11 @@ internal class MXMegolmEncryption(
// MSC 2399
// send withheld m.no_olm: an olm session could not be established.
// This may happen, for example, if the sender was unable to obtain a one-time key from the recipient.
+ Timber.tag(loggerTag.value).v("shareUserDevicesKey() : No Olm Session for $userId:$deviceID mark for withheld")
noOlmToNotify.add(UserDevice(userId, deviceID))
continue
}
- Timber.i("## CRYPTO | shareUserDevicesKey() : Add to share keys contentMap for $userId:$deviceID")
+ Timber.tag(loggerTag.value).v("shareUserDevicesKey() : Add to share keys contentMap for $userId:$deviceID")
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, listOf(sessionResult.deviceInfo)))
haveTargets = true
}
@@ -275,7 +285,7 @@ internal class MXMegolmEncryption(
gossipingEventBuffer.add(
Event(
type = EventType.ROOM_KEY,
- senderId = this.userId,
+ senderId = myUserId,
content = submap.apply {
this["session_key"] = ""
// we add a fake key for trail
@@ -289,17 +299,18 @@ internal class MXMegolmEncryption(
if (haveTargets) {
t0 = System.currentTimeMillis()
- Timber.i("## CRYPTO | shareUserDevicesKey() ${session.sessionId} : has target")
+ Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${session.sessionId} : has target")
+ Timber.tag(loggerTag.value).d("sending to device room key for ${session.sessionId} to ${contentMap.toDebugString()}")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
try {
sendToDeviceTask.execute(sendToDeviceParams)
- Timber.i("## CRYPTO | shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms")
+ Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms")
} catch (failure: Throwable) {
// What to do here...
- Timber.e("## CRYPTO | shareUserDevicesKey() : Failed to share session <${session.sessionId}> with $devicesByUser ")
+ Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share session <${session.sessionId}> with $devicesByUser ")
}
} else {
- Timber.i("## CRYPTO | shareUserDevicesKey() : no need to sharekey")
+ Timber.tag(loggerTag.value).i("shareUserDevicesKey() : no need to share key")
}
if (noOlmToNotify.isNotEmpty()) {
@@ -317,7 +328,8 @@ internal class MXMegolmEncryption(
sessionId: String,
senderKey: String?,
code: WithHeldCode) {
- Timber.i("## CRYPTO | notifyKeyWithHeld() :sending withheld key for $targets session:$sessionId and code $code")
+ Timber.tag(loggerTag.value).d("notifyKeyWithHeld() :sending withheld for session:$sessionId and code $code to" +
+ " ${targets.joinToString { "${it.userId}|${it.deviceId}" }}")
val withHeldContent = RoomKeyWithHeldContent(
roomId = roomId,
senderKey = senderKey,
@@ -336,7 +348,7 @@ internal class MXMegolmEncryption(
try {
sendToDeviceTask.execute(params)
} catch (failure: Throwable) {
- Timber.e("## CRYPTO | notifyKeyWithHeld() : Failed to notify withheld key for $targets session: $sessionId ")
+ Timber.tag(loggerTag.value).e("notifyKeyWithHeld() : Failed to notify withheld key for $targets session: $sessionId ")
}
}
@@ -363,7 +375,7 @@ internal class MXMegolmEncryption(
// Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key.
- map["device_id"] = deviceId
+ map["device_id"] = myDeviceId
session.useCount++
return map
}
@@ -424,9 +436,9 @@ internal class MXMegolmEncryption(
userId: String,
deviceId: String,
senderKey: String): Boolean {
- Timber.i("## Crypto process reshareKey for $sessionId to $userId:$deviceId")
+ Timber.tag(loggerTag.value).i("process reshareKey for $sessionId to $userId:$deviceId")
val deviceInfo = cryptoStore.getUserDevice(userId, deviceId) ?: return false
- .also { Timber.w("## Crypto reshareKey: Device not found") }
+ .also { Timber.tag(loggerTag.value).w("reshareKey: Device not found") }
// Get the chain index of the key we previously sent this device
val wasSessionSharedWithUser = cryptoStore.getSharedSessionInfo(roomId, sessionId, deviceInfo)
@@ -434,13 +446,13 @@ internal class MXMegolmEncryption(
// This session was never shared with this user
// Send a room key with held
notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), sessionId, senderKey, WithHeldCode.UNAUTHORISED)
- Timber.w("## Crypto reshareKey: ERROR : Never shared megolm with this device")
+ Timber.tag(loggerTag.value).w("reshareKey: ERROR : Never shared megolm with this device")
return false
}
// if found chain index should not be null
val chainIndex = wasSessionSharedWithUser.chainIndex ?: return false
.also {
- Timber.w("## Crypto reshareKey: Null chain index")
+ Timber.tag(loggerTag.value).w("reshareKey: Null chain index")
}
val devicesByUser = mapOf(userId to listOf(deviceInfo))
@@ -449,10 +461,10 @@ internal class MXMegolmEncryption(
olmSessionResult?.sessionId // no session with this device, probably because there were no one-time keys.
// ensureOlmSessionsForDevicesAction has already done the logging, so just skip it.
?: return false.also {
- Timber.w("## Crypto reshareKey: no session with this device, probably because there were no one-time keys")
+ Timber.tag(loggerTag.value).w("reshareKey: no session with this device, probably because there were no one-time keys")
}
- Timber.i("[MXMegolmEncryption] reshareKey: sharing keys for session $senderKey|$sessionId:$chainIndex with device $userId:$deviceId")
+ Timber.tag(loggerTag.value).i(" reshareKey: sharing keys for session $senderKey|$sessionId:$chainIndex with device $userId:$deviceId")
val payloadJson = mutableMapOf("type" to EventType.FORWARDED_ROOM_KEY)
@@ -464,7 +476,7 @@ internal class MXMegolmEncryption(
},
{
// TODO
- Timber.e(it, "[MXMegolmEncryption] reshareKey: failed to get session $sessionId|$senderKey|$roomId")
+ Timber.tag(loggerTag.value).e(it, "reshareKey: failed to get session $sessionId|$senderKey|$roomId")
}
)
@@ -472,14 +484,14 @@ internal class MXMegolmEncryption(
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap()
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
- Timber.i("## CRYPTO | reshareKey() : sending session $sessionId to $userId:$deviceId")
+ Timber.tag(loggerTag.value).i("reshareKey() : sending session $sessionId to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
return try {
sendToDeviceTask.execute(sendToDeviceParams)
- Timber.i("## CRYPTO reshareKey() : successfully send <$sessionId> to $userId:$deviceId")
+ Timber.tag(loggerTag.value).i("reshareKey() : successfully send <$sessionId> to $userId:$deviceId")
true
} catch (failure: Throwable) {
- Timber.e(failure, "## CRYPTO reshareKey() : fail to send <$sessionId> to $userId:$deviceId")
+ Timber.tag(loggerTag.value).e(failure, "reshareKey() : fail to send <$sessionId> to $userId:$deviceId")
false
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
index 238d7eed88..136fdc05f5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
@@ -52,8 +52,8 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
cryptoStore = cryptoStore,
deviceListManager = deviceListManager,
ensureOlmSessionsForDevicesAction = ensureOlmSessionsForDevicesAction,
- userId = userId,
- deviceId = deviceId!!,
+ myUserId = userId,
+ myDeviceId = deviceId!!,
sendToDeviceTask = sendToDeviceTask,
messageEncrypter = messageEncrypter,
warnOnUnknownDevicesRepository = warnOnUnknownDevicesRepository,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt
index 9d7f2d9883..662541428e 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt
@@ -129,3 +129,11 @@ inline fun MXUsersDevicesMap.forEach(action: (String, String, T) -> Unit)
}
}
}
+
+internal fun MXUsersDevicesMap.toDebugString() =
+ map.entries.joinToString { "${it.key} [${it.value.keys.joinToString { it }}]" }
+
+internal fun MXUsersDevicesMap.toDebugCount() =
+ map.entries.fold(0) { acc, new ->
+ acc + new.value.keys.size
+ }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
index e40db6af67..bdfe818c62 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.task.Task
+import timber.log.Timber
import javax.inject.Inject
internal interface SendEventTask : Task {
@@ -60,7 +61,9 @@ internal class DefaultSendEventTask @Inject constructor(
)
}
localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENT)
- return response.eventId
+ return response.eventId.also {
+ Timber.d("Event: $it just sent in ${params.event.roomId}")
+ }
} catch (e: Throwable) {
// localEchoRepository.updateSendState(params.event.eventId!!, SendState.UNDELIVERED)
throw e
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt
index 148f727ba7..737c4b4608 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt
@@ -26,20 +26,22 @@ internal object DraftMapper {
fun map(entity: DraftEntity): UserDraft {
return when (entity.draftMode) {
- DraftEntity.MODE_REGULAR -> UserDraft.REGULAR(entity.content)
- DraftEntity.MODE_EDIT -> UserDraft.EDIT(entity.linkedEventId, entity.content)
- DraftEntity.MODE_QUOTE -> UserDraft.QUOTE(entity.linkedEventId, entity.content)
- DraftEntity.MODE_REPLY -> UserDraft.REPLY(entity.linkedEventId, entity.content)
+ DraftEntity.MODE_REGULAR -> UserDraft.Regular(entity.content)
+ DraftEntity.MODE_EDIT -> UserDraft.Edit(entity.linkedEventId, entity.content)
+ DraftEntity.MODE_QUOTE -> UserDraft.Quote(entity.linkedEventId, entity.content)
+ DraftEntity.MODE_REPLY -> UserDraft.Reply(entity.linkedEventId, entity.content)
+ DraftEntity.MODE_VOICE -> UserDraft.Voice(entity.content)
else -> null
- } ?: UserDraft.REGULAR("")
+ } ?: UserDraft.Regular("")
}
fun map(domain: UserDraft): DraftEntity {
return when (domain) {
- is UserDraft.REGULAR -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REGULAR, linkedEventId = "")
- is UserDraft.EDIT -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_EDIT, linkedEventId = domain.linkedEventId)
- is UserDraft.QUOTE -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_QUOTE, linkedEventId = domain.linkedEventId)
- is UserDraft.REPLY -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REPLY, linkedEventId = domain.linkedEventId)
+ is UserDraft.Regular -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_REGULAR, linkedEventId = "")
+ is UserDraft.Edit -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_EDIT, linkedEventId = domain.linkedEventId)
+ is UserDraft.Quote -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_QUOTE, linkedEventId = domain.linkedEventId)
+ is UserDraft.Reply -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_REPLY, linkedEventId = domain.linkedEventId)
+ is UserDraft.Voice -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_VOICE, linkedEventId = "")
}
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/DraftEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/DraftEntity.kt
index 15a5d37963..fd09da4448 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/DraftEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/DraftEntity.kt
@@ -21,7 +21,6 @@ import io.realm.RealmObject
internal open class DraftEntity(var content: String = "",
var draftMode: String = MODE_REGULAR,
var linkedEventId: String = ""
-
) : RealmObject() {
companion object {
@@ -29,5 +28,6 @@ internal open class DraftEntity(var content: String = "",
const val MODE_EDIT = "EDIT"
const val MODE_REPLY = "REPLY"
const val MODE_QUOTE = "QUOTE"
+ const val MODE_VOICE = "VOICE"
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
index bcd30cb54b..836fc4efaf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database.model
import io.realm.RealmObject
import io.realm.annotations.Index
import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
import org.matrix.android.sdk.internal.di.MoshiProvider
@@ -56,10 +57,10 @@ internal open class EventEntity(@Index var eventId: String = "",
companion object
- fun setDecryptionResult(result: MXEventDecryptionResult) {
+ fun setDecryptionResult(result: MXEventDecryptionResult, clearEvent: JsonDict? = null) {
assertIsManaged()
val decryptionResult = OlmDecryptionResult(
- payload = result.clearEvent,
+ payload = clearEvent ?: result.clearEvent,
senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
index 567fec962a..74c5254cf5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
@@ -282,6 +282,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
Timber.e(failure, "## Failed to update file cache")
}
+ // Delete the temporary voice message file
+ if (params.attachment.type == ContentAttachmentData.Type.VOICE_MESSAGE) {
+ context.contentResolver.delete(params.attachment.queryUri, null, null)
+ }
+
val uploadThumbnailResult = dealWithThumbnail(params)
handleSuccess(params,
@@ -302,11 +307,6 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
filesToDelete.forEach {
tryOrNull { it.delete() }
}
-
- // Delete the temporary voice message file
- if (params.attachment.type == ContentAttachmentData.Type.AUDIO && params.attachment.mimeType == MimeTypes.Ogg) {
- context.contentResolver.delete(params.attachment.queryUri, null, null)
- }
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt
index 9d80f27e01..51d305f441 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt
@@ -29,7 +29,6 @@ internal class DefaultEventService @Inject constructor(
override suspend fun getEvent(roomId: String, eventId: String): Event {
val event = getEventTask.execute(GetEventTask.Params(roomId, eventId))
- event.ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
// Fast lane to the call event processors: try to make the incoming call ring faster
if (callEventProcessor.shouldProcessFastLane(event.getClearType())) {
callEventProcessor.processFastLane(event)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
index 69f2542d12..c4630056fb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
@@ -206,10 +206,11 @@ internal class LocalEchoEventFactory @Inject constructor(
fun createMediaEvent(roomId: String, attachment: ContentAttachmentData): Event {
return when (attachment.type) {
- ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment)
- ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment)
- ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment)
- ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment)
+ ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment)
+ ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment)
+ ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false)
+ ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(roomId, attachment, isVoiceMessage = true)
+ ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment)
}
}
@@ -301,8 +302,7 @@ internal class LocalEchoEventFactory @Inject constructor(
return createMessageEvent(roomId, content)
}
- private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event {
- val isVoiceMessage = attachment.waveform != null
+ private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData, isVoiceMessage: Boolean): Event {
val content = MessageAudioContent(
msgType = MessageType.MSGTYPE_AUDIO,
body = attachment.name ?: "audio",
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 1dd27ec104..850529c0c7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -23,6 +23,7 @@ import io.realm.RealmConfiguration
import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
+import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
@@ -33,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.api.util.CancelableBag
import org.matrix.android.sdk.internal.database.RealmSessionProvider
+import org.matrix.android.sdk.internal.database.mapper.EventMapper
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.RoomEntity
@@ -43,6 +45,7 @@ import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.database.query.whereRoomId
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
+import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.util.Debouncer
@@ -73,6 +76,7 @@ internal class DefaultTimeline(
private val eventDecryptor: TimelineEventDecryptor,
private val realmSessionProvider: RealmSessionProvider,
private val loadRoomMembersTask: LoadRoomMembersTask,
+ private val threadsAwarenessHandler: ThreadsAwarenessHandler,
private val readReceiptHandler: ReadReceiptHandler
) : Timeline,
TimelineInput.Listener,
@@ -595,6 +599,10 @@ internal class DefaultTimeline(
} else {
nextDisplayIndex = offsetIndex + 1
}
+
+ // Prerequisite to in order for the ThreadsAwarenessHandler to work properly
+ fetchRootThreadEventsIfNeeded(offsetResults)
+
offsetResults.forEach { eventEntity ->
val timelineEvent = buildTimelineEvent(eventEntity)
@@ -619,6 +627,20 @@ internal class DefaultTimeline(
return offsetResults.size
}
+ /**
+ * This function is responsible to fetch and store the root event of a thread event
+ * in order to be able to display the event to the user appropriately
+ */
+ private fun fetchRootThreadEventsIfNeeded(offsetResults: RealmResults) = runBlocking {
+ val eventEntityList = offsetResults
+ .mapNotNull {
+ it?.root
+ }.map {
+ EventMapper.map(it)
+ }
+ threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(eventEntityList)
+ }
+
private fun buildTimelineEvent(eventEntity: TimelineEventEntity): TimelineEvent {
return timelineEventMapper.map(
timelineEventEntity = eventEntity,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
index 47e8f7e3a3..75e7e774df 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
@@ -38,6 +38,7 @@ import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
+import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
import org.matrix.android.sdk.internal.task.TaskExecutor
internal class DefaultTimelineService @AssistedInject constructor(
@@ -52,6 +53,7 @@ internal class DefaultTimelineService @AssistedInject constructor(
private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
private val timelineEventMapper: TimelineEventMapper,
private val loadRoomMembersTask: LoadRoomMembersTask,
+ private val threadsAwarenessHandler: ThreadsAwarenessHandler,
private val readReceiptHandler: ReadReceiptHandler
) : TimelineService {
@@ -75,6 +77,7 @@ internal class DefaultTimelineService @AssistedInject constructor(
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
realmSessionProvider = realmSessionProvider,
loadRoomMembersTask = loadRoomMembersTask,
+ threadsAwarenessHandler = threadsAwarenessHandler,
readReceiptHandler = readReceiptHandler
)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
index cbbc54e90d..9ede2f6562 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
@@ -59,6 +59,8 @@ internal class DefaultGetEventTask @Inject constructor(
}
}
+ event.ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
+
return event
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
index 721dae0b1b..75d02dfd98 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
import timber.log.Timber
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
@@ -34,7 +35,8 @@ import javax.inject.Inject
internal class TimelineEventDecryptor @Inject constructor(
@SessionDatabase
private val realmConfiguration: RealmConfiguration,
- private val cryptoService: CryptoService
+ private val cryptoService: CryptoService,
+ private val threadsAwarenessHandler: ThreadsAwarenessHandler
) {
private val newSessionListener = object : NewSessionListener {
@@ -106,10 +108,19 @@ internal class TimelineEventDecryptor @Inject constructor(
val result = cryptoService.decryptEvent(request.event, timelineId)
Timber.v("Successfully decrypted event ${event.eventId}")
realm.executeTransaction {
- val eventId = event.eventId ?: ""
- EventEntity.where(it, eventId = eventId)
+ val eventId = event.eventId ?: return@executeTransaction
+ val eventEntity = EventEntity
+ .where(it, eventId = eventId)
.findFirst()
- ?.setDecryptionResult(result)
+
+ eventEntity?.apply {
+ val decryptedPayload = threadsAwarenessHandler.handleIfNeededDuringDecryption(
+ it,
+ roomId = event.roomId,
+ event,
+ result)
+ setDecryptionResult(result, decryptedPayload)
+ }
}
} catch (e: MXCryptoError) {
Timber.v("Failed to decrypt event ${event.eventId} : ${e.localizedMessage}")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
index 8fd969e373..f178074507 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
@@ -41,6 +41,7 @@ import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler
import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler
import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler
import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler
+import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
import org.matrix.android.sdk.internal.util.awaitTransaction
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import timber.log.Timber
@@ -65,6 +66,7 @@ internal class SyncResponseHandler @Inject constructor(
private val tokenStore: SyncTokenStore,
private val processEventForPushTask: ProcessEventForPushTask,
private val pushRuleService: PushRuleService,
+ private val threadsAwarenessHandler: ThreadsAwarenessHandler,
private val presenceSyncHandler: PresenceSyncHandler
) {
@@ -97,6 +99,10 @@ internal class SyncResponseHandler @Inject constructor(
Timber.v("Finish handling toDevice in $it ms")
}
val aggregator = SyncResponsePostTreatmentAggregator()
+
+ // Prerequisite for thread events handling in RoomSyncHandler
+ threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(syncResponse)
+
// Start one big transaction
monarchy.awaitTransaction { realm ->
measureTimeMillis {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
index c5ec34176c..f299d3effa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
@@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.sync.handler
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
@@ -32,6 +33,8 @@ import org.matrix.android.sdk.internal.session.initsync.ProgressReporter
import timber.log.Timber
import javax.inject.Inject
+private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
+
internal class CryptoSyncHandler @Inject constructor(private val cryptoService: DefaultCryptoService,
private val verificationService: DefaultVerificationService) {
@@ -40,11 +43,11 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
toDevice.events?.forEachIndexed { index, event ->
progressReporter?.reportProgress(index * 100F / total)
// Decrypt event if necessary
- Timber.i("## CRYPTO | To device event from ${event.senderId} of type:${event.type}")
+ Timber.tag(loggerTag.value).i("To device event from ${event.senderId} of type:${event.type}")
decryptToDeviceEvent(event, null)
if (event.getClearType() == EventType.MESSAGE &&
event.getClearContent()?.toModel()?.msgType == "m.bad.encrypted") {
- Timber.e("## CRYPTO | handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
+ Timber.tag(loggerTag.value).e("handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
} else {
verificationService.onToDeviceEvent(event)
cryptoService.onToDeviceEvent(event)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index 6c350fd74d..70fc598128 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -76,6 +76,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
private val cryptoService: DefaultCryptoService,
private val roomMemberEventHandler: RoomMemberEventHandler,
private val roomTypingUsersHandler: RoomTypingUsersHandler,
+ private val threadsAwarenessHandler: ThreadsAwarenessHandler,
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
@UserId private val userId: String,
private val timelineInput: TimelineInput) {
@@ -363,10 +364,17 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
}
eventIds.add(event.eventId)
- if (event.isEncrypted() && insertType != EventInsertType.INITIAL_SYNC) {
+ val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
+
+ if (event.isEncrypted() && !isInitialSync) {
decryptIfNeeded(event, roomId)
}
+ threadsAwarenessHandler.handleIfNeeded(
+ realm = realm,
+ roomId = roomId,
+ event = event)
+
val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
if (event.stateKey != null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
new file mode 100644
index 0000000000..767a967522
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.sync.handler.room
+
+import com.zhuinden.monarchy.Monarchy
+import io.realm.Realm
+import org.matrix.android.sdk.api.session.crypto.CryptoService
+import org.matrix.android.sdk.api.session.crypto.MXCryptoError
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.RelationType
+import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
+import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
+import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.api.session.sync.model.SyncResponse
+import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
+import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
+import org.matrix.android.sdk.internal.database.mapper.EventMapper
+import org.matrix.android.sdk.internal.database.mapper.toEntity
+import org.matrix.android.sdk.internal.database.model.EventEntity
+import org.matrix.android.sdk.internal.database.model.EventInsertType
+import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
+import org.matrix.android.sdk.internal.database.query.where
+import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
+import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
+import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask
+import org.matrix.android.sdk.internal.util.awaitTransaction
+import javax.inject.Inject
+
+/**
+ * This handler is responsible for a smooth threads migration. It will map all incoming
+ * threads as replies. So a device without threads enabled/updated will be able to view
+ * threads response as replies to the original message
+ */
+internal class ThreadsAwarenessHandler @Inject constructor(
+ private val permalinkFactory: PermalinkFactory,
+ private val cryptoService: CryptoService,
+ @SessionDatabase private val monarchy: Monarchy,
+ private val getEventTask: GetEventTask
+) {
+
+ /**
+ * Fetch root thread events if they are missing from the local storage
+ * @param syncResponse the sync response
+ */
+ suspend fun fetchRootThreadEventsIfNeeded(syncResponse: SyncResponse) {
+ val handlingStrategy = syncResponse.rooms?.join?.let {
+ RoomSyncHandler.HandlingStrategy.JOINED(it)
+ }
+ if (handlingStrategy !is RoomSyncHandler.HandlingStrategy.JOINED) return
+ val eventList = handlingStrategy.data
+ .mapNotNull { (roomId, roomSync) ->
+ roomSync.timeline?.events?.map {
+ it.copy(roomId = roomId)
+ }
+ }.flatten()
+
+ fetchRootThreadEventsIfNeeded(eventList)
+ }
+
+ /**
+ * Fetch root thread events if they are missing from the local storage
+ * @param eventList a list with the events to examine
+ */
+ suspend fun fetchRootThreadEventsIfNeeded(eventList: List) {
+ if (eventList.isNullOrEmpty()) return
+
+ val threadsToFetch = emptyMap().toMutableMap()
+ Realm.getInstance(monarchy.realmConfiguration).use { realm ->
+ eventList.asSequence()
+ .filter {
+ isThreadEvent(it) && it.roomId != null
+ }.mapNotNull { event ->
+ getRootThreadEventId(event)?.let {
+ Pair(it, event.roomId!!)
+ }
+ }.forEach { (rootThreadEventId, roomId) ->
+ EventEntity.where(realm, rootThreadEventId).findFirst() ?: run { threadsToFetch[rootThreadEventId] = roomId }
+ }
+ }
+ fetchThreadsEvents(threadsToFetch)
+ }
+
+ /**
+ * Fetch multiple unique events using the fetchEvent function
+ */
+ private suspend fun fetchThreadsEvents(threadsToFetch: Map) {
+ val eventEntityList = threadsToFetch.mapNotNull { (eventId, roomId) ->
+ fetchEvent(eventId, roomId)?.let {
+ it.toEntity(roomId, SendState.SYNCED, it.ageLocalTs)
+ }
+ }
+
+ if (eventEntityList.isNullOrEmpty()) return
+
+ // Transaction should be done on its own thread, like below
+ monarchy.awaitTransaction { realm ->
+ eventEntityList.forEach {
+ it.copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC)
+ }
+ }
+ }
+
+ /**
+ * This function will fetch the event from the homeserver, this is mandatory when the
+ * initial thread message is too old and is not saved in the device, so in order to
+ * construct the "reply to" format we need to know the event thread.
+ * @return the Event or null otherwise
+ */
+ private suspend fun fetchEvent(eventId: String, roomId: String): Event? {
+ return runCatching {
+ getEventTask.execute(GetEventTask.Params(roomId = roomId, eventId = eventId))
+ }.fold(
+ onSuccess = {
+ it
+ },
+ onFailure = {
+ null
+ })
+ }
+
+ /**
+ * Handle events mainly coming from the RoomSyncHandler
+ */
+ fun handleIfNeeded(realm: Realm,
+ roomId: String,
+ event: Event) {
+ val payload = transformThreadToReplyIfNeeded(
+ realm = realm,
+ roomId = roomId,
+ event = event,
+ decryptedResult = event.mxDecryptionResult?.payload) ?: return
+
+ event.mxDecryptionResult = event.mxDecryptionResult?.copy(payload = payload)
+ }
+
+ /**
+ * Handle events while they are being decrypted
+ */
+ fun handleIfNeededDuringDecryption(realm: Realm,
+ roomId: String?,
+ event: Event,
+ result: MXEventDecryptionResult): JsonDict? {
+ return transformThreadToReplyIfNeeded(
+ realm = realm,
+ roomId = roomId,
+ event = event,
+ decryptedResult = result.clearEvent)
+ }
+
+ /**
+ * If the event is a thread event then transform/enhance it to a visual Reply Event,
+ * If the event is not a thread event, null value will be returned
+ * If there is an error (ex. the root/origin thread event is not found), null willl be returend
+ */
+ private fun transformThreadToReplyIfNeeded(realm: Realm, roomId: String?, event: Event, decryptedResult: JsonDict?): JsonDict? {
+ roomId ?: return null
+ if (!isThreadEvent(event)) return null
+ val rootThreadEventId = getRootThreadEventId(event) ?: return null
+ val payload = decryptedResult?.toMutableMap() ?: return null
+ val body = getValueFromPayload(payload, "body") ?: return null
+ val msgType = getValueFromPayload(payload, "msgtype") ?: return null
+ val rootThreadEvent = getEventFromDB(realm, rootThreadEventId) ?: return null
+ val rootThreadEventSenderId = rootThreadEvent.senderId ?: return null
+
+ decryptIfNeeded(rootThreadEvent, roomId)
+
+ val rootThreadEventBody = getValueFromPayload(rootThreadEvent.mxDecryptionResult?.payload?.toMutableMap(), "body")
+
+ val permalink = permalinkFactory.createPermalink(roomId, rootThreadEventId, false)
+ val userLink = permalinkFactory.createPermalink(rootThreadEventSenderId, false) ?: ""
+
+ val replyFormatted = LocalEchoEventFactory.REPLY_PATTERN.format(
+ permalink,
+ userLink,
+ rootThreadEventSenderId,
+ // Remove inner mx_reply tags if any
+ rootThreadEventBody,
+ body)
+
+ val messageTextContent = MessageTextContent(
+ msgType = msgType,
+ format = MessageFormat.FORMAT_MATRIX_HTML,
+ body = body,
+ formattedBody = replyFormatted
+ ).toContent()
+
+ payload["content"] = messageTextContent
+
+ return payload
+ }
+
+ /**
+ * Decrypt the event
+ */
+
+ private fun decryptIfNeeded(event: Event, roomId: String) {
+ try {
+ if (!event.isEncrypted() || event.mxDecryptionResult != null) return
+
+ // Event from sync does not have roomId, so add it to the event first
+ val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
+ event.mxDecryptionResult = OlmDecryptionResult(
+ payload = result.clearEvent,
+ senderKey = result.senderCurve25519Key,
+ keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
+ forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+ )
+ } catch (e: MXCryptoError) {
+ if (e is MXCryptoError.Base) {
+ event.mCryptoError = e.errorType
+ event.mCryptoErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription
+ }
+ }
+ }
+
+ /**
+ * Try to get the event form the local DB, if the event does not exist null
+ * will be returned
+ */
+ private fun getEventFromDB(realm: Realm, eventId: String): Event? {
+ val eventEntity = EventEntity.where(realm, eventId = eventId).findFirst() ?: return null
+ return EventMapper.map(eventEntity)
+ }
+
+ /**
+ * Returns True if the event is a thread
+ * @param event
+ */
+ private fun isThreadEvent(event: Event): Boolean =
+ event.content.toModel()?.relatesTo?.type == RelationType.THREAD
+
+ /**
+ * Returns the root thread eventId or null otherwise
+ * @param event
+ */
+ private fun getRootThreadEventId(event: Event): String? =
+ event.content.toModel()?.relatesTo?.eventId
+
+ @Suppress("UNCHECKED_CAST")
+ private fun getValueFromPayload(payload: JsonDict?, key: String): String? {
+ val content = payload?.get("content") as? JsonDict
+ return content?.get(key) as? String
+ }
+}
diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt
index 2b8c1d11e6..bb21196858 100644
--- a/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt
+++ b/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt
@@ -20,6 +20,8 @@ import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.provider.ContactsContract
+import androidx.core.database.getIntOrNull
+import androidx.core.database.getStringOrNull
import im.vector.lib.multipicker.entity.MultiPickerContactType
import im.vector.lib.multipicker.utils.getColumnIndexOrNull
@@ -54,9 +56,9 @@ class ContactPicker : Picker() {
val nameColumn = cursor.getColumnIndexOrNull(ContactsContract.Contacts.DISPLAY_NAME) ?: return@use
val photoUriColumn = cursor.getColumnIndexOrNull(ContactsContract.Contacts.PHOTO_URI) ?: return@use
- val contactId = cursor.getInt(idColumn)
- var name = cursor.getString(nameColumn)
- val photoUri = cursor.getString(photoUriColumn)
+ val contactId = cursor.getIntOrNull(idColumn) ?: return@use
+ var name = cursor.getStringOrNull(nameColumn) ?: return@use
+ val photoUri = cursor.getStringOrNull(photoUriColumn)
val phoneNumberList = mutableListOf()
val emailList = mutableListOf()
@@ -78,8 +80,8 @@ class ContactPicker : Picker() {
val data1ColumnIndex = innerCursor.getColumnIndexOrNull(ContactsContract.Data.DATA1) ?: return@inner
while (innerCursor.moveToNext()) {
- val mimeType = innerCursor.getString(mimeTypeColumnIndex)
- val contactData = innerCursor.getString(data1ColumnIndex)
+ val mimeType = innerCursor.getStringOrNull(mimeTypeColumnIndex)
+ val contactData = innerCursor.getStringOrNull(data1ColumnIndex) ?: continue
if (mimeType == ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) {
name = contactData
@@ -121,7 +123,7 @@ class ContactPicker : Picker() {
)?.use { cursor ->
return if (cursor.moveToFirst()) {
cursor.getColumnIndexOrNull(ContactsContract.RawContacts._ID)
- ?.let { cursor.getInt(it) }
+ ?.let { cursor.getIntOrNull(it) }
} else null
}
}
diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt
index 8e6c97f2f8..2e3148c9de 100644
--- a/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt
+++ b/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt
@@ -19,6 +19,8 @@ package im.vector.lib.multipicker
import android.content.Context
import android.content.Intent
import android.provider.OpenableColumns
+import androidx.core.database.getLongOrNull
+import androidx.core.database.getStringOrNull
import im.vector.lib.multipicker.entity.MultiPickerBaseType
import im.vector.lib.multipicker.entity.MultiPickerFileType
import im.vector.lib.multipicker.utils.getColumnIndexOrNull
@@ -53,8 +55,8 @@ class FilePicker : Picker() {
val nameColumn = cursor.getColumnIndexOrNull(OpenableColumns.DISPLAY_NAME) ?: return@use null
val sizeColumn = cursor.getColumnIndexOrNull(OpenableColumns.SIZE) ?: return@use null
if (cursor.moveToFirst()) {
- val name = cursor.getString(nameColumn)
- val size = cursor.getLong(sizeColumn)
+ val name = cursor.getStringOrNull(nameColumn)
+ val size = cursor.getLongOrNull(sizeColumn) ?: 0
MultiPickerFileType(
name,
diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt b/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt
index ead6b138be..9574859f77 100644
--- a/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt
+++ b/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt
@@ -20,6 +20,8 @@ import android.content.Context
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.provider.MediaStore
+import androidx.core.database.getLongOrNull
+import androidx.core.database.getStringOrNull
import im.vector.lib.multipicker.entity.MultiPickerAudioType
import im.vector.lib.multipicker.entity.MultiPickerImageType
import im.vector.lib.multipicker.entity.MultiPickerVideoType
@@ -42,8 +44,8 @@ internal fun Uri.toMultiPickerImageType(context: Context): MultiPickerImageType?
val sizeColumn = cursor.getColumnIndexOrNull(MediaStore.Images.Media.SIZE) ?: return@use null
if (cursor.moveToNext()) {
- val name = cursor.getString(nameColumn)
- val size = cursor.getLong(sizeColumn)
+ val name = cursor.getStringOrNull(nameColumn)
+ val size = cursor.getLongOrNull(sizeColumn) ?: 0
val bitmap = ImageUtils.getBitmap(context, this)
val orientation = ImageUtils.getOrientation(context, this)
@@ -80,8 +82,8 @@ internal fun Uri.toMultiPickerVideoType(context: Context): MultiPickerVideoType?
val sizeColumn = cursor.getColumnIndexOrNull(MediaStore.Video.Media.SIZE) ?: return@use null
if (cursor.moveToNext()) {
- val name = cursor.getString(nameColumn)
- val size = cursor.getLong(sizeColumn)
+ val name = cursor.getStringOrNull(nameColumn)
+ val size = cursor.getLongOrNull(sizeColumn) ?: 0
var duration = 0L
var width = 0
var height = 0
@@ -133,8 +135,8 @@ fun Uri.toMultiPickerAudioType(context: Context): MultiPickerAudioType? {
val sizeColumn = cursor.getColumnIndexOrNull(MediaStore.Audio.Media.SIZE) ?: return@use null
if (cursor.moveToNext()) {
- val name = cursor.getString(nameColumn)
- val size = cursor.getLong(sizeColumn)
+ val name = cursor.getStringOrNull(nameColumn)
+ val size = cursor.getLongOrNull(sizeColumn) ?: 0
var duration = 0L
context.contentResolver.openFileDescriptor(this, "r")?.use { pfd ->
diff --git a/tools/jitsi/build_jisti_libs.sh b/tools/jitsi/build_jisti_libs.sh
index 34051d463c..e352575775 100755
--- a/tools/jitsi/build_jisti_libs.sh
+++ b/tools/jitsi/build_jisti_libs.sh
@@ -25,8 +25,8 @@ cd jitsi-meet
# This is commit after version 2.2.2, which does not compile
# git checkout 5a934c071a5cbe64de275a25d0ed62d8193cdd03
-# Version android-sdk-3.1.0, commit 7a64bf006ea027b77564d8847570e1ac46ff0ec0
-git checkout android-sdk-3.1.0
+# Version android-sdk-3.10.0, commit 99e56e229dfa3c490096e37c3e5b76d2a3f23e32
+git checkout android-sdk-3.10.0
echo
echo "##################################################"
diff --git a/vector/build.gradle b/vector/build.gradle
index 65903d424a..1a9e530093 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -15,7 +15,7 @@ kapt {
// Note: 2 digits max for each value
ext.versionMajor = 1
ext.versionMinor = 3
-ext.versionPatch = 8
+ext.versionPatch = 9
ext.scVersion = 47
@@ -386,7 +386,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.37'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.38'
// FlowBinding
implementation libs.github.flowBinding
@@ -485,10 +485,10 @@ dependencies {
// WebRTC
// org.webrtc:google-webrtc is for development purposes only
// implementation 'org.webrtc:google-webrtc:1.0.+'
- implementation('com.facebook.react:react-native-webrtc:1.87.3-jitsi-6624067@aar')
+ implementation('com.facebook.react:react-native-webrtc:1.92.1-jitsi-9093212@aar')
// Jitsi
- implementation('org.jitsi.react:jitsi-meet-sdk:3.1.0') {
+ implementation('org.jitsi.react:jitsi-meet-sdk:3.10.0') {
exclude group: 'com.google.firebase'
exclude group: 'com.google.android.gms'
exclude group: 'com.android.installreferrer'
diff --git a/vector/lint.xml b/vector/lint.xml
index e534bdc355..9d9b208df7 100644
--- a/vector/lint.xml
+++ b/vector/lint.xml
@@ -84,4 +84,7 @@
+
+
+
diff --git a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
index fbcb9b8cb3..59982c72aa 100644
--- a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
+++ b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
@@ -68,6 +68,18 @@ object EspressoHelper {
}
}
+fun withRetry(attempts: Int = 3, action: () -> Unit) {
+ runCatching { action() }.onFailure {
+ val remainingAttempts = attempts - 1
+ if (remainingAttempts <= 0) {
+ throw it
+ } else {
+ Thread.sleep(500)
+ withRetry(remainingAttempts, action)
+ }
+ }
+}
+
fun getString(@StringRes id: Int): String {
return EspressoHelper.getCurrentActivity()!!.resources.getString(id)
}
@@ -235,11 +247,16 @@ fun clickOnAndGoBack(@StringRes name: Int, block: () -> Unit) {
Espresso.pressBack()
}
-inline fun > interactWithSheet(contentMatcher: Matcher, noinline block: () -> Unit = {}) {
+inline fun > interactWithSheet(
+ contentMatcher: Matcher,
+ @BottomSheetBehavior.State openState: Int = BottomSheetBehavior.STATE_EXPANDED,
+ @BottomSheetBehavior.State exitState: Int = BottomSheetBehavior.STATE_HIDDEN,
+ noinline block: () -> Unit = {}
+) {
waitUntilViewVisible(contentMatcher)
val behaviour = (EspressoHelper.getBottomSheetDialog()!!.dialog as BottomSheetDialog).behavior
- withIdlingResource(BottomSheetResource(behaviour, BottomSheetBehavior.STATE_EXPANDED), block)
- withIdlingResource(BottomSheetResource(behaviour, BottomSheetBehavior.STATE_HIDDEN)) {}
+ withIdlingResource(BottomSheetResource(behaviour, openState), block)
+ withIdlingResource(BottomSheetResource(behaviour, exitState)) {}
}
class BottomSheetResource(
diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt b/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt
index 7744d4b720..05f1ca2815 100644
--- a/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt
+++ b/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt
@@ -18,17 +18,25 @@ package im.vector.app.espresso.tools
import android.app.Activity
import android.view.View
-import androidx.test.espresso.Espresso
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.assertion.ViewAssertions
+import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers
import im.vector.app.activityIdlingResource
import im.vector.app.waitForView
import im.vector.app.withIdlingResource
import org.hamcrest.Matcher
+import org.hamcrest.Matchers.not
inline fun waitUntilActivityVisible(noinline block: (() -> Unit) = {}) {
withIdlingResource(activityIdlingResource(T::class.java), block)
}
fun waitUntilViewVisible(viewMatcher: Matcher) {
- Espresso.onView(ViewMatchers.isRoot()).perform(waitForView(viewMatcher))
+ onView(ViewMatchers.isRoot()).perform(waitForView(viewMatcher))
+}
+
+fun waitUntilDialogVisible(viewMatcher: Matcher) {
+ onView(viewMatcher).inRoot(isDialog()).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ waitUntilViewVisible(viewMatcher)
}
diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
index f998a9f23c..e4a536d422 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
@@ -16,6 +16,7 @@
package im.vector.app.ui
+import androidx.test.espresso.IdlingPolicies
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
@@ -30,6 +31,7 @@ import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import java.util.UUID
+import java.util.concurrent.TimeUnit
/**
* This test aim to open every possible screen of the application
@@ -51,6 +53,8 @@ class UiAllScreensSanityTest {
// 2021-04-08 Testing 429 change
@Test
fun allScreensTest() {
+ IdlingPolicies.setMasterPolicyTimeout(120, TimeUnit.SECONDS)
+
// Create an account
val userId = "UiTest_" + UUID.randomUUID().toString()
elementRobot.signUp(userId)
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
index a3bc5b26fc..99af7851ef 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
@@ -28,6 +28,7 @@ import com.adevinta.android.barista.interaction.BaristaDrawerInteractions.openDr
import im.vector.app.EspressoHelper
import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilActivityVisible
+import im.vector.app.espresso.tools.waitUntilDialogVisible
import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.createdirect.CreateDirectRoomActivity
import im.vector.app.features.home.HomeActivity
@@ -104,17 +105,19 @@ class ElementRobot {
}.isSuccess
if (expectSignOutWarning != isShowingSignOutWarning) {
- Timber.w("Unexpected sign out flow, expected warning to be: ${expectSignOutWarning.toWarningType()} but was ${isShowingSignOutWarning.toWarningType()}")
+ val expected = expectSignOutWarning.toWarningType()
+ val actual = isShowingSignOutWarning.toWarningType()
+ Timber.w("Unexpected sign out flow, expected warning to be: $expected but was $actual")
}
if (isShowingSignOutWarning) {
// We have sent a message in a e2e room, accept to loose it
clickOn(R.id.exitAnywayButton)
// Dark pattern
- waitUntilViewVisible(withId(android.R.id.button2))
+ waitUntilDialogVisible(withId(android.R.id.button2))
clickDialogNegativeButton()
} else {
- waitUntilViewVisible(withId(android.R.id.button1))
+ waitUntilDialogVisible(withId(android.R.id.button1))
clickDialogPositiveButton()
}
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt
index fd579c0d9f..934c6c76a1 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt
@@ -17,9 +17,13 @@
package im.vector.app.ui.robot
import androidx.test.espresso.Espresso.pressBack
+import androidx.test.espresso.matcher.ViewMatchers.withText
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
import com.adevinta.android.barista.interaction.BaristaListInteractions.clickListItem
+import com.google.android.material.bottomsheet.BottomSheetBehavior
import im.vector.app.R
+import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
+import im.vector.app.interactWithSheet
import java.lang.Thread.sleep
class MessageMenuRobot(
@@ -36,7 +40,9 @@ class MessageMenuRobot(
fun editHistory() {
clickOn(R.string.message_view_edit_history)
- pressBack()
+ interactWithSheet(withText(R.string.message_edits), openState = BottomSheetBehavior.STATE_COLLAPSED) {
+ pressBack()
+ }
autoClosed = true
}
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt
index fef5d4a1a2..8b87abadab 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt
@@ -26,6 +26,7 @@ import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions.assert
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo
import im.vector.app.R
+import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.waitForView
class OnboardingRobot {
@@ -42,6 +43,7 @@ class OnboardingRobot {
userId: String,
password: String,
homeServerUrl: String) {
+ waitUntilViewVisible(withId(R.id.loginSplashSubmit))
assertDisplayed(R.id.loginSplashSubmit, R.string.login_splash_submit)
clickOn(R.id.loginSplashSubmit)
assertDisplayed(R.id.loginServerTitle, R.string.login_server_title)
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt
index 24fe5adf64..53d6c16bb7 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt
@@ -30,12 +30,15 @@ import com.adevinta.android.barista.interaction.BaristaClickInteractions.longCli
import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo
import com.adevinta.android.barista.interaction.BaristaMenuClickInteractions.clickMenu
import com.adevinta.android.barista.interaction.BaristaMenuClickInteractions.openMenu
+import com.google.android.material.bottomsheet.BottomSheetBehavior
import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
+import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.app.features.reactions.data.EmojiDataSource
import im.vector.app.interactWithSheet
import im.vector.app.waitForView
+import im.vector.app.withRetry
import java.lang.Thread.sleep
class RoomDetailRobot {
@@ -68,10 +71,14 @@ class RoomDetailRobot {
openMessageMenu(message) {
addQuickReaction(quickReaction)
}
+ println("Open reactions bottom sheet")
// Open reactions
- longClickOn(quickReaction)
+ longClickReaction(quickReaction)
// wait for bottom sheet
- pressBack()
+ interactWithSheet(withText(R.string.reactions), openState = BottomSheetBehavior.STATE_COLLAPSED) {
+ pressBack()
+ }
+ println("Room Detail Robot: Open reaction from emoji picker")
// Test add reaction
openMessageMenu(message) {
addReactionFromEmojiPicker()
@@ -81,16 +88,24 @@ class RoomDetailRobot {
edit()
}
// TODO Cancel action
- writeTo(R.id.composerEditText, "Hello universe!")
+ val edit = "Hello universe - long message to avoid espresso tapping edited!"
+ writeTo(R.id.composerEditText, edit)
// Wait a bit for the keyboard layout to update
waitUntilViewVisible(withId(R.id.sendButton))
clickOn(R.id.sendButton)
// Wait for the UI to update
- waitUntilViewVisible(withText("Hello universe! (edited)"))
+ waitUntilViewVisible(withText("$edit (edited)"))
// Open edit history
- openMessageMenu("Hello universe! (edited)") {
+ openMessageMenu("$edit (edited)") {
editHistory()
}
+ waitUntilViewVisible(withId(R.id.composerEditText))
+ }
+
+ private fun longClickReaction(quickReaction: String) {
+ withRetry {
+ longClickOn(quickReaction)
+ }
}
fun openMessageMenu(message: String, block: MessageMenuRobot.() -> Unit) {
@@ -111,7 +126,7 @@ class RoomDetailRobot {
}
fun openSettings(block: RoomSettingsRobot.() -> Unit) {
- clickOn(R.id.roomToolbarTitleView)
+ clickMenu(R.id.timeline_setting)
waitForView(withId(R.id.roomProfileAvatarView))
sleep(1000)
block(RoomSettingsRobot())
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt
index 15186fe0c8..2c57dd058d 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt
@@ -26,6 +26,7 @@ import com.adevinta.android.barista.interaction.BaristaDialogInteractions.clickD
import com.adevinta.android.barista.interaction.BaristaListInteractions.clickListItem
import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilActivityVisible
+import im.vector.app.espresso.tools.waitUntilDialogVisible
import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.roommemberprofile.RoomMemberProfileActivity
@@ -78,9 +79,9 @@ class RoomSettingsRobot {
// Room permissions
clickListItem(R.id.matrixProfileRecyclerView, 17)
- waitUntilViewVisible(withText(R.string.room_permissions_title))
+ waitUntilViewVisible(withText(R.string.room_permissions_change_room_avatar))
clickOn(R.string.room_permissions_change_room_avatar)
- waitUntilViewVisible(withId(android.R.id.button2))
+ waitUntilDialogVisible(withId(android.R.id.button2))
clickDialogNegativeButton()
waitUntilViewVisible(withText(R.string.room_permissions_title))
// Toggle
@@ -95,7 +96,7 @@ class RoomSettingsRobot {
private fun leaveRoom(block: DialogRobot.() -> Unit) {
clickListItem(R.id.matrixProfileRecyclerView, 13)
- waitUntilViewVisible(withId(android.R.id.button2))
+ waitUntilDialogVisible(withId(android.R.id.button2))
val dialogRobot = DialogRobot()
block(dialogRobot)
if (dialogRobot.returnedToPreviousScreen) {
@@ -135,7 +136,7 @@ class RoomSettingsRobot {
// Role
clickListItem(R.id.matrixProfileRecyclerView, 3)
- waitUntilViewVisible(withId(android.R.id.button2))
+ waitUntilDialogVisible(withId(android.R.id.button2))
clickDialogNegativeButton()
waitUntilViewVisible(withId(R.id.matrixProfileRecyclerView))
pressBack()
diff --git a/vector/src/debug/AndroidManifest.xml b/vector/src/debug/AndroidManifest.xml
index 8ffcec6bc1..dba8440602 100644
--- a/vector/src/debug/AndroidManifest.xml
+++ b/vector/src/debug/AndroidManifest.xml
@@ -5,6 +5,7 @@
+
diff --git a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
index 960994b169..64de648a23 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
@@ -35,6 +35,7 @@ import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.toast
import im.vector.app.databinding.ActivityDebugMenuBinding
import im.vector.app.features.debug.sas.DebugSasEmojiActivity
+import im.vector.app.features.debug.settings.DebugPrivateSettingsActivity
import im.vector.app.features.qrcode.QrCodeScannerActivity
import im.vector.lib.ui.styles.debug.DebugMaterialThemeDarkDefaultActivity
import im.vector.lib.ui.styles.debug.DebugMaterialThemeDarkTestActivity
@@ -75,6 +76,7 @@ class DebugMenuActivity : VectorBaseActivity() {
}
private fun setupViews() {
+ views.debugPrivateSetting.setOnClickListener { openPrivateSettings() }
views.debugTestTextViewLink.setOnClickListener { testTextViewLink() }
views.debugOpenButtonStylesLight.setOnClickListener {
startActivity(Intent(this, DebugVectorButtonStylesLightActivity::class.java))
@@ -115,6 +117,10 @@ class DebugMenuActivity : VectorBaseActivity() {
}
}
+ private fun openPrivateSettings() {
+ startActivity(Intent(this, DebugPrivateSettingsActivity::class.java))
+ }
+
private fun renderQrCode(text: String) {
views.debugQrCode.setData(text)
}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/di/MavericksViewModelDebugModule.kt b/vector/src/debug/java/im/vector/app/features/debug/di/MavericksViewModelDebugModule.kt
new file mode 100644
index 0000000000..8be4470b3f
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/di/MavericksViewModelDebugModule.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.di
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.multibindings.IntoMap
+import im.vector.app.core.di.MavericksAssistedViewModelFactory
+import im.vector.app.core.di.MavericksViewModelComponent
+import im.vector.app.core.di.MavericksViewModelKey
+import im.vector.app.features.debug.settings.DebugPrivateSettingsViewModel
+
+@InstallIn(MavericksViewModelComponent::class)
+@Module
+interface MavericksViewModelDebugModule {
+
+ @Binds
+ @IntoMap
+ @MavericksViewModelKey(DebugPrivateSettingsViewModel::class)
+ fun debugPrivateSettingsViewModelFactory(factory: DebugPrivateSettingsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsActivity.kt
new file mode 100644
index 0000000000..25a068e794
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsActivity.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.settings
+
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
+import im.vector.app.core.extensions.addFragment
+import im.vector.app.core.platform.VectorBaseActivity
+import im.vector.app.databinding.ActivitySimpleBinding
+
+@AndroidEntryPoint
+class DebugPrivateSettingsActivity : VectorBaseActivity() {
+
+ override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
+
+ override fun initUiAndData() {
+ if (isFirstCreation()) {
+ addFragment(
+ R.id.simpleFragmentContainer,
+ DebugPrivateSettingsFragment::class.java
+ )
+ }
+ }
+}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt
new file mode 100644
index 0000000000..808c379354
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.settings
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.databinding.FragmentDebugPrivateSettingsBinding
+
+class DebugPrivateSettingsFragment : VectorBaseFragment() {
+
+ private val viewModel: DebugPrivateSettingsViewModel by fragmentViewModel()
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentDebugPrivateSettingsBinding {
+ return FragmentDebugPrivateSettingsBinding.inflate(inflater, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setViewListeners()
+ }
+
+ private fun setViewListeners() {
+ views.forceDialPadTabDisplay.setOnCheckedChangeListener { _, isChecked ->
+ viewModel.handle(DebugPrivateSettingsViewActions.SetDialPadVisibility(isChecked))
+ }
+ }
+
+ override fun invalidate() = withState(viewModel) {
+ views.forceDialPadTabDisplay.isChecked = it.dialPadVisible
+ }
+}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt
new file mode 100644
index 0000000000..ecbb241387
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.settings
+
+import im.vector.app.core.platform.VectorViewModelAction
+
+sealed class DebugPrivateSettingsViewActions : VectorViewModelAction {
+ data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions()
+}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt
new file mode 100644
index 0000000000..624c46556a
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.settings
+
+import com.airbnb.mvrx.MavericksViewModelFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.di.MavericksAssistedViewModelFactory
+import im.vector.app.core.di.hiltMavericksViewModelFactory
+import im.vector.app.core.platform.EmptyViewEvents
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.features.settings.VectorDataStore
+import kotlinx.coroutines.launch
+
+class DebugPrivateSettingsViewModel @AssistedInject constructor(
+ @Assisted initialState: DebugPrivateSettingsViewState,
+ private val vectorDataStore: VectorDataStore
+) : VectorViewModel(initialState) {
+
+ @AssistedFactory
+ interface Factory : MavericksAssistedViewModelFactory {
+ override fun create(initialState: DebugPrivateSettingsViewState): DebugPrivateSettingsViewModel
+ }
+
+ companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
+
+ init {
+ observeVectorDataStore()
+ }
+
+ private fun observeVectorDataStore() {
+ vectorDataStore.forceDialPadDisplayFlow.setOnEach {
+ copy(
+ dialPadVisible = it
+ )
+ }
+ }
+
+ override fun handle(action: DebugPrivateSettingsViewActions) {
+ when (action) {
+ is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action)
+ }
+ }
+
+ private fun handleSetDialPadVisibility(action: DebugPrivateSettingsViewActions.SetDialPadVisibility) {
+ viewModelScope.launch {
+ vectorDataStore.setForceDialPadDisplay(action.force)
+ }
+ }
+}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt
new file mode 100644
index 0000000000..0ad4b185ec
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.settings
+
+import com.airbnb.mvrx.MavericksState
+
+data class DebugPrivateSettingsViewState(
+ val dialPadVisible: Boolean = false
+) : MavericksState
diff --git a/vector/src/debug/res/layout/activity_debug_menu.xml b/vector/src/debug/res/layout/activity_debug_menu.xml
index fadffecf83..ac70e4ef0e 100644
--- a/vector/src/debug/res/layout/activity_debug_menu.xml
+++ b/vector/src/debug/res/layout/activity_debug_menu.xml
@@ -20,6 +20,12 @@
android:padding="@dimen/layout_horizontal_margin"
android:showDividers="middle">
+
+
${app_name} wymaga dostępu do kamery i mikrofonu, aby przeprowadzać rozmowy wideo.
-
-Przyznaj dostęp w następnym oknie.
+\n
+\nPrzyznaj dostęp w następnym oknie.${app_name} może sprawdzić Twoją książkę adresową, aby znajdywać innych użytkowników Matrixa bazując na ich adresie e-mail i numerze telefonu. Jeśli zgadzasz się na udostępnienie Twojej książki adresowej w tym celu, zezwól na dostęp w następnym okienku.${app_name} wymaga dostępu do kontaktów, aby znajdywać innych użytkowników Matrixa bazując na adresie e-mail i numerze telefonu.
\n
@@ -610,9 +612,10 @@ Przyznaj dostęp w następnym oknie.%d członków
- Nie będziesz w stanie cofnąć tej zmiany, ponieważ przyznajesz użytkownikowi uprawnienia równe swoim.
-Jesteś pewien?
- Czy chcesz zablokować tego użytkownika w tej rozmowie?
+ Nie będziesz w stanie cofnąć tej zmiany, ponieważ przyznajesz użytkownikowi uprawnienia równe swoim.
+\n
+\nJesteś pewien\?
+ Zbanowanie użytkownika usunie go z tego pokoju i uniemożliwi ponowne dołączenie.Nie udało się zarejestrować: błąd własności e-mailapołączenie odebrane gdzie indziejWiadomość nie została wysłana. Czy %1$s lub %2$s teraz?
@@ -663,7 +666,7 @@ Jesteś pewien?Wibruj gdy ktoś wspomni o TobieAnalitykaSerwer domowy
- Serwer Tożsamości
+ Serwer tożsamościSprawdź pocztę e-mail i kliknij odnośnik, który zawiera. Gdy to zrobisz, kliknij przycisk Kontynuuj.Nie udało się zweryfikować adresu e-mail. Sprawdź swoją skrzynkę i kliknij w link. Gdy to zrobisz, kliknij kontynuuj.Adres e-mail nie został znaleziony.
@@ -730,8 +733,8 @@ Jesteś pewien?Informacja o sesji nadawcyEksportuj klucze E2E pokojuKlucze pokoju zostały zapisane w \'%s\'
-
-Uwaga: ten plik może zostać usunięty, jeśli aplikacja zostanie odinstalowana.
+\n
+\nUwaga: ten plik może zostać usunięty, jeśli aplikacja zostanie odinstalowana.Importuj klucze E2E pokojuImportuj klucze pokojuImportuj klucze z lokalnego pliku
@@ -741,7 +744,11 @@ Uwaga: ten plik może zostać usunięty, jeśli aplikacja zostanie odinstalowana
Usuń weryfikacjęAby sprawdzić czy ta sesja jest zaufana, skontaktuj się z jej właścicielem używając innych form (np. osobiście lub telefonicznie) i zapytaj czy klucz, który widzą w ustawieniach użytkownika dla tego urządzenia pasuje do klucza poniżej:Jeśli klucz pasuje, potwierdź to przyciskiem poniżej. Jeśli nie, to ktoś inny najprawdopodobniej przejmuje lub podszywa się pod tą sesję i powinieneś dodać tę sesję do czarnej listy. W przyszłości proces weryfikacji będzie bardziej skomplikowany.
- Ten pokój zawiera nieznane sesje, które nie zostały zweryfikowane. Oznacza to brak gwarancji, że sesje należą do użytkowników do których twierdzą, że należą. Przed kontynuowaniem, zalecamy wykonanie procesu weryfikacji każdego urządzenia, ale możesz ponownie wysłać wiadomość bez weryfikacji, jeśli wolisz. Nieznane sesje:
+ Ten pokój zawiera nieznane sesje, które nie zostały zweryfikowane.
+\nOznacza to brak gwarancji, że sesje należą do użytkowników do których twierdzą, że należą.
+\nZalecamy wcześniejsze wykonanie procesu weryfikacji każdego urządzenia, ale możesz ponownie wysłać wiadomość bez weryfikacji, jeśli wolisz.
+\n
+\nNieznane sesje:Wyślij naklejkęLicencje osób trzecichNiestety, nie znaleziono zewnętrznej aplikacji, która ukończy to działanie.
@@ -761,9 +768,9 @@ Uwaga: ten plik może zostać usunięty, jeśli aplikacja zostanie odinstalowana
Użyj natywnej kameryMówWyślij głos
- Nie masz obecnie włączonych żadnych pakietów naklejek.
-
-Czy chcesz dodać teraz kilka?
+ Nie masz obecnie aktywnych żadnych pakietów naklejek.
+\n
+\nCzy chcesz dodać teraz kilka\?zadziałaj poprzez…Klucz tożsamości Curve25519Zażądano klucza odcisku palca Ed25519
@@ -827,10 +834,10 @@ Czy chcesz dodać teraz kilka?Dezaktywuj kontoProsimy wpisać swoje hasło.To sprawi, że Twoje konto stanie się na stałe niezdatne do użytku. Nie będziesz mógł się zalogować i nikt nie będzie mógł ponownie zarejestrować tego samego identyfikatora użytkownika. Spowoduje to, że Twoje konto opuści wszystkie pokoje, w których uczestniczy, i usunie dane Twojego konta z serwera tożsamości. Ta czynność jest nieodwracalna.
-
-Dezaktywacja konta domyślnie nie powoduje zapomnienia wysłanych przez Ciebie wiadomości. Jeśli chcesz, abyśmy zapomnieli o Twoich wiadomościach, zaznacz pole poniżej.
-
-Widoczność wiadomości w Matrix jest podobna do wiadomości e-mail. Nasze zapomnienie wiadomości oznacza, że wysłane wiadomości nie będą udostępniane żadnym nowym lub niezarejestrowanym użytkownikom, ale zarejestrowani użytkownicy, którzy już mają dostęp do tych wiadomości, nadal będą mieli dostęp do ich kopii.
+\n
+\nDezaktywacja konta domyślnie nie powoduje zapomnienia wysłanych przez Ciebie wiadomości. Jeśli chcesz, abyśmy zapomnieli o Twoich wiadomościach, zaznacz pole poniżej.
+\n
+\nWidoczność wiadomości w Matrix jest podobna do wiadomości e-mail. Nasze zapomnienie wiadomości oznacza, że wysłane wiadomości nie będą udostępniane żadnym nowym lub niezarejestrowanym użytkownikom, ale zarejestrowani użytkownicy, którzy już mają dostęp do tych wiadomości, nadal będą mieli dostęp do ich kopii.Uzyskaj awatarZamień awatarBłąd
@@ -971,10 +978,11 @@ Widoczność wiadomości w Matrix jest podobna do wiadomości e-mail. Nasze zapo
Nadaj uprawnienieJeden lub więcej testów nie powiodło się, spróbuj sugerowaną poprawkę(-ki).Powiadomienia są wyłączone w ustawieniach systemowych.
-Sprawdź ustawienia systemowe.
- Powiadomienia są wyłączone dla twojego konta.
-Sprawdź ustawienia konta.
- Powiadomienia nie są włączone dla tej sesji. Proszę sprawdź ustawienia ${app_name}.
+\nSprawdź ustawienia systemowe.
+ Powiadomienia są wyłączone dla Twojego konta.
+\nSprawdź ustawienia konta.
+ Powiadomienia nie są włączone dla tej sesji.
+\nProszę sprawdź ustawienia ${app_name}.Dodatkowe informacje: %sWystąpił błąd podczas weryfikowania numeru telefonu.Wystąpił błąd podczas weryfikowania adresu e-mail.
@@ -985,8 +993,8 @@ Sprawdź ustawienia konta.Uruchom UsługęUsługa PowiadomieńUsługa Powiadomień jest uruchomiona.
- Usługa Powiadomień nie jest uruchomiona.
-Spróbuj uruchomić ponownie aplikację.
+ Usługa powiadomień nie jest uruchomiona.
+\nSpróbuj uruchomić ponownie aplikację.Połączenie wideo trwa…Kopia Zapasowa KluczaKopia zapasowa kluczy nie jest zakończona, proszę czekać…
@@ -1732,7 +1740,7 @@ Spróbuj uruchomić ponownie aplikację.Edycja wiadomościOdkąd zostanie włączone, szyfrowanie nie może zostać wyłączone.Aktywować szyfrowanie\?
- Odkąd zostanie włączone, szyfrowanie w pokoju nie może zostać wyłączone. Wiadomości wysłane w zaszyfrowanym pokoju nie są widzane przez serwer, a jedynie przez uczestników w pokoju.
+ Raz włączone szyfrowanie w pokoju nie może zostać wyłączone. Wiadomości wysłane w zaszyfrowanym pokoju nie są widziane przez serwer, a jedynie przez uczestników w pokoju.
\nAktywowanie szyfrowania może uniemożliwić wielu botom i mostom prawidłowe działanie.Aktywuj szyfrowanieAby być bezpiecznym, zweryfikuj %s poprzez sprawdzenie jednorazowego kodu.
@@ -1795,7 +1803,7 @@ Spróbuj uruchomić ponownie aplikację.Wybrana OpcjaTworzy prostą ankietę
- Nie masz dostępu do instniejącej sesji\?
+ Użyj hasła odzyskiwania lub kluczaUżyj klucza odzyskiwania lub hasłaNowa rejestracjaNie można odnaleźć tajemnej przestrzeni w pamięci
@@ -1820,7 +1828,7 @@ Spróbuj uruchomić ponownie aplikację.ZrezygnujNiezgodność kluczaNiezgodność użytkowników
- Nie używasz Serwera Tożsamości
+ Nie korzystasz z serwera tożsamościNie skonfigurowano serwera toższamości, który jest wymagany do resetowania hasła.Wygląda na to, że próbujesz podłączyć się do innego serwera domowego. Czy chcesz się wylogować\?Przysyła zaproszenie
@@ -2015,7 +2023,7 @@ Spróbuj uruchomić ponownie aplikację.
Klucz wiadomościHasło odzyskiwaniaWeryfikacja anulowana
- Zweryfikuj Twoje urządzenia w Ustawieniach.
+ Weryfikacja anulowana. Możesz rozpocząć jej proces ponownie.Jedno z poniższych mogło zostać skompromitowane:
\n
\n-Twoje hasło
@@ -2109,9 +2117,9 @@ Spróbuj uruchomić ponownie aplikację.Utwórz nową konwersację bezpośrednią poprzez Matrix IDNiepoprawny kod weryfikacyjny.Kod
- Czy akceptujesz wysłanie informacji o Twoich kontaktach numerów telefonu i/lub adresów e-mail) do skonfigurowanego Serwera Tożsamości (%1$s) w celu odkrycia istniejących osób, które znasz\?
+ Czy akceptujesz wysłanie numerów telefonów Twoich kontaktów i/lub ich adresów e-mail do skonfigurowanego serwera tożsamości (%1$s) w celu znalezienia osób, które znasz\?
\n
-\nW celu zapewnienia większej prywatności, wysłane dane zostaną poddane operacji haszowania przed wysłaniem.
+\nBy chronić wrażliwe dane, przed wysłaniem zostaną one poddane funkcji skrótu.Wyślij adresy e-mail oraz numery telefonówUdziel zgodyWycofaj moją zgodę
@@ -2527,7 +2535,7 @@ Spróbuj uruchomić ponownie aplikację.
Wysyłanie wiadomościRola domyślnaNie masz uprawnień do modyfikowania roli wymaganych aby zmieniać poszczególne części tej przestrzeni
- "Nie masz uprawnieni do modyfikowania roli wymaganych do zmiany poszczególnych części pokoju"
+ Nie masz uprawnień do modyfikowania ról wymaganych do zmiany poszczególnych części pokojuPrzeglądaj i modyfikuj role wymagane do zmiany różnych części przestrzeni.Wybierz role wymagane do zmieniania poszczególnych części pokojuWybierz role wymagane do zmiany poszczególnych części tej przestrzeni
@@ -2845,7 +2853,7 @@ Spróbuj uruchomić ponownie aplikację.
Zdecyduj kto może odnaleźć i dołączyć do tego pokoju.Dotknij, aby edytować przestrzenieWybierz przestrzenie
- Zdecyduj które przestrzenie mogą mieć dostęp do tego pokoju. Członkowie wybranej przestrzeni będą mogli odnaleźć i dołączyć do nazwy pokoju.
+ Zdecyduj które przestrzenie mogą mieć dostęp do tego pokoju. Członkowie wybranej przestrzeni będą mogli ją odnaleźć i dołączyć do Pokoju przez nazwę.Przestrzenie mogące uzyskać dostępZezwól użytkownikom przestrzeni na znalezienie i dostęp.Ulepszenia pokoju
@@ -2864,4 +2872,188 @@ Spróbuj uruchomić ponownie aplikację.
Synchronizacja klucza samopodpisującego (Self Signing key)Weryfikacja ręczna poprzez tekstlub innego klienta Matrix z krzyżową weryfikacją nowych sesji logowania
+ Nie masz uprawnień do zmiany poziomu pokoju
+ Oczekiwanie na historię szyfrowania
+ Ten pokój pracuje na wersji pokoju %s, którą serwer domowy oznaczył jako niestabilną.
+ Pozwól każdemu w %s na znalezienie i dostęp. Możesz wybrać także inne miejsca.
+ Każdy w %s będzie mógł znaleźć i dołączyć do tego pokoju bez konieczności otrzymania zaproszenia. Można to zmienić w ustawieniach pokoju.
+ niestabilna
+ stabilna
+ Wersja domyślna
+ Wersje pokoju 👓
+ Limit jest nieznany.
+ Twój serwer domowy akceptuje załączniki (pliki, zdjęcia, etc.) o wielkości do %s.
+ Limit wielkości pliku na serwerze
+ Wersja serwera
+ Nazwa serwera
+ Zweryfikuj zgodność wyświetlonych emotikon
+ Zeskanuj za pomocą tego urządzenia
+ Zeskanuj kod Twoim drugim urządzeniem lub przełącz się i zeskanuj za pomocą tego urządzenia
+ Głos
+ Tworzę przestrzeń…
+ Adres przestrzeni
+ Przygotowuje ( ͡° ͜ʖ ͡°) jako zwykły tekst
+ Adres e-mail wygląda na niepoprawny
+ Wyczyść historię
+ Zarejestruj się za pomocą %s
+ Zaloguj się za pomocą %s
+ Kontynuuj z %s
+ Lub
+ Ustawienia pokoju
+ Ankieta
+ Plik jest zbyt duży, aby go przesłać.
+ Ta funkcja jest w fazie beta
+ Polityka
+ Do odnalezienia istniejących kontaktów potrzebujemy Twojej zgody na wysłanie informacji o nich na serwer tożsamości.
+\n
+\nTwoje dane są zabezpieczone funkcją skrótu. Czy zgadzasz się na ich wysłanie\?
+ Wyślij maile i numery telefonów do %s
+ Twoje kontakty są prywatne. Aby odnaleźć użytkowników z Twoich kontaktów, potrzebujemy zgody do wysłania informacji o nich na Twój serwer tożsamości.
+ Bezpieczna kopia
+ Sesja została wylogowana!
+ Włącz krzyżowe logowanie
+ Wymagane ponowne logowanie
+ Przesuń aby zakończyć rozmowę
+ Nieznana osoba
+ Użytkownicy
+ Wystąpił błąd podczas przekazywania połączenia
+ Połącz
+ Połączenie nieudane
+ Brak odpowiedzi
+ Nieodebrane połączenie głosowe
+ Aktywna wideorozmowa
+ Aktywna rozmowa głosowa
+ Przychodząca wideorozmowa
+ Przychodzące połączenie głosowe
+ Rozmowa zakończona
+ %1$s odrzucił połączenie
+ Odrzuciłeś połączenie
+ Odrzuciłeś połączenie od %s
+ Jesteś obecnie w tej rozmowie
+ %1$s rozpoczął rozmowę
+ Rozpocząłeś rozmowę
+ Udostępnij przez tekst
+ Konfiguracja
+ Bezpieczna kopia zapasowa
+ Ustaw bezpieczną Kopię Zapasową
+ wysyła opady śniegu ❄️
+ wysyła konfetti 🎉
+ Wysyła wiadomość z opadami śniegu
+ Wysyła wiadomość z konfetti
+ Wybierz swój klucz odzyskiwania, wpisz go ręcznie lub wklej ze schowka
+ Użyj Klucza Odzyskiwania
+ Użyj najnowszej wersji ${app_name} na innych urządzeniach, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} dla Androida, oraz innych wspieranych klientów protokołu Matrix
+ użyj swojego klucza odzyskiwania Klucza Kopii Zapasowej
+ Zapisywanie sekretu klucza kopii zapasowej w SSSS
+ Generowania klucza SSSS z hasła
+ Sprawdzanie Klucza kopii zapasowej (%s)
+ Konfiguracja Kopii Zapasowej Klucza
+ Definiowanie SSSS domyślnego Klucza
+ Wyślij media w oryginalnym rozmiarze
+
+ Wyślij wideo w oryginalnym rozmiarze
+ Wyślij kilka wideo w oryginalnym rozmiarze
+ Wyślij wideo w oryginalnym rozmiarze
+ Wyślij wideo w oryginalnym rozmiarze
+
+
+ %1$s usunął %2$s jako adres dla tego pokoju.
+ %1$s usunął %2$s jako adresy dla tego pokoju.
+ %1$s usunął %2$s jako adresy dla tego pokoju.
+ %1$s usunął %2$s jako adresy dla tego pokoju.
+
+
+ Dodano %1$s jako adres dla tego pokoju.
+ Dodano %1$s jako adresy dla tego pokoju.
+ Dodano %1$s jako adresy dla tego pokoju.
+ Dodano %1$s jako adresy dla tego pokoju.
+
+ pojedyncze logowanie
+ Pokój opuszczony!
+ Błąd podczas wyszukiwania numeru telefonu
+ Przekaż
+ %1$s dotknij by powrócić
+ Aktywne połączenie (%1$s) ·
+ Aktywne połączenie (%1$s)
+ Klawisze wybierania
+ Wideorozmowa zakończona • %1$s
+ Odrzucono wideorozmowę
+ Nieodebrana wideorozmowa
+ Odrzucono połączenie głosowe
+ Rozmowa głosowa zakończona • %1$s
+ Oddzwoń
+ Nie udało się skonfigurować logowania krzyżowego
+ Aktualizacja spowoduje utworzenie pokoju w nowej wersji. Wszystkie obecne wiadomości zostaną w zarchiwizowanym pokoju.
+ Nagrywanie wiadomości głosowej
+ Zatrzymaj nagrywanie
+ Wstrzymaj wiadomość głosową
+ Odtwórz wiadomość głosową
+ Blokada wiadomości głosowej
+ Zaktualizuj do rekomendowanej wersji pokoju
+ Automatycznie zapraszaj użytkowników
+ Zaktualizujesz ten pokój z %1$s do %2$s.
+ Aktualizacja pokoju jest dla zaawansowanych i zaleca się ją w chwili gdy pokój jest niestabilny przez błędy, brakujące funkcje czy luki bezpieczeństwa.
+\nZazwyczaj wpływa to tylko na sposób, w jaki w pokój jest przetwarzany na serwerze.
+ Aktualizuj pokój prywatny
+ Aktualizuj pokój publiczny
+ Dołącz do pokoju zastępczego
+ Na chwilę obecną ludzie mogą nie być w stanie dołączyć do tworzonych przez Ciebie prywatnych pokojów.
+\n
+\nBędziemy usprawniać tę funkcję w fazie beta i po prostu chcemy, byś wiedział.
+ Dodaj kilka szczegółów, aby ułatwić ludziom identyfikację. Możesz je zmienić w dowolnym momencie.
+ Dodaj kilka szczegółów, aby się wyróżnić. Możesz je zmienić w dowolnym momencie.
+ Twoja przestrzeń prywatna
+ Twoja przestrzeń publiczna
+ Dodaj przestrzeń
+ Przestrzeń prywatna
+ Przestrzeń publiczna
+ Chcesz usunąć wszystkie niewysłane wiadomości w tym pokoju\?
+ Usuń niewysłane wiadomości
+ Nie udało się wysłać wiadomości
+ Chcesz anulować wysyłanie wiadomości\?
+ Nieudane
+ Wysłano
+ Wysyłanie
+ Aktualizuje pokój do nowej wersji
+ Dołącz do przestrzeni o danym id
+ Dodaj do danej przestrzeni
+ Stwórz przestrzeń
+ Edytuj treść
+ Poznaj stan pokoju
+ Narzędzia deweloperskie
+ Niedostępny
+ Przestrzeń publiczna
+ Pokój publiczny
+ Nie powiadamiaj
+ Powiadom bez dźwięku
+ Powiadom z dźwiękiem
+ Wiadomość nie została wysłana z powodu błędu
+ Zamknij selektor emoji
+ Otwórz selektor emoji
+ Wiarygodny poziom zaufania
+ Domyślny poziom zaufania
+ Wybrany
+ Wideo
+ ma niewysłany szkic
+ Niektóre wiadomości nie zostały wysłane
+ Usuń awatar
+ Zmień awatar
+ Obraz
+ Importuj klucz z pliku
+ Otwórz widżety
+ Zrzut ekranu
+ Uwierzytelnianie nieudane
+ ${app_name} wymaga podania Twoich danych do wykonania tej czynności.
+ Przekaż do %1$s
+ Pytanie nie może być puste
+ UTWÓRZ ANKIETĘ
+ DODAJ OPCJĘ
+ Opcja %1$d
+ Utwórz opcje
+ Pytanie lub temat
+ Pytanie lub temat ankiety
+ Utwórz ankietę
+ Powiąż ten email ze swoim kontem
+ Stwórzmy pokój dla każdego z nich. Możesz potem dodać kolejne, także te już istniejące.
+ Nad czym pracujesz\?
\ No newline at end of file
diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index e25319e426..0a158f196e 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -3079,4 +3079,24 @@
Escolher servidorcasaNão dá para alcançar um servidorcasa na URL %s. Por favor cheque seu link ou escolha um servidorcasa manualmente.À escuta por notificações
+
+ Pelo menos %1$s opção é requerida
+ Pelo menos %1$s opções são requeridas
+
+ Pergunta não pode estar vazia
+ CRIAR SONDAGEM
+ ADICIONAR OPÇÃO
+ Opção %1$d
+ Criar opções
+ Pergunta ou tópico
+ Sondar pergunta ou tópico
+ Criar Sondagem
+ Sondagem
+ Para descobrir contatos existentes, você precisa envir info de contato a seu servidor de identidade.
+\n
+\nNós hashamos seus dados antes de enviar por privacidade. Você consente para enviar esta info\?
+ Enviar emails e números de telefone para %s
+ Seus contatos são privados. Para descobrir usuárias(os) de seus contatos, você precisa de permissão para enviar info de contato a seu servidor de identidade.
+ O signout desta sessão tem sido feito!
+ Esta sala tem sido saída!
\ No newline at end of file
diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index a6969464a2..69203b5aa5 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -3009,4 +3009,24 @@
Välj hemserverKan inte nå en hemserver på URL:en %s. Vänligen kontrollera din länk eller välj en hemserver manuellt.Lyssnar efter aviseringar
+
+ Minst %1$s alternativ krävs
+ Minst %1$s alternativ krävs
+
+ Frågan kan inte vara tom
+ SKAPA OMRÖSTNING
+ LÄGG TILL ALTERNATIV
+ Alternativ %1$d
+ Skapa alternativ
+ Fråga eller ämne
+ Omröstningens fråga eller ämne
+ Skapa omröstning
+ Omröstning
+ För att upptäcka befintliga kontakter så behöver du skicka kontaktinfo till din identitetsserver.
+\n
+\nVi hashar din data innan den skickas av sekretesskäl. Godkänner du att denna information skickas\?
+ Skicka e-postadresser och telefonnummer till %s
+ Dina kontakter är privata. För att upptäcka användare från dina kontakter så behöver vi ditt tillstånd att skicka kontaktinfo till din identitetsserver.
+ Sessionen har loggats ut!
+ Rummet har lämnats!
\ No newline at end of file
diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
index c652c1d217..d4d884a463 100644
--- a/vector/src/main/res/values-uk/strings.xml
+++ b/vector/src/main/res/values-uk/strings.xml
@@ -179,7 +179,7 @@
OLED ТемаСинхронізація…
- Слухати події
+ Стеження за подіямиПовідомленняКімната
@@ -219,7 +219,7 @@
Все одно надіслатиабоЗапрошення
- Офлайн
+ Не в мережіВийти з облікового записуГолосовий виклик
@@ -430,9 +430,9 @@
Ви впевнені, що хочете вийти з кімнати\?Ви впевнені, що бажаєте вилучити %s з цієї кімнати\?Створити
- Online
- Offline
- Перестій
+ У мережі
+ Не в мережі
+ ПростійАДМІНІСТРУВАННЯВИКЛИКОсобисті повідомлення
@@ -2878,4 +2878,114 @@
Експериментальний простір - обмежена кімната.Усі кімнати, у яких ви перебуваєте, буде показано на сторінці Домівка.Показувати у Домівці
+ Перевірку скасовано
+ Перевірку скасовано. Ви можете розпочати її повторно.
+ Подію змінено адміністратором кімнати, причина: %1$s
+ Причина редагування
+ Вказати причину
+ Надіслати медіа в оригінальному розмірі
+
+ Надіслати відео в оригінальному розмірі
+ Надіслати відео в оригінальному розмірі
+ Надіслати відео в оригінальному розмірі
+ Надіслати відео в оригінальному розмірі
+
+
+ Надіслати зображення в оригінальному розмірі
+ Надіслати зображення в оригінальному розмірі
+ Надіслати зображення в оригінальному розмірі
+ Надіслати зображення в оригінальному розмірі
+
+ Хочете надіслати це вкладення до %1$s\?
+ Введіть парольну фразу таємного сховища
+ Не вдалося знайти дані у сховищі
+ Інші користувачі можуть йому не довіряти
+ Опитування
+ Вибрати домашній сервер
+ Стеження за сповіщеннями
+
+ Потрібен принаймні %1$s варіант
+ Потрібні принаймні %1$s варіанти
+ Потрібні принаймні %1$s варіантів
+ Потрібні принаймні %1$s варіантів
+
+ Питання не може бути порожнім
+ СТВОРИТИ ОПИТУВАННЯ
+ ДОДАТИ ВАРІАНТ
+ Варіант %1$d
+ Створити варіанти
+ Питання або тема
+ Питання опитування або тема
+ Створити опитування
+ Голосове повідомлення (%1$s)
+ Не вдалося записати голосове повідомлення
+ Не вдалося відтворити це голосове повідомлення
+ Увімкнути голосові повідомлення
+ Видалити запис
+ Запис голосового повідомлення
+ Зупинити запис
+ Зупинити відтворення голосового повідомлення
+ Відтворити голосове повідомлення
+ Блокування голосового повідомлення
+ Оновити приватну кімнату
+ Оновити загальнодоступну кімнату
+ Оновлення обов\'язкове
+ Оновити
+ Кімната без назви
+ У цьому просторі немає кімнат
+ Керувати кімнатами та просторами
+ Позначити не пропонованим
+ Позначити запропонованим
+ Запропоновано
+ Зробіть цей простір загальнодоступним
+ Додайте простір до будь-якого простору, яким ви керуєте.
+ Додати наявні простори
+ Додати наявні кімнати
+ Ви вийдете з усіх кімнат і просторів у %s.
+ Вийти з усіх кімнат і просторів
+ Ви впевнені, що хочете вийти з %s\?
+ Приватний простір для вас і учасників вашої команди
+ Я та учасники команди
+ З ким ви працюєте\?
+ Щоб приєднатися до наявного простору, вам потрібне запрошення.
+ Ви можете змінити це пізніше
+ Який тип простору ви хочете створити\?
+ Не вдалося надіслати повідомлення
+ Оновлює кімнату до нової версії
+ Вийти з кімнати з цим id (або з поточної кімнати, якщо не вказано)
+ Приєднатися до простору з цим id
+ Додати до цього простору
+ Подію стану надіслано!
+ Недійсна подія
+ Відсутній тип повідомлення
+ Ключ стану
+ Надіслати власну подію стану
+ Події стану
+ Надіслати подію стану
+ Надіслати власну подію
+ Дізнатися стан кімнати
+ Недоступний
+ Не в мережі
+ У мережі
+ Перегляд підтвердження прочитання
+ Не перевірено
+ Перевірено
+ Консультація з %1$s
+ Спочатку порадьтеся
+ Є незбережені зміни. Відкинути зміни\?
+ Кімнату ще не створено. Скасувати створення кімнати\?
+ Посилання було неправильним
+ Не можна надсилати повідомлення самим собі!
+ Збереження резервної копії ключа в SSSS
+ Генерування ключа SSSS з ключа відновлення
+ Генерування ключа SSSS з парольної фрази (%s)
+ Генерування ключа SSSS з парольної фрази
+ Отримання кривої ключа
+ Перевірка ключа резервного копіювання (%s)
+ Перевірка ключа резервного копіювання
+ Неправильне ім\'я користувача та/або пароль. Введений пароль починається або закінчується пробілами, перевірте це.
+ Розблокувати історію зашифрованих повідомлень
+ Експорт аудиту
+ Ключі вже оновлено!
+ Додає ( ͡° ͜ʖ ͡°) до текстового повідомлення
\ No newline at end of file
diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml
index 72f86d24e5..bcb7c1583e 100644
--- a/vector/src/main/res/values-zh-rCN/strings.xml
+++ b/vector/src/main/res/values-zh-rCN/strings.xml
@@ -2963,4 +2963,23 @@
选择主服务器无法访问 URL %s 上的主服务器。请检查您的链接或手动选择一个主服务器。侦听通知
+
+ 需要至少 %1$s 个选项
+
+ 问题不能为空
+ 创建投票
+ 添加选项
+ 选项 %1$d
+ 创建选项
+ 问题或主题
+ 投票问题或主题
+ 创建投票
+ 投票
+ 要发现现有联系人,您需要将联系信息发送到您的身份服务器。
+\n
+\n我们在发送之前对您的数据进行哈希处理以保护隐私。 您同意发送此信息吗?
+ 向 %s 发送电子邮件和电话号码
+ 您的联系人是私密的。 要从您的联系人中发现用户,我们需要您的许可才能将联系信息发送到您的身份服务器。
+ 已退出此会话!
+ 已离开此聊天室!
\ No newline at end of file
diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
index 27fd4910cf..efb6648004 100644
--- a/vector/src/main/res/values-zh-rTW/strings.xml
+++ b/vector/src/main/res/values-zh-rTW/strings.xml
@@ -2953,4 +2953,23 @@
選擇家伺服器無法存取 URL %s 的家伺服器。請檢查您的連結或手動選擇家伺服器。監聽通知
+
+ 需要至少 %1$s 選項
+
+ 問題不能為空
+ 建立投票
+ 新增選項
+ 選項 %1$d
+ 建立選項
+ 問題或主題
+ 投票問題或主題
+ 建立投票
+ 投票
+ 要探索既有的聯絡人,您必須傳送聯絡人資訊到您的身份識別伺服器。
+\n
+\n我們會在傳送前雜湊您的資料以保護隱私。您同意傳送此資訊嗎?
+ 向 %s 傳送電子郵件與電話號碼
+ 您的通訊錄是私人的。要從您的通訊錄中探索使用者,我們需要您的權限來傳送聯絡人資訊到您的身份識別伺服器。
+ 已登出工作階段!
+ 已離開聊天室!
\ No newline at end of file
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 1c0a1ba9f1..ddb731a5e0 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -456,6 +456,7 @@
Copied to clipboardDisableReturn
+ Not nowConfirmation
@@ -2193,7 +2194,9 @@
Your rooms will be displayed here. Tap the + bottom right to find existing ones or start some of your own.Reactions
+
Agree
+
LikeAdd ReactionView Reactions
@@ -2377,6 +2380,8 @@
Send emails and phone numbers to %sIn order to discover existing contacts you know, do you accept to send your contact data (phone numbers and/or emails) to the configured identity server (%1$s)?\n\nFor more privacy, the sent data will be hashed before being sent.To discover existing contacts, you need to send contact info to your identity server.\n\nWe hash your data before sending for privacy. Do you consent to send this info?
+ To discover existing contacts, you need to send contact info (emails and phone numbers) to your identity server. We hash your data before sending for privacy.
+ Do you agree to send this info?PolicyEnter an identity server URL
@@ -2898,7 +2903,7 @@
QR code
- Almost there! Is %s showing the same shield?
+ Almost there! Is %s showing a tick?YesNo
@@ -3051,7 +3056,7 @@
Add a topic"Topic: "
- Almost there! Is the other device showing the same shield?
+ Almost there! Is the other device showing a tick?Almost there! Waiting for confirmation…Waiting for %s…
diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml
index d7796ee325..c6bdf1e4d3 100644
--- a/vector/src/main/res/xml/vector_settings_preferences.xml
+++ b/vector/src/main/res/xml/vector_settings_preferences.xml
@@ -121,8 +121,8 @@
+ android:summary="@string/all_rooms_youre_in_will_be_shown_in_home"
+ android:title="@string/preference_show_all_rooms_in_home" />
-
-
-
-
) = NotificationEventQueue(events.toMutableList())
+ private fun givenQueue(events: List) = NotificationEventQueue(events.toMutableList(), seenEventIds = seenIdsCache)
}