Merge branch 'release/1.5.12' into main

This commit is contained in:
Benoit Marty 2022-12-15 10:16:25 +01:00
commit cf1a115c67
277 changed files with 5866 additions and 1427 deletions

1
.gitattributes vendored
View file

@ -1 +1,2 @@
**/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text
**/src/androidTest/assets/*.realm filter=lfs diff=lfs merge=lfs -text

View file

@ -11,9 +11,9 @@ jobs:
- run: |
npm install --save-dev @babel/plugin-transform-flow-strip-types
- name: Danger
uses: danger/danger-js@11.1.4
uses: danger/danger-js@11.2.0
with:
args: "--dangerfile tools/danger/dangerfile.js"
args: "--dangerfile ./tools/danger/dangerfile.js"
env:
DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
# Fallback for forks

View file

@ -66,9 +66,9 @@ jobs:
yarn add danger-plugin-lint-report --dev
- name: Danger lint
if: always()
uses: danger/danger-js@11.1.4
uses: danger/danger-js@11.2.0
with:
args: "--dangerfile tools/danger/dangerfile-lint.js"
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
env:
DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
# Fallback for forks

View file

@ -17,7 +17,8 @@ jobs:
contains(github.event.issue.labels.*.name, 'Z-IA') ||
contains(github.event.issue.labels.*.name, 'A-Themes-Custom') ||
contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') ||
contains(github.event.issue.labels.*.name, 'A-Tags')
contains(github.event.issue.labels.*.name, 'A-Tags') ||
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor')
steps:
- uses: actions/github-script@v5
with:
@ -88,7 +89,7 @@ jobs:
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PN_kwDOAM0swc0sUA"
PROJECT_ID: "PVT_kwDOAM0swc0sUA"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
add_product_issues:
@ -112,7 +113,7 @@ jobs:
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PN_kwDOAM0swc4AAg6N"
PROJECT_ID: "PVT_kwDOAM0swc4AAg6N"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
delight_issues_to_board:
@ -138,7 +139,7 @@ jobs:
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PN_kwDOAM0swc1HvQ"
PROJECT_ID: "PVT_kwDOAM0swc1HvQ"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_voice-message_issues:
@ -163,7 +164,7 @@ jobs:
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PN_kwDOAM0swc2KCw"
PROJECT_ID: "PVT_kwDOAM0swc2KCw"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_message_bubbles_issues:
name: A-Message-Bubbles to Message bubbles board
@ -187,7 +188,7 @@ jobs:
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PN_kwDOAM0swc3m-g"
PROJECT_ID: "PVT_kwDOAM0swc3m-g"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_ftue_issues:
@ -212,7 +213,7 @@ jobs:
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PN_kwDOAM0swc4AAqVx"
PROJECT_ID: "PVT_kwDOAM0swc4AAqVx"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_WTF_issues:
@ -237,7 +238,7 @@ jobs:
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PN_kwDOAM0swc4AArk0"
PROJECT_ID: "PVT_kwDOAM0swc4AArk0"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_element_x_issues:
@ -267,7 +268,7 @@ jobs:
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PN_kwDOAM0swc4ABTXY"
PROJECT_ID: "PVT_kwDOAM0swc4ABTXY"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
ps_features1:

View file

@ -69,7 +69,7 @@ jobs:
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.pull_request.node_id }}
env:
PROJECT_ID: "PN_kwDOAM0swc0sUA"
PROJECT_ID: "PVT_kwDOAM0swc0sUA"
TEAM: "design"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
@ -138,6 +138,6 @@ jobs:
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.pull_request.node_id }}
env:
PROJECT_ID: "PN_kwDOAM0swc4AAg6N"
PROJECT_ID: "PVT_kwDOAM0swc4AAg6N"
TEAM: "product"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}

View file

@ -1 +0,0 @@
[Rich text editor] Fix design and spacing of rich text editor

View file

@ -1,3 +1,54 @@
Changes in Element v1.5.12 (2022-12-15)
=======================================
Features ✨
----------
- [Threads] - Threads Labs Flag is enabled by default and forced to be enabled for existing users, but sill can be disabled manually ([#5503](https://github.com/vector-im/element-android/issues/5503))
- [Session manager] Add action to signout all the other session ([#7693](https://github.com/vector-im/element-android/issues/7693))
- Remind unverified sessions with a banner once a week ([#7694](https://github.com/vector-im/element-android/issues/7694))
- [Session manager] Add actions to rename and signout current session ([#7697](https://github.com/vector-im/element-android/issues/7697))
- Voice Broadcast - Update last message in the room list ([#7719](https://github.com/vector-im/element-android/issues/7719))
- Delete unused client information from account data ([#7754](https://github.com/vector-im/element-android/issues/7754))
Bugfixes 🐛
----------
- Fix bad pills color background. For light and dark theme the color is now 61708B (iso EleWeb) ([#7274](https://github.com/vector-im/element-android/issues/7274))
- [Notifications] Fixed a bug when push notification was automatically dismissed while app is on background ([#7643](https://github.com/vector-im/element-android/issues/7643))
- ANR when asking to select the notification method ([#7653](https://github.com/vector-im/element-android/issues/7653))
- [Rich text editor] Fix design and spacing of rich text editor ([#7658](https://github.com/vector-im/element-android/issues/7658))
- [Rich text editor] Fix keyboard closing after collapsing editor ([#7659](https://github.com/vector-im/element-android/issues/7659))
- Rich Text Editor: fix several issues related to insets:
* Empty space displayed at the bottom when you don't have permissions to send messages into a room.
* Wrong insets being kept when you exit the room screen and the keyboard is displayed, then come back to it. ([#7680](https://github.com/vector-im/element-android/issues/7680))
- Fix crash in message composer when room is missing ([#7683](https://github.com/vector-im/element-android/issues/7683))
- Fix crash when invalid homeserver url is entered. ([#7684](https://github.com/vector-im/element-android/issues/7684))
- Rich Text Editor: improve performance when entering reply/edit/quote mode. ([#7691](https://github.com/vector-im/element-android/issues/7691))
- [Rich text editor] Add error tracking for rich text editor ([#7695](https://github.com/vector-im/element-android/issues/7695))
- Fix E2EE set up failure whilst signing in using QR code ([#7699](https://github.com/vector-im/element-android/issues/7699))
- Fix usage of unknown shield in room summary ([#7710](https://github.com/vector-im/element-android/issues/7710))
- Fix crash when the network is not available. ([#7725](https://github.com/vector-im/element-android/issues/7725))
- [Session manager] Sessions without encryption support should not prompt to verify ([#7733](https://github.com/vector-im/element-android/issues/7733))
- Fix issue of Scan QR code button sometimes not showing when it should be available ([#7737](https://github.com/vector-im/element-android/issues/7737))
- Verification request is not showing when verify session popup is displayed ([#7743](https://github.com/vector-im/element-android/issues/7743))
- Fix crash when inviting by email. ([#7744](https://github.com/vector-im/element-android/issues/7744))
- Revert usage of stable fields in live location sharing and polls ([#7751](https://github.com/vector-im/element-android/issues/7751))
- [Poll] Poll end event is not recognized ([#7753](https://github.com/vector-im/element-android/issues/7753))
- [Push Notifications] When push notification for threaded message is clicked, thread timeline will be opened instead of room's main timeline ([#7770](https://github.com/vector-im/element-android/issues/7770))
Other changes
-------------
- [Threads] - added API to fetch threads list from the server instead of building it locally from events ([#5819](https://github.com/vector-im/element-android/issues/5819))
- Add Z-Labs label for rich text editor and migrate to new label naming. ([#7477](https://github.com/vector-im/element-android/issues/7477))
- Crypto database migration tests ([#7645](https://github.com/vector-im/element-android/issues/7645))
- Add tracing Id for to device messages ([#7708](https://github.com/vector-im/element-android/issues/7708))
- Disable nightly popup and add an entry point in the advanced settings instead. ([#7723](https://github.com/vector-im/element-android/issues/7723))
- Save m.local_notification_settings.<device-id> event in account_data ([#7596](https://github.com/vector-im/element-android/issues/7596))
- Update notifications setting when m.local_notification_settings.<device-id> event changes for current device ([#7632](https://github.com/vector-im/element-android/issues/7632))
SDK API changes ⚠️
------------------
- Handle account data removal ([#7740](https://github.com/vector-im/element-android/issues/7740))
Changes in Element 1.5.11 (2022-12-07)
======================================

View file

@ -29,7 +29,7 @@ buildscript {
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
classpath "com.likethesalad.android:stem-plugin:2.2.3"
classpath 'org.owasp:dependency-check-gradle:7.3.0'
classpath 'org.owasp:dependency-check-gradle:7.4.1'
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20"
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
@ -45,10 +45,10 @@ plugins {
// Detekt
id "io.gitlab.arturbosch.detekt" version "1.22.0"
// Ksp
id "com.google.devtools.ksp" version "1.7.21-1.0.8"
id "com.google.devtools.ksp" version "1.7.22-1.0.8"
// Dependency Analysis
id 'com.autonomousapps.dependency-analysis' version "1.16.0"
id 'com.autonomousapps.dependency-analysis' version "1.17.0"
// Gradle doctor
id "com.osacky.doctor" version "0.8.1"
}

View file

@ -8,7 +8,7 @@ ext.versions = [
def gradle = "7.3.1"
// Ref: https://kotlinlang.org/releases.html
def kotlin = "1.7.21"
def kotlin = "1.7.22"
def kotlinCoroutines = "1.6.4"
def dagger = "2.44.2"
def appDistribution = "16.0.0-beta05"
@ -17,7 +17,7 @@ def markwon = "4.6.2"
def moshi = "1.14.0"
def lifecycle = "2.5.1"
def flowBinding = "1.2.0"
def flipper = "0.174.0"
def flipper = "0.176.0"
def epoxy = "5.0.0"
def mavericks = "3.0.1"
def glide = "4.14.2"
@ -26,8 +26,8 @@ def jjwt = "0.11.5"
// Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert
// the whole commit which set version 0.16.0-SNAPSHOT
def vanniktechEmoji = "0.16.0-SNAPSHOT"
def sentry = "6.7.0"
def fragment = "1.5.4"
def sentry = "6.9.0"
def fragment = "1.5.5"
// Testing
def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819
def espresso = "3.4.0"
@ -98,7 +98,7 @@ ext.libs = [
],
element : [
'opusencoder' : "io.element.android:opusencoder:1.1.0",
'wysiwyg' : "io.element.android:wysiwyg:0.7.0.1"
'wysiwyg' : "io.element.android:wysiwyg:0.9.0"
],
squareup : [
'moshi' : "com.squareup.moshi:moshi:$moshi",

View file

@ -0,0 +1,55 @@
<!--- TOC -->
* [Testing database migration](#testing-database-migration)
* [Creating a reference database](#creating-a-reference-database)
* [Testing](#testing)
<!--- END -->
## Testing database migration
### Creating a reference database
Databases are encrypted, the key to decrypt is needed to setup the test.
A special build property must be enabled to extract it.
Set `vector.debugPrivateData=true` in `~/.gradle/gradle.properties` (to avoid committing by mistake)
Launch the app in your emulator, login and use the app to fill up the database.
Save the key for the tested database
```
RealmKeysUtils W Database key for alias `session_db_fe9f212a611ccf6dea1141777065ed0a`: 935a6dfa0b0fc5cce1414194ed190....
RealmKeysUtils W Database key for alias `crypto_module_fe9f212a611ccf6dea1141777065ed0a`: 7b9a21a8a311e85d75b069a343.....
```
Use the [Device File Explorer](https://developer.android.com/studio/debug/device-file-explorer) to extrat the database file from the emulator.
Go to `data/data/im.vector.app.debug/files/<hash>/`
Pick the database you want to test (name can be found in SessionRealmConfigurationFactory):
- crypto_store.realm for crypto
- disk_store.realm for session
- etc...
Download the file on your disk
### Testing
Copy the file in `src/AndroidTest/assets`
see `CryptoSanityMigrationTest` or `RealmSessionStoreMigration43Test` for sample tests.
There are already some databases in the assets folder.
The existing test will properly detect schema changes, and fail with such errors if a migration is missing:
```
io.realm.exceptions.RealmMigrationNeededException: Migration is required due to the following errors:
- Property 'CryptoMetadataEntity.foo' has been added.
```
If you want to test properly more complex database migration (dynamic transforms) ensure that the database contains
the entity you want to migrate.
You can explore the database with [realm studio](https://www.mongodb.com/docs/realm/studio/) if needed.

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: Nová implementace celoobrazovkového režimu pro editor formátovaného textu a opravy chyb.
Úplný seznam změn: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Die wichtigsten Änderungen in dieser Version: Der Vollbildmodus des Textverarbeitungseditors wurde neu umgesetzt und es wurden diverse Fehler behoben.
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Main changes in this version: Thread are now enabled by default.
Full changelog: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Põhilised muutused selles versioonis: tekstitoimeti täisekraanivaade ja erinevate vigade parandused.
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: Penerapan baru mode layar penuh untuk Penyunting Teks Kaya dan perbaikan kutu.
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: Nová implementácia celo-obrazovkového režimu pre Rozšírený textový editor a opravy chýb.
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Ndryshimet kryesore në këtë version: ndreqje të metash dhe përmirësime.
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Huvudsakliga ändringar i den här versionen: buggfixar och förbättringar.
Full ändringslogg: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Основні зміни в цій версії: Нова реалізація повноекранного режиму для редактора розширеного тексту та виправлення помилок.
Перелік усіх змін: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
此版本中的主要變動:格式化文字編輯器的全螢幕模式新實作與臭蟲修復。
完整的變更紀錄https://github.com/vector-im/element-android/releases

Binary file not shown.

View file

@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=db9c8211ed63f61f60292c69e80d89196f9eb36665e369e7f00ac4cc841c2219
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
distributionSha256Sum=312eb12875e1747e05c2f81a4789902d7e4ec5defbd1eefeaccc08acf096505d
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

12
gradlew vendored
View file

@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -80,10 +80,10 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
@ -143,12 +143,16 @@ fi
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac

1
gradlew.bat vendored
View file

@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

View file

@ -2789,7 +2789,7 @@
<string name="key_authenticity_not_guaranteed">Pravost této šifrované zprávy nelze v tomto zařízení zaručit.</string>
<string name="settings_security_incognito_keyboard_summary">Požadujte, aby klávesnice neaktualizovala žádné personalizované údaje, jako je historie psaní a slovník, na základě toho, co jste napsali v konverzacích. Upozorňujeme, že některé klávesnice nemusí toto nastavení respektovat.</string>
<string name="settings_security_incognito_keyboard_title">Inkognito klávesnice</string>
<string name="command_description_table_flip">Přidá znaky (╯°□°)╯︵ ┻━┻ před zprávy ve formátu obyčejného textu</string>
<string name="command_description_table_flip">Přidá znaky (╯°□°)╯︵ ┻━┻ před zprávy ve formátu prostého textu</string>
<string name="attachment_type_voice_broadcast">Hlasové vysílání</string>
<string name="command_description_devtools">Otevřít nástroje pro vývojáře</string>
<string name="room_settings_global_block_unverified_info_text">🔒 V nastavení zabezpečení jste povolili šifrování pouze do ověřených relací pro všechny místnosti.</string>
@ -2824,8 +2824,8 @@
<string name="permissions_rationale_msg_notification">${app_name} potřebuje oprávnění k zobrazování oznámení. Oznámení mohou zobrazovat vaše zprávy, pozvánky atd.
\n
\nPro zobrazování oznámení povolte přístup na dalších vyskakovacích oknech.</string>
<string name="labs_enable_rich_text_editor_summary">Vyzkoušejte rozšířený textový editor (textový režim již brzy)</string>
<string name="labs_enable_rich_text_editor_title">Povolit rozšířený textový editor</string>
<string name="labs_enable_rich_text_editor_summary">Vyzkoušejte editor formátovaného textu (režim prostého textu již brzy)</string>
<string name="labs_enable_rich_text_editor_title">Povolit editor formátovaného textu</string>
<string name="qr_code_login_confirm_security_code_description">Ujistěte se, že znáte původ tohoto kódu. Propojením zařízení poskytnete někomu plný přístup ke svému účtu.</string>
<string name="qr_code_login_confirm_security_code">Potvrdit</string>
<string name="qr_code_login_try_again">Zkuste to znovu</string>
@ -2868,7 +2868,7 @@
<string name="qr_code_login_header_failed_other_device_already_signed_in_description">Druhé zařízení je již přihlášeno.</string>
<string name="qr_code_login_header_failed_e2ee_security_issue_description">Při nastavování zabezpečeného zasílání zpráv se vyskytl problém se zabezpečením. Může být napadena jedna z následujících věcí: váš domovský server; vaše internetové připojení; vaše zařízení;</string>
<string name="qr_code_login_header_failed_other_description">Žádost se nezdařila.</string>
<string name="a11y_voice_broadcast_buffering">Ukládání do vyrovnávací paměti</string>
<string name="a11y_voice_broadcast_buffering">Ukládání do vyrovnávací paměti</string>
<string name="a11y_pause_voice_broadcast">Pozastavit hlasové vysílání</string>
<string name="a11y_play_voice_broadcast">Přehrát nebo obnovit hlasové vysílání</string>
<string name="a11y_stop_voice_broadcast_record">Ukončit záznam hlasového vysílání</string>
@ -2922,4 +2922,6 @@
<string name="quoting">Citace</string>
<string name="replying_to">Odpovídám na %s</string>
<string name="editing">Úpravy</string>
<string name="settings_enable_direct_share_summary">Zobrazit poslední chaty v nabídce sdílení systému</string>
<string name="settings_enable_direct_share_title">Povolit přímé sdílení</string>
</resources>

View file

@ -2815,7 +2815,7 @@
<string name="qr_code_login_header_failed_other_description">Die Anfrage ist fehlgeschlagen.</string>
<string name="a11y_play_voice_broadcast">Abspielen oder fortsetzen der Sprachübertragung</string>
<string name="a11y_resume_voice_broadcast_record">Fortsetzen der Sprachübertragung</string>
<string name="a11y_voice_broadcast_buffering">Puffere</string>
<string name="a11y_voice_broadcast_buffering">Puffere</string>
<string name="a11y_pause_voice_broadcast">Pausiere Sprachübertragung</string>
<string name="a11y_stop_voice_broadcast_record">Stoppe Aufzeichnung der Sprachübertragung</string>
<string name="a11y_pause_voice_broadcast_record">Pausiere Aufzeichnung der Sprachübertragung</string>
@ -2865,4 +2865,6 @@
<string name="replying_to">%s antworten</string>
<string name="device_manager_other_sessions_hide_ip_address">IP-Adresse ausblenden</string>
<string name="device_manager_other_sessions_show_ip_address">IP-Adresse anzeigen</string>
<string name="settings_enable_direct_share_summary">Kürzliche Unterhaltungen im Teilen-Menü des Systems anzeigen</string>
<string name="settings_enable_direct_share_title">Direktes Teilen aktivieren</string>
</resources>

View file

@ -2805,7 +2805,7 @@
<string name="qr_code_login_header_failed_other_device_already_signed_in_description">Teine seade on juba võrku loginud.</string>
<string name="qr_code_login_header_failed_e2ee_security_issue_description">Turvalise sõnumivahetuse ülesseadmisel tekkis turvaviga. Üks kolmest võib olla sattunud vale osapoole kontrolli alla: sinu koduserver, sinu internetiühendus või sinu seade;</string>
<string name="qr_code_login_header_failed_other_description">Päring ei õnnestunud.</string>
<string name="a11y_voice_broadcast_buffering">Andmed on puhverdamisel</string>
<string name="a11y_voice_broadcast_buffering">Andmed on puhverdamisel</string>
<string name="a11y_play_voice_broadcast">Alusta või jätka ringhäälingukõne esitamist</string>
<string name="a11y_stop_voice_broadcast_record">Lõpeta ringhäälingukõne salvestamine</string>
<string name="a11y_pause_voice_broadcast_record">Peata ringhäälingukõne salvestamine</string>
@ -2857,4 +2857,6 @@
<string name="message_reply_to_sender_sent_video">saatis video.</string>
<string name="message_reply_to_sender_sent_sticker">saatis kleepsu.</string>
<string name="message_reply_to_sender_created_poll">koostas küsitluse.</string>
<string name="settings_enable_direct_share_title">Kasuta otsejagamist</string>
<string name="settings_enable_direct_share_summary">Näita viimaseid vestlusi süsteemses jagamisvaates</string>
</resources>

View file

@ -943,7 +943,7 @@
\n
\nپیامهایتان با قفل‌هایی امن شده‌اند و فقط شما و گیرندگان دیگر، کلیدهای یکتا را برای قفل‌گشاییشان دارید.</string>
<string name="room_profile_section_security">امنیت</string>
<string name="room_profile_section_security_learn_more">بثیش‌تر بدانید</string>
<string name="room_profile_section_security_learn_more">بیش‌تر بدانید</string>
<string name="room_profile_section_more">بیش‌تر</string>
<string name="room_profile_section_admin">کنش‌های مدیر</string>
<string name="room_profile_section_more_settings">تنظمیات اتاق</string>
@ -2783,7 +2783,7 @@
<string name="attachment_type_selector_poll">نظرسنجی‌ها</string>
<string name="attachment_type_selector_file">پیوست‌ها</string>
<string name="attachment_type_selector_sticker">برچسب‌ها</string>
<string name="a11y_voice_broadcast_buffering">میانگیری</string>
<string name="a11y_voice_broadcast_buffering">میانگیری</string>
<string name="voice_broadcast_live">زنده</string>
<string name="qr_code_login_confirm_security_code">تأیید</string>
<string name="three">۳</string>
@ -2844,4 +2844,9 @@
<string name="quoting">نقل کردن</string>
<string name="replying_to">پاسخ دادن به %s</string>
<string name="editing">ویرایش کردن</string>
<string name="device_manager_sessions_sign_in_with_qr_code_description">می‌توانید با یک رمز QR از این افزاره برای ورود به افزاره‌ای همراه یا روی وب استفاده کنید. دو راه برای این کار وجود دارد:</string>
<string name="qr_code_login_header_failed_e2ee_security_issue_description">مشکلی امنیتی در برپایی پیام‌رسانی امن وجود داشت. ممکن است یکی از موارد زیر دستکاری شده باشند: کارساز خانیگیتان؛ اتّصال اینترنتیتان؛ افزاره(های)تان؛</string>
<string name="qr_code_login_confirm_security_code_description">لطفاً مطمئن شوید که مبدأ این کد را می‌دانید. با پیوند دادن افزاره‌ها، دسترسی کامل را به حسابتان می‌دهید.</string>
<string name="settings_enable_direct_share_summary">نمایش گپ‌های اخیر در فهرست هم رسانی سامانه</string>
<string name="settings_enable_direct_share_title">به کار انداختن هم‌رسانی مستقیم</string>
</resources>

View file

@ -2814,7 +2814,7 @@
<string name="device_manager_sessions_sign_in_with_qr_code_description">Vous pouvez utiliser cet appareil pour connecter un appareil mobile ou un client web avec un QR code. Il y a deux façons de le faire :</string>
<string name="device_manager_sessions_sign_in_with_qr_code_title">Se connecter avec un QR code</string>
<string name="login_scan_qr_code">Scanner le QR code</string>
<string name="a11y_voice_broadcast_buffering">Mise en mémoire tampon</string>
<string name="a11y_voice_broadcast_buffering">Mise en mémoire tampon</string>
<string name="a11y_pause_voice_broadcast">Mettre en pause la diffusion audio</string>
<string name="a11y_play_voice_broadcast">Lire ou continuer la diffusion audio</string>
<string name="a11y_stop_voice_broadcast_record">Arrêter lenregistrement de la diffusion audio</string>
@ -2866,4 +2866,6 @@
<string name="quoting">Citation de</string>
<string name="replying_to">Réponse à %s</string>
<string name="editing">Modification</string>
<string name="settings_enable_direct_share_summary">Affiche les conversations récentes dans le menu de partage du système</string>
<string name="settings_enable_direct_share_title">Activer le partage direct</string>
</resources>

View file

@ -2762,7 +2762,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string>
<string name="qr_code_login_header_failed_other_description">Permintaan gagal.</string>
<string name="labs_enable_voice_broadcast_summary">Memungkinkan untuk merekam dan mengirim siaran suara dalam linimasa ruangan.</string>
<string name="labs_enable_voice_broadcast_title">Aktifkan siaran suara (dalam pengembangan aktif)</string>
<string name="a11y_voice_broadcast_buffering">Memuat</string>
<string name="a11y_voice_broadcast_buffering">Memuat</string>
<string name="a11y_pause_voice_broadcast">Jeda siaran suara</string>
<string name="a11y_play_voice_broadcast">Mainkan atau lanjutkan siaran suara</string>
<string name="a11y_stop_voice_broadcast_record">Hentikan rekaman siaran suara</string>
@ -2812,4 +2812,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string>
<string name="editing">Mengedit</string>
<string name="device_manager_other_sessions_show_ip_address">Tampilkan alamat IP</string>
<string name="replying_to">Membalas ke %s</string>
<string name="settings_enable_direct_share_summary">Tampilkan obrolan terkini dalam menu pembagian sistem</string>
<string name="settings_enable_direct_share_title">Aktifkan pembagian langsung</string>
</resources>

View file

@ -2805,7 +2805,7 @@
<string name="qr_code_login_header_failed_other_device_already_signed_in_description">L\'altro dispositivo ha già fatto l\'accesso.</string>
<string name="qr_code_login_header_failed_e2ee_security_issue_description">Si è verificato un problema di sicurezza configurando i messaggi sicuri. Una delle seguenti cose potrebbe essere compromessa: il tuo homeserver; la/e connessione/i internet; il/i dispositivo/i;</string>
<string name="qr_code_login_header_failed_other_description">La richiesta è fallita.</string>
<string name="a11y_voice_broadcast_buffering">Buffering</string>
<string name="a11y_voice_broadcast_buffering">Buffer</string>
<string name="a11y_pause_voice_broadcast">Sospendi trasmissione vocale</string>
<string name="a11y_play_voice_broadcast">Avvia o riprendi trasmissione vocale</string>
<string name="a11y_stop_voice_broadcast_record">Ferma registrazione trasmissione vocale</string>
@ -2857,4 +2857,6 @@
<string name="quoting">Citazione</string>
<string name="replying_to">Risposta a %s</string>
<string name="editing">Modifica</string>
<string name="settings_enable_direct_share_summary">Mostra chat recenti nel menu di condivisione di sistema</string>
<string name="settings_enable_direct_share_title">Attiva condivisione diretta</string>
</resources>

View file

@ -2723,7 +2723,7 @@
<string name="device_manager_learn_more_sessions_unverified_title">Sessões não-verificadas</string>
<string name="device_manager_learn_more_sessions_inactive">Sessões inativas são sessões que você não tem usado em algum tempo, mas elas continuam a receber chaves de encriptação.
\n
\nRemover sessões inativas melhora segurança e performance, e torna-o mais fácil para você identificar se uma nova sessão é suspeita.</string>
\nRemover sessões inativas melhora segurança e performance, e torna mais fácil para você identificar se uma nova sessão é suspeita.</string>
<string name="device_manager_learn_more_sessions_inactive_title">Sessões inativas</string>
<string name="device_manager_session_rename_warning">Por favor esteja ciente que nomes de sessões também são visíveis a pessoas com quem você se comunica.</string>
<string name="device_manager_session_rename_description">Nomes de sessões personalizadas podem ajudar você a reconhecer seus dispositivos mais facilmente.</string>
@ -2844,9 +2844,9 @@
<string name="error_voice_broadcast_unauthorized_title">Não dá pra começar um novo broadcast de voz</string>
<string name="a11y_voice_broadcast_fast_forward">Avançar rápido 30 segundos</string>
<string name="a11y_voice_broadcast_fast_backward">Retroceder 30 segundos</string>
<string name="device_manager_learn_more_sessions_verified_description">Sessões verificadas são onde quer que você está usando esta conta depois de entrar sua frasepasse ou confirmar sua identidade com uma outra sessão verificada.
<string name="device_manager_learn_more_sessions_verified_description">Sessões verificadas são onde quer que você esteja usando esta conta depois de entrar sua frasepasse ou confirmar sua identidade com uma outra sessão verificada.
\n
\nIsto significa que você tem todas as chaves necessitadas para destrancar suas mensagens encriptadas e confirmar a outras(os) usuárias(os) que você confia nesta sessão.</string>
\nIsto significa que você tem todas as chaves necessárias para destrancar suas mensagens encriptadas e confirmar a outras(os) usuárias(os) que você confia nesta sessão.</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Fazer signout de %1$d sessão</item>
<item quantity="other">Fazer signout de %1$d sessões</item>

View file

@ -2868,7 +2868,7 @@
<string name="qr_code_login_header_failed_other_description">Žiadosť zlyhala.</string>
<string name="labs_enable_voice_broadcast_summary">Možnosť nahrávania a odosielania hlasového vysielania v časovej osi miestnosti.</string>
<string name="labs_enable_voice_broadcast_title">Zapnúť hlasové vysielanie (v štádiu aktívneho vývoja)</string>
<string name="a11y_voice_broadcast_buffering">Načítavanie do vyrovnávacej pamäte</string>
<string name="a11y_voice_broadcast_buffering">Načítavanie do vyrovnávacej pamäte</string>
<string name="a11y_pause_voice_broadcast">Pozastaviť hlasové vysielanie</string>
<string name="a11y_play_voice_broadcast">Prehrať alebo pokračovať v nahrávaní hlasového vysielania</string>
<string name="a11y_stop_voice_broadcast_record">Zastaviť nahrávanie hlasového vysielania</string>
@ -2922,4 +2922,6 @@
<string name="device_manager_other_sessions_show_ip_address">Zobraziť IP adresu</string>
<string name="replying_to">Odpoveď na %s</string>
<string name="editing">Úprava</string>
<string name="settings_enable_direct_share_summary">Zobraziť posledné konverzácie v systémovej ponuke zdieľania</string>
<string name="settings_enable_direct_share_title">Povoliť priame zdieľanie</string>
</resources>

View file

@ -2659,7 +2659,7 @@
\nKy shërbyes Home mund të mos jetë formësuar të shfaqë harta.</string>
<string name="poll_undisclosed_not_ended">Përfundimet do të jenë të dukshme pasi të ketë përfunduar pyetësori</string>
<string name="labs_enable_msc3061_share_history_desc">Kur bëhet ftesë në një dhomë të fshehtëzuar që ka historik ndarjesh me të tjerët, historiku i fshehtëzuar do të jetë i dukshëm.</string>
<string name="a11y_voice_broadcast_buffering">Përdo</string>
<string name="a11y_voice_broadcast_buffering"></string>
<string name="a11y_pause_voice_broadcast">Ndal transmetim zanor</string>
<string name="a11y_play_voice_broadcast">Luani ose vazhdoni luajtje transmetimi zanor</string>
<string name="a11y_stop_voice_broadcast_record">Ndal incizim transmetimi zanor</string>
@ -2851,4 +2851,6 @@
<string name="a11y_voice_broadcast_fast_backward">Kthim prapa 30 sekonda</string>
<string name="replying_to">Si përgjigje për %s</string>
<string name="labs_enable_deferred_dm_title">Aktivizo MD të lënë për më vonë</string>
<string name="a11y_collapse_space_children">Tkurr pjella të %s</string>
<string name="a11y_expand_space_children">Zgjero pjella të %s</string>
</resources>

View file

@ -2852,4 +2852,18 @@
<string name="error_voice_broadcast_unauthorized_title">Kan inte starta en ny röstsändning</string>
<string name="a11y_voice_broadcast_fast_forward">Spola framåt 30 sekunder</string>
<string name="a11y_voice_broadcast_fast_backward">Spola tillbaka 30 sekunder</string>
<string name="message_reply_to_sender_created_poll">skickade en omröstning.</string>
<string name="message_reply_to_sender_sent_sticker">skickade en dekal.</string>
<string name="message_reply_to_sender_sent_video">skickade en video.</string>
<string name="message_reply_to_sender_sent_image">skickade en bild.</string>
<string name="message_reply_to_sender_sent_voice_message">skickade ett röstmeddelande.</string>
<string name="message_reply_to_sender_sent_audio_file">skickade en ljudfil.</string>
<string name="message_reply_to_sender_sent_file">skickade en fil.</string>
<string name="message_reply_to_prefix">Svar på</string>
<string name="device_manager_other_sessions_hide_ip_address">Dölj IP-adress</string>
<string name="device_manager_other_sessions_show_ip_address">Visa IP-adress</string>
<string name="voice_broadcast_recording_time_left">%1$s kvar</string>
<string name="quoting">Citerar</string>
<string name="replying_to">Besvarar %s</string>
<string name="editing">Redigerar</string>
</resources>

View file

@ -2922,7 +2922,7 @@
<string name="qr_code_login_header_failed_other_description">Запит не виконаний.</string>
<string name="labs_enable_voice_broadcast_summary">Можливість записувати та надсилати голосові трансляції до стрічки кімнати.</string>
<string name="labs_enable_voice_broadcast_title">Увімкнути голосові трансляції (в активній розробці)</string>
<string name="a11y_voice_broadcast_buffering">Буферизація</string>
<string name="a11y_voice_broadcast_buffering">Буферизація</string>
<string name="a11y_pause_voice_broadcast">Призупинити голосову трансляцію</string>
<string name="a11y_play_voice_broadcast">Відтворити або поновити відтворення голосової трансляції</string>
<string name="a11y_stop_voice_broadcast_record">Припинити запис голосової трансляції</string>
@ -2966,16 +2966,18 @@
<string name="device_manager_other_sessions_multi_signout_selection">Вийти</string>
<string name="voice_broadcast_recording_time_left">Залишилося %1$s</string>
<string name="message_reply_to_sender_sent_audio_file">надсилає аудіофайл.</string>
<string name="message_reply_to_sender_sent_file">відправив файл.</string>
<string name="message_reply_to_sender_sent_file">надсилає файл.</string>
<string name="message_reply_to_prefix">У відповідь на</string>
<string name="device_manager_other_sessions_hide_ip_address">Сховати IP-адресу</string>
<string name="message_reply_to_sender_created_poll">створив голосування.</string>
<string name="message_reply_to_sender_sent_sticker">відправив наліпку.</string>
<string name="message_reply_to_sender_sent_video">відправив відео.</string>
<string name="message_reply_to_sender_sent_image">відправив зображення.</string>
<string name="message_reply_to_sender_sent_voice_message">відправив голосове повідомлення.</string>
<string name="message_reply_to_sender_created_poll">створює опитування.</string>
<string name="message_reply_to_sender_sent_sticker">надсилає наліпку.</string>
<string name="message_reply_to_sender_sent_video">надсилає відео.</string>
<string name="message_reply_to_sender_sent_image">надсилає зображення.</string>
<string name="message_reply_to_sender_sent_voice_message">надсилає голосове повідомлення.</string>
<string name="device_manager_other_sessions_show_ip_address">Показати IP-адресу</string>
<string name="quoting">Цитуючи</string>
<string name="replying_to">У відповідь на %s</string>
<string name="replying_to">У відповідь %s</string>
<string name="editing">Редагування</string>
<string name="settings_enable_direct_share_summary">Показувати останні бесіди в системному меню загального доступу</string>
<string name="settings_enable_direct_share_title">Увімкнути пряме поширення</string>
</resources>

View file

@ -1007,7 +1007,7 @@
<string name="settings_discovery_disconnect_with_bound_pid">您当前在身份服务器 %1$s 上共享电子邮件地址或电话号码。您需要重新连接到 %2$s 才能停止共享它们。</string>
<string name="settings_agree_to_terms">同意身份服务器 (%s) 服务条款使你可以通过电子邮件地址或电话号码被发现。</string>
<string name="labs_allow_extended_logging">启用详细日志。</string>
<string name="labs_allow_extended_logging_summary">详细日志将通过在您发送 RageShake 时提供更多日志来帮助开发人员。即使启用,应用程序也不会记录消息内容或任何其他私人数据。</string>
<string name="labs_allow_extended_logging_summary">详细日志将通过在您发送愤怒摇动RageShake时提供更多日志来帮助开发人员。即使启用,应用程序也不会记录消息内容或任何其他私人数据。</string>
<string name="error_terms_not_accepted">接收你的主服务器条款和条件后请重试。</string>
<string name="error_network_timeout">服务器似乎响应时间太长,这可能是由于连接不良或服务器错误引起的。请稍后再试。</string>
<string name="send_attachment">发送附件</string>
@ -1205,7 +1205,7 @@
<string name="settings_advanced_settings">高级设置</string>
<string name="settings_developer_mode">开发者模式</string>
<string name="settings_developer_mode_summary">开发者模式激活隐藏的功能,也可能使应用不稳定。仅供开发者使用!</string>
<string name="settings_rageshake">摇一摇</string>
<string name="settings_rageshake">愤怒摇动Rageshake</string>
<string name="settings_rageshake_detection_threshold">检测阈值</string>
<string name="settings_rageshake_detection_threshold_summary">摇动手机以测试检测阈值</string>
<string name="rageshake_detected">检测到摇动!</string>
@ -1213,7 +1213,7 @@
<string name="devices_current_device">当前会话</string>
<string name="devices_other_devices">其它会话</string>
<string name="autocomplete_limited_results">仅显示第一个结果,请输入更多字符…</string>
<string name="settings_developer_mode_fail_fast_title">快速失败</string>
<string name="settings_developer_mode_fail_fast_title">快速失败Fail-fast</string>
<string name="settings_developer_mode_fail_fast_summary">发生意外错误时,${app_name} 可能更经常崩溃</string>
<string name="command_description_shrug">在明文消息前添加 ¯\\_(ツ)_/¯</string>
<string name="create_room_encryption_title">启用加密</string>
@ -2694,7 +2694,7 @@
<string name="device_manager_verification_status_detail_other_session_unknown">验证您当前的会话以显示此会话的验证状态。</string>
<string name="device_manager_verification_status_unknown">未知的验证状态</string>
<string name="tooltip_attachment_voice_broadcast">开始语音广播</string>
<string name="a11y_voice_broadcast_buffering">缓冲</string>
<string name="a11y_voice_broadcast_buffering">正在缓冲……</string>
<string name="a11y_pause_voice_broadcast">暂停语音广播</string>
<string name="voice_broadcast_live">实时</string>
<string name="action_got_it">知道了</string>
@ -2789,4 +2789,19 @@
<plurals name="x_selected">
<item quantity="other">已选择 %1$d</item>
</plurals>
<string name="message_reply_to_sender_created_poll">已创建投票。</string>
<string name="message_reply_to_sender_sent_sticker">已发送贴纸。</string>
<string name="message_reply_to_sender_sent_video">已发送视频。</string>
<string name="message_reply_to_sender_sent_image">已发送图片。</string>
<string name="message_reply_to_sender_sent_voice_message">已发送语音消息。</string>
<string name="message_reply_to_sender_sent_audio_file">已发送音频文件。</string>
<string name="message_reply_to_sender_sent_file">已发送文件。</string>
<string name="device_manager_learn_more_sessions_verified_description">已验证的会话是在输入你的口令词组或用另一个已验证的会话确认你的身份之后你使用此账户的任何地方。
\n
\n这意味着你拥有解锁你的已加密消息和向其他用户证明你信任此会话所需的全部密钥。</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="other">登出%1$d个会话</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">登出</string>
<string name="voice_broadcast_recording_time_left">剩余%1$s</string>
</resources>

View file

@ -2760,7 +2760,7 @@
<string name="qr_code_login_header_failed_other_description">請求失敗。</string>
<string name="labs_enable_voice_broadcast_summary">可以在聊天室時間軸中錄製並傳送語音廣播。</string>
<string name="labs_enable_voice_broadcast_title">啟用語音廣播(正在積極開發中)</string>
<string name="a11y_voice_broadcast_buffering">正在緩衝</string>
<string name="a11y_voice_broadcast_buffering">正在緩衝……</string>
<string name="a11y_pause_voice_broadcast">暫停語音廣播</string>
<string name="a11y_play_voice_broadcast">播放或繼續語音廣播</string>
<string name="a11y_stop_voice_broadcast_record">停止語音廣播錄製</string>
@ -2810,4 +2810,6 @@
<string name="quoting">引用</string>
<string name="replying_to">回覆給 %s</string>
<string name="editing">正在編輯</string>
<string name="settings_enable_direct_share_summary">在系統分享選單中顯示最近聊天</string>
<string name="settings_enable_direct_share_title">啟用直接分享</string>
</resources>

View file

@ -134,6 +134,9 @@
<string name="notice_crypto_unable_to_decrypt">** Unable to decrypt: %s **</string>
<string name="notice_crypto_error_unknown_inbound_session_id">The sender\'s device has not sent us the keys for this message.</string>
<string name="notice_voice_broadcast_ended">%1$s ended a voice broadcast.</string>
<string name="notice_voice_broadcast_ended_by_you">You ended a voice broadcast.</string>
<!-- Messages -->
<!-- Room Screen -->
@ -2487,6 +2490,9 @@
<string name="settings_key_requests">Key Requests</string>
<string name="settings_export_trail">Export Audit</string>
<string name="settings_nightly_build">Nightly build</string>
<string name="settings_nightly_build_update">Get the latest build (note: you may have trouble to sign in)</string>
<string name="e2e_use_keybackup">Unlock encrypted messages history</string>
<string name="refresh">Refresh</string>
@ -2649,8 +2655,12 @@
<string name="unencrypted">Unencrypted</string>
<string name="encrypted_unverified">Encrypted by an unverified device</string>
<string name="key_authenticity_not_guaranteed">The authenticity of this encrypted message can\'t be guaranteed on this device.</string>
<string name="review_logins">Review where youre logged in</string>
<string name="verify_other_sessions">Verify all your sessions to ensure your account &amp; messages are safe</string>
<!-- TODO TO BE REMOVED -->
<string name="review_logins" tools:ignore="UnusedResources">Review where youre logged in</string>
<!-- TODO TO BE REMOVED -->
<string name="verify_other_sessions" tools:ignore="UnusedResources">Verify all your sessions to ensure your account &amp; messages are safe</string>
<string name="review_unverified_sessions_title">You have unverified sessions</string>
<string name="review_unverified_sessions_description">Review to ensure your account is safe</string>
<!-- Argument will be replaced by the other session name (e.g, Desktop, mobile) -->
<string name="verify_this_session">Verify the new login accessing your account: %1$s</string>
@ -3025,7 +3035,7 @@
<string name="labs_auto_report_uisi">Auto Report Decryption Errors.</string>
<string name="labs_auto_report_uisi_desc">Your system will automatically send logs when an unable to decrypt error occurs</string>
<string name="labs_enable_thread_messages">Enable Thread Messages</string>
<string name="labs_enable_thread_messages">Enable threaded messages</string>
<string name="labs_enable_thread_messages_desc">Note: app will be restarted</string>
<string name="settings_show_latest_profile">Show latest user info</string>
<string name="settings_show_latest_profile_description">Show the latest profile info (avatar and display name) for all the messages.</string>
@ -3094,6 +3104,7 @@
<string name="audio_message_file_size">(%1$s)</string>
<string name="voice_broadcast_live">Live</string>
<string name="voice_broadcast_live_broadcast">Live broadcast</string>
<!-- TODO Rename id to voice_broadcast_buffering -->
<string name="a11y_voice_broadcast_buffering">Buffering…</string>
<string name="a11y_resume_voice_broadcast_record">Resume voice broadcast record</string>
@ -3301,6 +3312,7 @@
<string name="device_manager_verification_status_detail_current_session_unverified">Verify your current session for enhanced secure messaging.</string>
<string name="device_manager_verification_status_detail_other_session_unverified">Verify or sign out from this session for best security and reliability.</string>
<string name="device_manager_verification_status_detail_other_session_unknown">Verify your current session to reveal this session\'s verification status.</string>
<string name="device_manager_verification_status_detail_session_encryption_not_supported">This session doesn\'t support encryption and thus can\'t be verified.</string>
<string name="device_manager_verify_session">Verify Session</string>
<string name="device_manager_view_details">View Details</string>
<string name="device_manager_other_sessions_view_all">View All (%1$d)</string>
@ -3359,6 +3371,7 @@
<item quantity="one">Sign out of %1$d session</item>
<item quantity="other">Sign out of %1$d sessions</item>
</plurals>
<string name="device_manager_signout_all_other_sessions">Sign out of all other sessions</string>
<string name="device_manager_other_sessions_show_ip_address">Show IP address</string>
<string name="device_manager_other_sessions_hide_ip_address">Hide IP address</string>
<string name="device_manager_session_overview_signout">Sign out of this session</string>
@ -3392,6 +3405,7 @@
<!-- TODO TO BE REMOVED -->
<string name="device_manager_learn_more_sessions_verified" tools:ignore="UnusedResources">Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.\n\nThis means they hold encryption keys for your previous messages, and confirm to other users you are communicating with that these sessions are really you.</string>
<string name="device_manager_learn_more_sessions_verified_description">Verified sessions are anywhere you are using this account after entering your passphrase or confirming your identity with another verified session.\n\nThis means that you have all the keys needed to unlock your encrypted messages and confirm to other users that you trust this session.</string>
<string name="device_manager_learn_more_sessions_encryption_not_supported">This session doesn\'t support encryption, so it can\'t be verified.\n\nYou won\'t be able to participate in rooms where encryption is enabled when using this session.\n\nFor best security and privacy, it is recommended to use Matrix clients that support encryption.</string>
<string name="device_manager_learn_more_session_rename_title">Renaming sessions</string>
<string name="device_manager_learn_more_session_rename">Other users in direct messages and rooms that you join are able to view a full list of your sessions.\n\nThis provides them with confidence that they are really speaking to you, but it also means they can see the session name you enter here.</string>
<string name="labs_enable_session_manager_title">Enable new session manager</string>

View file

@ -44,4 +44,4 @@
<color name="palette_black_800">#15191E</color>
<color name="palette_black_950">#21262C</color>
</resources>
</resources>

View file

@ -53,7 +53,7 @@
<item name="vctr_list_separator">?vctr_content_quinary</item>
<item name="vctr_list_separator_system">?vctr_system</item>
<item name="vctr_list_separator_on_surface">?vctr_system</item>
<item name="vctr_unread_background">?vctr_content_tertiary</item>
<item name="vctr_unread_background">?vctr_notice_secondary</item>
<!-- Material color -->
<item name="colorPrimary">@color/element_accent_dark</item>

View file

@ -53,7 +53,7 @@
<item name="vctr_list_separator">?vctr_content_quinary</item>
<item name="vctr_list_separator_system">?vctr_system</item>
<item name="vctr_list_separator_on_surface">?vctr_system</item>
<item name="vctr_unread_background">?vctr_content_tertiary</item>
<item name="vctr_unread_background">?vctr_notice_secondary</item>
<!-- Material color -->
<item name="colorPrimary">@color/element_accent_light</item>

View file

@ -31,7 +31,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
import org.matrix.android.sdk.api.session.room.send.UserDraft
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
@ -119,13 +118,6 @@ class FlowRoom(private val room: Room) {
return room.roomPushRuleService().getLiveRoomNotificationState().asFlow()
}
fun liveThreadSummaries(): Flow<List<ThreadSummary>> {
return room.threadsService().getAllThreadSummariesLive().asFlow()
.startWith(room.coroutineDispatchers.io) {
room.threadsService().getAllThreadSummaries()
}
}
fun liveThreadList(): Flow<List<ThreadRootEvent>> {
return room.threadsLocalService().getAllThreadsLive().asFlow()
.startWith(room.coroutineDispatchers.io) {

View file

@ -62,7 +62,7 @@ android {
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
buildConfigField "String", "SDK_VERSION", "\"1.5.11\""
buildConfigField "String", "SDK_VERSION", "\"1.5.12\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a7acd69f37612bab0a1ab7f456656712d7ba19dbb679f81b97b58ef44e239f42
size 8523776

View file

@ -16,7 +16,7 @@
package org.matrix.android.sdk.common
import org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider
import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider {

View file

@ -0,0 +1,65 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import io.realm.Realm
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
import org.matrix.android.sdk.internal.util.time.Clock
class CryptoSanityMigrationTest {
@get:Rule val configurationFactory = TestRealmConfigurationFactory()
lateinit var context: Context
var realm: Realm? = null
@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().context
}
@After
fun tearDown() {
realm?.close()
}
@Test
fun cryptoDatabaseShouldMigrateGracefully() {
val realmName = "crypto_store_20.realm"
val migration = RealmCryptoStoreMigration(object : Clock {
override fun epochMillis(): Long {
return 0L
}
})
val realmConfiguration = configurationFactory.createConfiguration(
realmName,
"7b9a21a8a311e85d75b069a343c23fc952fc3fec5e0c83ecfa13f24b787479c487c3ed587db3dd1f5805d52041fc0ac246516e94b27ffa699ff928622e621aca",
RealmCryptoStoreModule(),
migration.schemaVersion,
migration
)
configurationFactory.copyRealmFromAssets(context, realmName, realmName)
realm = Realm.getInstance(realmConfiguration)
}
}

View file

@ -20,6 +20,9 @@ import okhttp3.ConnectionSpec
import okhttp3.Interceptor
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.metrics.MetricPlugin
import org.matrix.android.sdk.api.provider.CustomEventTypesProvider
import org.matrix.android.sdk.api.provider.MatrixItemDisplayNameFallbackProvider
import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
import java.net.Proxy
data class MatrixConfiguration(
@ -66,7 +69,7 @@ data class MatrixConfiguration(
/**
* Thread messages default enable/disabled value.
*/
val threadMessagesEnabledDefault: Boolean = false,
val threadMessagesEnabledDefault: Boolean = true,
/**
* List of network interceptors, they will be added when building an OkHttp client.
*/
@ -75,9 +78,12 @@ data class MatrixConfiguration(
* Sync configuration.
*/
val syncConfig: SyncConfig = SyncConfig(),
/**
* Metrics plugin that can be used to capture metrics from matrix-sdk-android.
*/
val metricPlugins: List<MetricPlugin> = emptyList()
val metricPlugins: List<MetricPlugin> = emptyList(),
/**
* CustomEventTypesProvider to provide custom event types to the sdk which should be processed with internal events.
*/
val customEventTypesProvider: CustomEventTypesProvider? = null,
)

View file

@ -21,5 +21,6 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class LocalNotificationSettingsContent(
@Json(name = "is_silenced") val isSilenced: Boolean = false
@Json(name = "is_silenced")
val isSilenced: Boolean?
)

View file

@ -125,12 +125,6 @@ interface AuthenticationService {
deviceId: String? = null
): Session
/**
* @param homeServerConnectionConfig the information about the homeserver and other configuration
* Return true if qr code login is supported by the server, false otherwise.
*/
suspend fun isQrLoginSupported(homeServerConnectionConfig: HomeServerConnectionConfig): Boolean
/**
* Authenticate using m.login.token method during sign in with QR code.
* @param homeServerConnectionConfig the information about the homeserver and other configuration

View file

@ -22,5 +22,6 @@ data class LoginFlowResult(
val isLoginAndRegistrationSupported: Boolean,
val homeServerUrl: String,
val isOutdatedHomeserver: Boolean,
val isLogoutDevicesSupported: Boolean
val isLogoutDevicesSupported: Boolean,
val isLoginWithQrSupported: Boolean,
)

View file

@ -0,0 +1,30 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.provider
import org.matrix.android.sdk.api.session.room.model.RoomSummary
/**
* Provide custom event types which should be processed with the internal event types.
*/
interface CustomEventTypesProvider {
/**
* Custom event types to include when computing [RoomSummary.latestPreviewableEvent].
*/
val customPreviewableEventTypes: List<String>
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.matrix.android.sdk.api
package org.matrix.android.sdk.api.provider
import org.matrix.android.sdk.api.util.MatrixItem

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.matrix.android.sdk.api
package org.matrix.android.sdk.api.provider
/**
* This interface exists to let the implementation provide localized room display name fallback.

View file

@ -35,7 +35,10 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.util.MatrixJsonParser
import org.matrix.android.sdk.api.util.awaitCallback
import timber.log.Timber
/**
@ -147,6 +150,14 @@ class Rendezvous(
val deviceKey = crypto.getMyDevice().fingerprint()
send(Payload(PayloadType.PROGRESS, outcome = Outcome.SUCCESS, deviceId = deviceId, deviceKey = deviceKey))
try {
// explicitly download keys for ourself rather than racing with initial sync which might not complete in time
awaitCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { crypto.downloadKeys(listOf(userId), false, it) }
} catch (e: Throwable) {
// log as warning and continue as initial sync might still complete
Timber.tag(TAG).w(e, "Failed to download keys for self")
}
// await confirmation of verification
val verificationResponse = receive()
if (verificationResponse?.outcome == Outcome.VERIFIED) {

View file

@ -63,4 +63,17 @@ interface SessionAccountDataService {
* Update the account data with the provided type and the provided account data content.
*/
suspend fun updateUserAccountData(type: String, content: Content)
/**
* Retrieve user account data list whose type starts with the given type.
* @param type the type or the starting part of a type
* @return list of account data whose type starts with the given type
*/
fun getUserAccountDataEventsStartWith(type: String): List<UserAccountDataEvent>
/**
* Deletes user account data of the given type.
* @param type the type to delete from user account data
*/
suspend fun deleteUserAccountData(type: String)
}

View file

@ -16,6 +16,8 @@
package org.matrix.android.sdk.api.session.events.model
import org.matrix.android.sdk.api.session.room.model.message.MessageType.MSGTYPE_VERIFICATION_REQUEST
/**
* Constants defining known event types from Matrix specifications.
*/
@ -126,6 +128,7 @@ object EventType {
fun isVerificationEvent(type: String): Boolean {
return when (type) {
MSGTYPE_VERIFICATION_REQUEST,
KEY_VERIFICATION_START,
KEY_VERIFICATION_ACCEPT,
KEY_VERIFICATION_KEY,

View file

@ -0,0 +1,23 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.room.threads
sealed class FetchThreadsResult {
data class ShouldFetchMore(val nextBatch: String) : FetchThreadsResult()
object ReachedEnd : FetchThreadsResult()
object Failed : FetchThreadsResult()
}

View file

@ -0,0 +1,26 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.room.threads
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class ThreadFilter {
@Json(name = "all") ALL,
@Json(name = "participated") PARTICIPATED,
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.room.threads
import androidx.lifecycle.LiveData
import androidx.paging.PagedList
import org.matrix.android.sdk.api.session.room.ResultBoundaries
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
data class ThreadLivePageResult(
val livePagedList: LiveData<PagedList<ThreadSummary>>,
val liveBoundaries: LiveData<ResultBoundaries>
)

View file

@ -16,7 +16,7 @@
package org.matrix.android.sdk.api.session.room.threads
import androidx.lifecycle.LiveData
import androidx.paging.PagedList
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
/**
@ -27,15 +27,14 @@ import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
*/
interface ThreadsService {
/**
* Returns a [LiveData] list of all the [ThreadSummary] that exists at the room level.
*/
fun getAllThreadSummariesLive(): LiveData<List<ThreadSummary>>
suspend fun getPagedThreadsList(userParticipating: Boolean, pagedListConfig: PagedList.Config): ThreadLivePageResult
suspend fun fetchThreadList(nextBatchId: String?, limit: Int, filter: ThreadFilter = ThreadFilter.ALL): FetchThreadsResult
/**
* Returns a list of all the [ThreadSummary] that exists at the room level.
*/
fun getAllThreadSummaries(): List<ThreadSummary>
suspend fun getAllThreadSummaries(): List<ThreadSummary>
/**
* Enhance the provided ThreadSummary[List] by adding the latest
@ -51,9 +50,4 @@ interface ThreadsService {
* @param limit defines the number of max results the api will respond with
*/
suspend fun fetchThreadTimeline(rootThreadEventId: String, from: String, limit: Int)
/**
* Fetch all thread summaries for the current room using the enhanced /messages api.
*/
suspend fun fetchThreadSummaries()
}

View file

@ -30,7 +30,6 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixIdFailure
import org.matrix.android.sdk.api.session.Session
@ -299,7 +298,8 @@ internal class DefaultAuthenticationService @Inject constructor(
isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(),
homeServerUrl = homeServerUrl,
isOutdatedHomeserver = !versions.isSupportedBySdk(),
isLogoutDevicesSupported = versions.doesServerSupportLogoutDevices()
isLogoutDevicesSupported = versions.doesServerSupportLogoutDevices(),
isLoginWithQrSupported = versions.doesServerSupportQrCodeLogin(),
)
}
@ -408,20 +408,6 @@ internal class DefaultAuthenticationService @Inject constructor(
)
}
override suspend fun isQrLoginSupported(homeServerConnectionConfig: HomeServerConnectionConfig): Boolean {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
val versions = runCatching {
executeRequest(null) {
authAPI.versions()
}
}
return if (versions.isSuccess) {
versions.getOrNull()?.doesServerSupportQrCodeLogin().orFalse()
} else {
false
}
}
override suspend fun loginUsingQrLoginToken(
homeServerConnectionConfig: HomeServerConnectionConfig,
loginToken: String,

View file

@ -17,14 +17,22 @@
package org.matrix.android.sdk.internal.crypto.tasks
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
const val TO_DEVICE_TRACING_ID_KEY = "org.matrix.msgid"
fun Event.toDeviceTracingId(): String? = content?.get(TO_DEVICE_TRACING_ID_KEY) as? String
internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
data class Params(
// the type of event to send
@ -32,7 +40,9 @@ internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
// the content to send. Map from user_id to device_id to content dictionary.
val contentMap: MXUsersDevicesMap<Any>,
// the transactionId. If not provided, a transactionId will be created by the task
val transactionId: String? = null
val transactionId: String? = null,
// add tracing id, notice that to device events that do signature on content might be broken by it
val addTracingIds: Boolean = !EventType.isVerificationEvent(eventType),
)
}
@ -42,15 +52,22 @@ internal class DefaultSendToDeviceTask @Inject constructor(
) : SendToDeviceTask {
override suspend fun execute(params: SendToDeviceTask.Params) {
val sendToDeviceBody = SendToDeviceBody(
messages = params.contentMap.map
)
// If params.transactionId is not provided, we create a unique txnId.
// It's important to do that outside the requestBlock parameter of executeRequest()
// to use the same value if the request is retried
val txnId = params.transactionId ?: createUniqueTxnId()
// add id tracing to debug
val decorated = if (params.addTracingIds) {
decorateWithToDeviceTracingIds(params)
} else {
params.contentMap.map to emptyList()
}
val sendToDeviceBody = SendToDeviceBody(
messages = decorated.first
)
return executeRequest(
globalErrorReceiver,
canRetry = true,
@ -61,8 +78,35 @@ internal class DefaultSendToDeviceTask @Inject constructor(
transactionId = txnId,
body = sendToDeviceBody
)
Timber.i("Sent to device type=${params.eventType} txnid=$txnId [${decorated.second.joinToString(",")}]")
}
}
/**
* To make it easier to track down where to-device messages are getting lost,
* add a custom property to each one, and that will be logged after sent and on reception. Synapse will also log
* this property.
* @return A pair, first is the decorated content, and second info to log out after sending
*/
private fun decorateWithToDeviceTracingIds(params: SendToDeviceTask.Params): Pair<Map<String, Map<String, Any>>, List<String>> {
val tracingInfo = mutableListOf<String>()
val decoratedContent = params.contentMap.map.map { userToDeviceMap ->
val userId = userToDeviceMap.key
userId to userToDeviceMap.value.map {
val deviceId = it.key
deviceId to it.value.toContent().toMutableMap().apply {
put(
TO_DEVICE_TRACING_ID_KEY,
UUID.randomUUID().toString().also {
tracingInfo.add("$userId/$deviceId (msgid $it)")
}
)
}
}.toMap()
}.toMap()
return decoratedContent to tracingInfo
}
}
internal fun createUniqueTxnId() = UUID.randomUUID().toString()

View file

@ -62,6 +62,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo042
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo043
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo044
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046
import org.matrix.android.sdk.internal.util.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject
@ -70,7 +71,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer
) : MatrixRealmMigration(
dbName = "Session",
schemaVersion = 45L,
schemaVersion = 46L,
) {
/**
* Forces all RealmSessionStoreMigration instances to be equal.
@ -125,5 +126,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 43) MigrateSessionTo043(realm).perform()
if (oldVersion < 44) MigrateSessionTo044(realm).perform()
if (oldVersion < 45) MigrateSessionTo045(realm).perform()
if (oldVersion < 46) MigrateSessionTo046(realm).perform()
}
}

View file

@ -37,9 +37,11 @@ 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.model.RoomEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
import org.matrix.android.sdk.internal.database.query.get
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.database.query.getOrNull
import org.matrix.android.sdk.internal.database.query.where
@ -113,16 +115,16 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate(
userId: String,
cryptoService: CryptoService? = null,
currentTimeMillis: Long,
) {
): ThreadSummaryEntity? {
when (threadSummaryType) {
ThreadSummaryUpdateType.REPLACE -> {
rootThreadEvent?.eventId ?: return
rootThreadEvent.senderId ?: return
rootThreadEvent?.eventId ?: return null
rootThreadEvent.senderId ?: return null
val numberOfThreads = rootThreadEvent.unsignedData?.relations?.latestThread?.count ?: return
val numberOfThreads = rootThreadEvent.unsignedData?.relations?.latestThread?.count ?: return null
// Something is wrong with the server return
if (numberOfThreads <= 0) return
if (numberOfThreads <= 0) return null
val threadSummary = ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEvent.eventId).also {
Timber.i("###THREADS ThreadSummaryHelper REPLACE eventId:${it.rootThreadEventId} ")
@ -153,12 +155,13 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate(
)
roomEntity.addIfNecessary(threadSummary)
return threadSummary
}
ThreadSummaryUpdateType.ADD -> {
val rootThreadEventId = threadEventEntity?.rootThreadEventId ?: return
val rootThreadEventId = threadEventEntity?.rootThreadEventId ?: return null
Timber.i("###THREADS ThreadSummaryHelper ADD for root eventId:$rootThreadEventId")
val threadSummary = ThreadSummaryEntity.getOrNull(realm, roomId, rootThreadEventId)
var threadSummary = ThreadSummaryEntity.getOrNull(realm, roomId, rootThreadEventId)
if (threadSummary != null) {
// ThreadSummary exists so lets add the latest event
Timber.i("###THREADS ThreadSummaryHelper ADD root eventId:$rootThreadEventId exists, lets update latest thread event.")
@ -172,7 +175,7 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate(
Timber.i("###THREADS ThreadSummaryHelper ADD root eventId:$rootThreadEventId do not exists, lets try to create one")
threadEventEntity.findRootThreadEvent()?.let { rootThreadEventEntity ->
// Root thread event entity exists so lets create a new record
ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEventEntity.eventId).let {
threadSummary = ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEventEntity.eventId).also {
it.updateThreadSummary(
rootThreadEventEntity = rootThreadEventEntity,
numberOfThreads = 1,
@ -183,7 +186,12 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate(
roomEntity.addIfNecessary(it)
}
}
threadSummary?.let {
ThreadListPageEntity.get(realm, roomId)?.threadSummaries?.add(it)
}
}
return threadSummary
}
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
internal class MigrateSessionTo046(realm: DynamicRealm) : RealmMigrator(realm, 46) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.create("ThreadListPageEntity")
.addField(ThreadListPageEntityFields.ROOM_ID, String::class.java)
.addPrimaryKey(ThreadListPageEntityFields.ROOM_ID)
.setRequired(ThreadListPageEntityFields.ROOM_ID, true)
.addRealmListField(ThreadListPageEntityFields.THREAD_SUMMARIES.`$`, realm.schema.get("ThreadSummaryEntity")!!)
}
}

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database.model
import io.realm.annotations.RealmModule
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
/**
@ -72,6 +73,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
UserPresenceEntity::class,
ThreadSummaryEntity::class,
SyncFilterParamsEntity::class,
ThreadListPageEntity::class
]
)
internal class SessionRealmModule

View file

@ -0,0 +1,28 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database.model.threads
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
internal open class ThreadListPageEntity(
@PrimaryKey var roomId: String = "",
var threadSummaries: RealmList<ThreadSummaryEntity> = RealmList()
) : RealmObject() {
companion object
}

View file

@ -40,5 +40,8 @@ internal open class ThreadSummaryEntity(
@LinkingObjects("threadSummaries")
val room: RealmResults<RoomEntity>? = null
@LinkingObjects("threadSummaries")
val page: RealmResults<ThreadListPageEntity>? = null
companion object
}

View file

@ -21,6 +21,7 @@ import io.realm.RealmQuery
import io.realm.kotlin.where
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
import org.matrix.android.sdk.internal.database.model.RoomEntity
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
@ -44,3 +45,11 @@ internal fun RoomEntity.Companion.where(realm: Realm, membership: Membership? =
internal fun RoomEntity.fastContains(eventId: String): Boolean {
return EventEntity.where(realm, eventId = eventId).findFirst() != null
}
internal fun RoomEntity.removeAccountData(type: String) {
accountData
.where()
.equalTo(RoomAccountDataEntityFields.TYPE, type)
.findFirst()
?.deleteFromRealm()
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database.query
import io.realm.Realm
import io.realm.kotlin.createObject
import io.realm.kotlin.where
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntityFields
internal fun ThreadListPageEntity.Companion.get(realm: Realm, roomId: String): ThreadListPageEntity? {
return realm.where<ThreadListPageEntity>().equalTo(ThreadListPageEntityFields.ROOM_ID, roomId).findFirst()
}
internal fun ThreadListPageEntity.Companion.getOrCreate(realm: Realm, roomId: String): ThreadListPageEntity {
return get(realm, roomId) ?: realm.createObject(roomId)
}

View file

@ -22,6 +22,7 @@ import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
import io.realm.kotlin.where
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
import org.matrix.android.sdk.internal.database.model.ChunkEntity
@ -94,14 +95,27 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
if (filters.filterTypes && filters.allowedTypes.isNotEmpty()) {
beginGroup()
filters.allowedTypes.forEachIndexed { index, filter ->
if (filter.stateKey == null) {
equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType)
if (filter.eventType == EventType.ENCRYPTED) {
val otherTypes = filters.allowedTypes.minus(filter).map { it.eventType }
if (filter.stateKey == null) {
filterEncryptedTypes(otherTypes)
} else {
beginGroup()
filterEncryptedTypes(otherTypes)
and()
equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey)
endGroup()
}
} else {
beginGroup()
equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType)
and()
equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey)
endGroup()
if (filter.stateKey == null) {
equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType)
} else {
beginGroup()
equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType)
and()
equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey)
endGroup()
}
}
if (index != filters.allowedTypes.size - 1) {
or()
@ -115,7 +129,6 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
if (filters.filterEdits) {
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.REFERENCE)
}
if (filters.filterRedacted) {
not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED)
@ -124,6 +137,21 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
return this
}
internal fun RealmQuery<TimelineEventEntity>.filterEncryptedTypes(allowedTypes: List<String>): RealmQuery<TimelineEventEntity> {
beginGroup()
equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.ENCRYPTED)
and()
beginGroup()
isNull(TimelineEventEntityFields.ROOT.DECRYPTION_RESULT_JSON)
allowedTypes.forEach { eventType ->
or()
like(TimelineEventEntityFields.ROOT.DECRYPTION_RESULT_JSON, TimelineEventFilter.DecryptedContent.type(eventType))
}
endGroup()
endGroup()
return this
}
internal fun RealmQuery<TimelineEventEntity>.filterTypes(filterTypes: List<String>): RealmQuery<TimelineEventEntity> {
return if (filterTypes.isEmpty()) {
this

View file

@ -34,6 +34,7 @@ internal object TimelineEventFilter {
*/
internal object DecryptedContent {
internal const val URL = """{*"file":*"url":*}"""
fun type(type: String) = """{*"type":*"$type"*}"""
}
/**

View file

@ -0,0 +1,33 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database.query
import io.realm.Realm
import io.realm.kotlin.where
import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity
import org.matrix.android.sdk.internal.database.model.UserAccountDataEntityFields
/**
* Delete an account_data event.
*/
internal fun UserAccountDataEntity.Companion.delete(realm: Realm, type: String) {
realm
.where<UserAccountDataEntity>()
.equalTo(UserAccountDataEntityFields.TYPE, type)
.findFirst()
?.deleteFromRealm()
}

View file

@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.session.room.membership.joining.InviteBod
import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody
import org.matrix.android.sdk.internal.session.room.read.ReadBody
import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
import org.matrix.android.sdk.internal.session.room.relation.threads.ThreadSummariesResponse
import org.matrix.android.sdk.internal.session.room.reporting.ReportContentBody
import org.matrix.android.sdk.internal.session.room.send.SendResponse
import org.matrix.android.sdk.internal.session.room.tags.TagBody
@ -427,6 +428,19 @@ internal interface RoomAPI {
@Body content: JsonDict
)
/**
* Remove an account_data event from the room.
* @param userId the user id
* @param roomId the room id
* @param type the type
*/
@DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc3391/user/{userId}/rooms/{roomId}/account_data/{type}")
suspend fun deleteRoomAccountData(
@Path("userId") userId: String,
@Path("roomId") roomId: String,
@Path("type") type: String
)
/**
* Upgrades the given room to a particular room version.
* Errors:
@ -451,4 +465,12 @@ internal interface RoomAPI {
@Path("roomIdOrAlias") roomidOrAlias: String,
@Query("via") viaServers: List<String>?
): RoomStrippedState
@GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "rooms/{roomId}/threads")
suspend fun getThreadsList(
@Path("roomId") roomId: String,
@Query("include") include: String? = "all",
@Query("from") from: String? = null,
@Query("limit") limit: Int? = null
): ThreadSummariesResponse
}

View file

@ -29,7 +29,6 @@ import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
import org.matrix.android.sdk.api.session.room.model.VoteInfo
import org.matrix.android.sdk.api.session.room.model.VoteSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
@ -78,7 +77,7 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
val content = event.getClearContent()?.toModel<MessagePollResponseContent>() ?: return false
val roomId = event.roomId ?: return false
val senderId = event.senderId ?: return false
val targetEventId = (event.getRelationContent() ?: content.relatesTo)?.eventId ?: return false
val targetEventId = event.getRelationContent()?.eventId ?: return false
val targetPollContent = getPollContent(session, roomId, targetEventId) ?: return false
val annotationsSummaryEntity = getAnnotationsSummaryEntity(realm, roomId, targetEventId)
@ -154,9 +153,8 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
}
override fun handlePollEndEvent(session: Session, powerLevelsHelper: PowerLevelsHelper, realm: Realm, event: Event): Boolean {
val content = event.getClearContent()?.toModel<MessageEndPollContent>() ?: return false
val roomId = event.roomId ?: return false
val pollEventId = content.relatesTo?.eventId ?: return false
val pollEventId = event.getRelationContent()?.eventId ?: return false
val pollOwnerId = getPollEvent(session, roomId, pollEventId)?.root?.senderId
val isPollOwner = pollOwnerId == event.senderId

View file

@ -46,7 +46,7 @@ internal class DefaultStartLiveLocationShareTask @Inject constructor(
isLive = true,
unstableTimestampMillis = clock.epochMillis()
).toContent()
val eventType = EventType.STATE_ROOM_BEACON_INFO.stable
val eventType = EventType.STATE_ROOM_BEACON_INFO.unstable
val sendStateTaskParams = SendStateTask.Params(
roomId = params.roomId,
stateKey = userId,

View file

@ -45,7 +45,7 @@ internal class DefaultStopLiveLocationShareTask @Inject constructor(
val sendStateTaskParams = SendStateTask.Params(
roomId = params.roomId,
stateKey = stateKey,
eventType = EventType.STATE_ROOM_BEACON_INFO.stable,
eventType = EventType.STATE_ROOM_BEACON_INFO.unstable,
body = updatedContent
)
return try {

View file

@ -16,37 +16,38 @@
package org.matrix.android.sdk.internal.session.room.relation.threads
import com.zhuinden.monarchy.Monarchy
import io.realm.RealmList
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.threads.FetchThreadsResult
import org.matrix.android.sdk.api.session.room.threads.ThreadFilter
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.database.helper.createOrUpdate
import org.matrix.android.sdk.internal.database.model.RoomEntity
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.filter.FilterFactory
import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse
import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.awaitTransaction
import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber
import javax.inject.Inject
/***
* This class is responsible to Fetch all the thread in the current room,
* To fetch all threads in a room, the /messages API is used with newly added filtering options.
*/
internal interface FetchThreadSummariesTask : Task<FetchThreadSummariesTask.Params, DefaultFetchThreadSummariesTask.Result> {
internal interface FetchThreadSummariesTask : Task<FetchThreadSummariesTask.Params, FetchThreadsResult> {
data class Params(
val roomId: String,
val from: String = "",
val limit: Int = 500,
val isUserParticipating: Boolean = true
val from: String? = null,
val limit: Int = 5,
val filter: ThreadFilter? = null,
)
}
@ -59,39 +60,43 @@ internal class DefaultFetchThreadSummariesTask @Inject constructor(
private val clock: Clock,
) : FetchThreadSummariesTask {
override suspend fun execute(params: FetchThreadSummariesTask.Params): Result {
val filter = FilterFactory.createThreadsFilter(
numberOfEvents = params.limit,
userId = if (params.isUserParticipating) userId else null
).toJSONString()
val response = executeRequest(
globalErrorReceiver,
canRetry = true
) {
roomAPI.getRoomMessagesFrom(params.roomId, params.from, PaginationDirection.BACKWARDS.value, params.limit, filter)
override suspend fun execute(params: FetchThreadSummariesTask.Params): FetchThreadsResult {
val response = executeRequest(globalErrorReceiver) {
roomAPI.getThreadsList(
roomId = params.roomId,
include = params.filter?.toString()?.lowercase(),
from = params.from,
limit = params.limit
)
}
Timber.i("###THREADS DefaultFetchThreadSummariesTask Fetched size:${response.events.size} nextBatch:${response.end} ")
handleResponse(response, params)
return handleResponse(response, params)
return when {
response.nextBatch != null -> FetchThreadsResult.ShouldFetchMore(response.nextBatch)
else -> FetchThreadsResult.ReachedEnd
}
}
private suspend fun handleResponse(
response: PaginationResponse,
response: ThreadSummariesResponse,
params: FetchThreadSummariesTask.Params
): Result {
val rootThreadList = response.events
) {
val rootThreadList = response.chunk
val threadSummaries = RealmList<ThreadSummaryEntity>()
monarchy.awaitTransaction { realm ->
val roomEntity = RoomEntity.where(realm, roomId = params.roomId).findFirst() ?: return@awaitTransaction
val roomMemberContentsByUser = HashMap<String, RoomMemberContent?>()
for (rootThreadEvent in rootThreadList) {
if (rootThreadEvent.eventId == null || rootThreadEvent.senderId == null || rootThreadEvent.type == null) {
continue
}
ThreadSummaryEntity.createOrUpdate(
val threadSummary = ThreadSummaryEntity.createOrUpdate(
threadSummaryType = ThreadSummaryUpdateType.REPLACE,
realm = realm,
roomId = params.roomId,
@ -102,14 +107,16 @@ internal class DefaultFetchThreadSummariesTask @Inject constructor(
cryptoService = cryptoService,
currentTimeMillis = clock.epochMillis(),
)
threadSummaries.add(threadSummary)
}
val page = ThreadListPageEntity.getOrCreate(realm, params.roomId)
threadSummaries.forEach {
if (!page.threadSummaries.contains(it)) {
page.threadSummaries.add(it)
}
}
}
return Result.SUCCESS
}
enum class Result {
SHOULD_FETCH_MORE,
REACHED_END,
SUCCESS
}
}

View file

@ -0,0 +1,27 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.room.relation.threads
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.events.model.Event
@JsonClass(generateAdapter = true)
internal data class ThreadSummariesResponse(
@Json(name = "chunk") val chunk: List<Event>,
@Json(name = "next_batch") val nextBatch: String?,
@Json(name = "prev_batch") val prevBatch: String?
)

View file

@ -181,7 +181,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
type = EventType.POLL_START.stable,
type = EventType.POLL_START.unstable,
content = newContent.toContent().plus(additionalContent.orEmpty())
)
}
@ -206,7 +206,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
type = EventType.POLL_RESPONSE.stable,
type = EventType.POLL_RESPONSE.unstable,
content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
@ -226,7 +226,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
type = EventType.POLL_START.stable,
type = EventType.POLL_START.unstable,
content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
@ -249,7 +249,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
type = EventType.POLL_END.stable,
type = EventType.POLL_END.unstable,
content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
@ -300,7 +300,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
type = EventType.BEACON_LOCATION_DATA.stable,
type = EventType.BEACON_LOCATION_DATA.unstable,
content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)

View file

@ -17,17 +17,23 @@
package org.matrix.android.sdk.internal.session.room.summary
import io.realm.Realm
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.session.room.summary.RoomSummaryConstants
import org.matrix.android.sdk.api.session.room.timeline.EventTypeFilter
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.latestEvent
import javax.inject.Inject
internal object RoomSummaryEventsHelper {
internal class RoomSummaryEventsHelper @Inject constructor(
matrixConfiguration: MatrixConfiguration,
) {
private val previewFilters = TimelineEventFilters(
filterTypes = true,
allowedTypes = RoomSummaryConstants.PREVIEWABLE_TYPES.map { EventTypeFilter(eventType = it, stateKey = null) },
allowedTypes = RoomSummaryConstants.PREVIEWABLE_TYPES
.plus(matrixConfiguration.customEventTypesProvider?.customPreviewableEventTypes.orEmpty())
.map { EventTypeFilter(eventType = it, stateKey = null) },
filterUseless = true,
filterRedacted = false,
filterEdits = true

View file

@ -78,12 +78,13 @@ internal class RoomSummaryUpdater @Inject constructor(
private val crossSigningService: DefaultCrossSigningService,
private val roomAccountDataDataSource: RoomAccountDataDataSource,
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
private val roomSummaryEventsHelper: RoomSummaryEventsHelper,
) {
fun refreshLatestPreviewContent(realm: Realm, roomId: String) {
val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId)
if (roomSummaryEntity != null) {
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
val latestPreviewableEvent = roomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
latestPreviewableEvent?.attemptToDecrypt()
}
}
@ -145,7 +146,7 @@ internal class RoomSummaryUpdater @Inject constructor(
val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root
Timber.d("## CRYPTO: currentEncryptionEvent is $encryptionEvent")
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
val latestPreviewableEvent = roomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs
if (lastActivityFromEvent != null) {
@ -231,7 +232,7 @@ internal class RoomSummaryUpdater @Inject constructor(
fun updateSendingInformation(realm: Realm, roomId: String) {
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
roomSummaryEntity.updateHasFailedSending()
roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
roomSummaryEntity.latestPreviewableEvent = roomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
}
/**

View file

@ -16,32 +16,39 @@
package org.matrix.android.sdk.internal.session.room.threads
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import com.zhuinden.monarchy.Monarchy
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import io.realm.Realm
import io.realm.Sort
import io.realm.kotlin.where
import org.matrix.android.sdk.api.session.room.ResultBoundaries
import org.matrix.android.sdk.api.session.room.threads.FetchThreadsResult
import org.matrix.android.sdk.api.session.room.threads.ThreadFilter
import org.matrix.android.sdk.api.session.room.threads.ThreadLivePageResult
import org.matrix.android.sdk.api.session.room.threads.ThreadsService
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
import org.matrix.android.sdk.internal.database.helper.enhanceWithEditions
import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId
import org.matrix.android.sdk.internal.database.mapper.ThreadSummaryMapper
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadSummariesTask
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
import org.matrix.android.sdk.internal.util.awaitTransaction
internal class DefaultThreadsService @AssistedInject constructor(
@Assisted private val roomId: String,
@UserId private val userId: String,
private val fetchThreadTimelineTask: FetchThreadTimelineTask,
private val fetchThreadSummariesTask: FetchThreadSummariesTask,
@SessionDatabase private val monarchy: Monarchy,
private val timelineEventMapper: TimelineEventMapper,
private val threadSummaryMapper: ThreadSummaryMapper
private val threadSummaryMapper: ThreadSummaryMapper,
private val fetchThreadSummariesTask: FetchThreadSummariesTask,
) : ThreadsService {
@AssistedFactory
@ -49,16 +56,58 @@ internal class DefaultThreadsService @AssistedInject constructor(
fun create(roomId: String): DefaultThreadsService
}
override fun getAllThreadSummariesLive(): LiveData<List<ThreadSummary>> {
return monarchy.findAllMappedWithChanges(
{ ThreadSummaryEntity.findAllThreadsForRoomId(it, roomId = roomId) },
{
threadSummaryMapper.map(it)
override suspend fun getPagedThreadsList(userParticipating: Boolean, pagedListConfig: PagedList.Config): ThreadLivePageResult {
monarchy.awaitTransaction { realm ->
realm.where<ThreadListPageEntity>().findAll().deleteAllFromRealm()
}
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
realm
.where<ThreadSummaryEntity>().equalTo(ThreadSummaryEntityFields.PAGE.ROOM_ID, roomId)
.sort(ThreadSummaryEntityFields.LATEST_THREAD_EVENT_ENTITY.ORIGIN_SERVER_TS, Sort.DESCENDING)
}
val dataSourceFactory = realmDataSourceFactory.map {
threadSummaryMapper.map(it)
}
val boundaries = MutableLiveData(ResultBoundaries())
val builder = LivePagedListBuilder(dataSourceFactory, pagedListConfig).also {
it.setBoundaryCallback(object : PagedList.BoundaryCallback<ThreadSummary>() {
override fun onItemAtEndLoaded(itemAtEnd: ThreadSummary) {
boundaries.postValue(boundaries.value?.copy(endLoaded = true))
}
override fun onItemAtFrontLoaded(itemAtFront: ThreadSummary) {
boundaries.postValue(boundaries.value?.copy(frontLoaded = true))
}
override fun onZeroItemsLoaded() {
boundaries.postValue(boundaries.value?.copy(zeroItemLoaded = true))
}
})
}
val livePagedList = monarchy.findAllPagedWithChanges(
realmDataSourceFactory,
builder
)
return ThreadLivePageResult(livePagedList, boundaries)
}
override suspend fun fetchThreadList(nextBatchId: String?, limit: Int, filter: ThreadFilter): FetchThreadsResult {
return fetchThreadSummariesTask.execute(
FetchThreadSummariesTask.Params(
roomId = roomId,
from = nextBatchId,
limit = limit,
filter = filter
)
)
}
override fun getAllThreadSummaries(): List<ThreadSummary> {
override suspend fun getAllThreadSummaries(): List<ThreadSummary> {
return monarchy.fetchAllMappedSync(
{ ThreadSummaryEntity.findAllThreadsForRoomId(it, roomId = roomId) },
{ threadSummaryMapper.map(it) }
@ -81,12 +130,4 @@ internal class DefaultThreadsService @AssistedInject constructor(
)
)
}
override suspend fun fetchThreadSummaries() {
fetchThreadSummariesTask.execute(
FetchThreadSummariesTask.Params(
roomId = roomId
)
)
}
}

View file

@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
import org.matrix.android.sdk.internal.session.sync.ProgressReporter
import timber.log.Timber
@ -48,12 +49,14 @@ internal class CryptoSyncHandler @Inject constructor(
?.forEachIndexed { index, event ->
progressReporter?.reportProgress(index * 100F / total)
// Decrypt event if necessary
Timber.tag(loggerTag.value).i("To device event from ${event.senderId} of type:${event.type}")
Timber.tag(loggerTag.value).d("To device event msgid:${event.toDeviceTracingId()}")
decryptToDeviceEvent(event, null)
if (event.getClearType() == EventType.MESSAGE &&
event.getClearContent()?.toModel<MessageContent>()?.msgType == "m.bad.encrypted") {
Timber.tag(loggerTag.value).e("handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
} else {
Timber.tag(loggerTag.value).d("received to-device ${event.getClearType()} from:${event.senderId} msgid:${event.toDeviceTracingId()}")
verificationService.onToDeviceEvent(event)
cryptoService.onToDeviceEvent(event)
}

View file

@ -45,6 +45,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity
import org.matrix.android.sdk.internal.database.model.UserAccountDataEntityFields
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
import org.matrix.android.sdk.internal.database.query.delete
import org.matrix.android.sdk.internal.database.query.findAllFrom
import org.matrix.android.sdk.internal.database.query.getDirectRooms
import org.matrix.android.sdk.internal.database.query.getOrCreate
@ -94,7 +95,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
// If we get some direct chat invites, we synchronize the user account data including those.
suspend fun synchronizeWithServerIfNeeded(invites: Map<String, InvitedRoomSync>) {
if (invites.isNullOrEmpty()) return
if (invites.isEmpty()) return
val directChats = directChatsHelper.getLocalDirectMessages().toMutable()
var hasUpdate = false
monarchy.doWithRealm { realm ->
@ -252,9 +253,17 @@ internal class UserAccountDataSyncHandler @Inject constructor(
}
fun handleGenericAccountData(realm: Realm, type: String, content: Content?) {
if (content.isNullOrEmpty()) {
// This is a response for a deleted account data according to
// https://github.com/ShadowJonathan/matrix-doc/blob/account-data-delete/proposals/3391-account-data-delete.md#sync
UserAccountDataEntity.delete(realm, type)
return
}
val existing = realm.where<UserAccountDataEntity>()
.equalTo(UserAccountDataEntityFields.TYPE, type)
.findFirst()
if (existing != null) {
// Update current value
existing.contentStr = ContentMapper.map(content)

View file

@ -27,6 +27,7 @@ import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntity
import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
import org.matrix.android.sdk.internal.database.model.RoomEntity
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.database.query.removeAccountData
import org.matrix.android.sdk.internal.session.room.read.FullyReadContent
import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler
import org.matrix.android.sdk.internal.session.sync.handler.room.RoomTagHandler
@ -56,6 +57,13 @@ internal class RoomSyncAccountDataHandler @Inject constructor(
}
private fun handleGeneric(roomEntity: RoomEntity, content: JsonDict?, eventType: String) {
if (content.isNullOrEmpty()) {
// This is a response for a deleted account data according to
// https://github.com/ShadowJonathan/matrix-doc/blob/account-data-delete/proposals/3391-account-data-delete.md#sync
roomEntity.removeAccountData(eventType)
return
}
val existing = roomEntity.accountData.where().equalTo(RoomAccountDataEntityFields.TYPE, eventType).findFirst()
if (existing != null) {
existing.contentStr = ContentMapper.map(content)

View file

@ -18,13 +18,14 @@ package org.matrix.android.sdk.internal.session.user.accountdata
import org.matrix.android.sdk.internal.network.NetworkConstants
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.PUT
import retrofit2.http.Path
internal interface AccountDataAPI {
/**
* Set some account_data for the client.
* Set some account_data for the user.
*
* @param userId the user id
* @param type the type
@ -36,4 +37,16 @@ internal interface AccountDataAPI {
@Path("type") type: String,
@Body params: Any
)
/**
* Remove an account_data for the user.
*
* @param userId the user id
* @param type the type
*/
@DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc3391/user/{userId}/account_data/{type}")
suspend fun deleteAccountData(
@Path("userId") userId: String,
@Path("type") type: String
)
}

View file

@ -42,4 +42,7 @@ internal abstract class AccountDataModule {
@Binds
abstract fun bindUpdateBreadcrumbsTask(task: DefaultUpdateBreadcrumbsTask): UpdateBreadcrumbsTask
@Binds
abstract fun bindDeleteUserAccountDataTask(task: DefaultDeleteUserAccountDataTask): DeleteUserAccountDataTask
}

View file

@ -34,10 +34,11 @@ import javax.inject.Inject
internal class DefaultSessionAccountDataService @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val deleteUserAccountDataTask: DeleteUserAccountDataTask,
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
private val userAccountDataDataSource: UserAccountDataDataSource,
private val roomAccountDataDataSource: RoomAccountDataDataSource,
private val taskExecutor: TaskExecutor
private val taskExecutor: TaskExecutor,
) : SessionAccountDataService {
override fun getUserAccountDataEvent(type: String): UserAccountDataEvent? {
@ -78,4 +79,12 @@ internal class DefaultSessionAccountDataService @Inject constructor(
userAccountDataSyncHandler.handleGenericAccountData(realm, type, content)
}
}
override fun getUserAccountDataEventsStartWith(type: String): List<UserAccountDataEvent> {
return userAccountDataDataSource.getAccountDataEventsStartWith(type)
}
override suspend fun deleteUserAccountData(type: String) {
deleteUserAccountDataTask.execute(DeleteUserAccountDataTask.Params(type))
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright 2022 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.user.accountdata
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface DeleteUserAccountDataTask : Task<DeleteUserAccountDataTask.Params, Unit> {
data class Params(
val type: String,
)
}
internal class DefaultDeleteUserAccountDataTask @Inject constructor(
private val accountDataApi: AccountDataAPI,
@UserId private val userId: String,
private val globalErrorReceiver: GlobalErrorReceiver,
) : DeleteUserAccountDataTask {
override suspend fun execute(params: DeleteUserAccountDataTask.Params) {
return executeRequest(globalErrorReceiver) {
accountDataApi.deleteAccountData(userId, params.type)
}
}
}

View file

@ -60,6 +60,16 @@ internal class UserAccountDataDataSource @Inject constructor(
)
}
fun getAccountDataEventsStartWith(type: String): List<UserAccountDataEvent> {
return realmSessionProvider.withRealm { realm ->
realm
.where(UserAccountDataEntity::class.java)
.beginsWith(UserAccountDataEntityFields.TYPE, type)
.findAll()
.map(accountDataMapper::map)
}
}
private fun accountDataEventsQuery(realm: Realm, types: Set<String>): RealmQuery<UserAccountDataEntity> {
val query = realm.where(UserAccountDataEntity::class.java)
if (types.isNotEmpty()) {

View file

@ -0,0 +1,255 @@
/*
* Copyright 2022 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.crypto
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import org.amshove.kluent.internal.assertEquals
import org.junit.Assert
import org.junit.Test
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDevicesParams
import org.matrix.android.sdk.internal.crypto.model.rest.KeyChangesResponse
import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimBody
import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryBody
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody
import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
import org.matrix.android.sdk.internal.crypto.model.rest.UpdateDeviceInfoBody
import org.matrix.android.sdk.internal.crypto.model.rest.UploadSigningKeysBody
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendToDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
class DefaultSendToDeviceTaskTest {
private val users = listOf(
"@alice:example.com" to listOf("D0", "D1"),
"bob@example.com" to listOf("D2", "D3")
)
private val fakeEncryptedContent = mapOf(
"algorithm" to "m.olm.v1.curve25519-aes-sha2",
"sender_key" to "gMObR+/4dqL5T4DisRRRYBJpn+OjzFnkyCFOktP6Eyw",
"ciphertext" to mapOf(
"tdwXf7006FDgzmufMCVI4rDdVPO51ecRTTT6HkRxUwE" to mapOf(
"type" to 0,
"body" to "AwogCA1ULEc0abGIFxMDIC9iv7ul3jqJSnapTHQ+8JJx"
)
)
)
private val fakeStartVerificationContent = mapOf(
"method" to "m.sas.v1",
"from_device" to "MNQHVEISFQ",
"key_agreement_protocols" to listOf(
"curve25519-hkdf-sha256",
"curve25519"
),
"hashes" to listOf("sha256"),
"message_authentication_codes" to listOf(
"org.matrix.msc3783.hkdf-hmac-sha256",
"hkdf-hmac-sha256",
"hmac-sha256"
),
"short_authentication_string" to listOf(
"decimal",
"emoji"
),
"transaction_id" to "4wNOpkHGwGZPXjkZToooCDWfb8hsf7vW"
)
@Test
fun `tracing id should be added to to_device contents`() {
val fakeCryptoAPi = FakeCryptoApi()
val sendToDeviceTask = DefaultSendToDeviceTask(
cryptoApi = fakeCryptoAPi,
globalErrorReceiver = mockk(relaxed = true)
)
val contentMap = MXUsersDevicesMap<Any>()
users.forEach { pairOfUserDevices ->
val userId = pairOfUserDevices.first
pairOfUserDevices.second.forEach {
contentMap.setObject(userId, it, fakeEncryptedContent)
}
}
val params = SendToDeviceTask.Params(
eventType = EventType.ENCRYPTED,
contentMap = contentMap
)
runBlocking {
sendToDeviceTask.execute(params)
}
val generatedIds = mutableListOf<String>()
users.forEach { pairOfUserDevices ->
val userId = pairOfUserDevices.first
pairOfUserDevices.second.forEach {
val modifiedContent = fakeCryptoAPi.body!!.messages!![userId]!![it] as Map<*, *>
Assert.assertNotNull("Tracing id should have been added", modifiedContent["org.matrix.msgid"])
generatedIds.add(modifiedContent["org.matrix.msgid"] as String)
assertEquals(
"The rest of the content should be the same",
fakeEncryptedContent.keys,
modifiedContent.toMutableMap().apply { remove("org.matrix.msgid") }.keys
)
}
}
assertEquals("Id should be unique per content", generatedIds.size, generatedIds.toSet().size)
println("modified content ${fakeCryptoAPi.body}")
}
@Test
fun `tracing id should not be added to verification start to_device contents`() {
val fakeCryptoAPi = FakeCryptoApi()
val sendToDeviceTask = DefaultSendToDeviceTask(
cryptoApi = fakeCryptoAPi,
globalErrorReceiver = mockk(relaxed = true)
)
val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject("@alice:example.com", "MNQHVEISFQ", fakeStartVerificationContent)
val params = SendToDeviceTask.Params(
eventType = EventType.KEY_VERIFICATION_START,
contentMap = contentMap
)
runBlocking {
sendToDeviceTask.execute(params)
}
val modifiedContent = fakeCryptoAPi.body!!.messages!!["@alice:example.com"]!!["MNQHVEISFQ"] as Map<*, *>
Assert.assertNull("Tracing id should not have been added", modifiedContent["org.matrix.msgid"])
// try to force
runBlocking {
sendToDeviceTask.execute(
SendToDeviceTask.Params(
eventType = EventType.KEY_VERIFICATION_START,
contentMap = contentMap,
addTracingIds = true
)
)
}
val modifiedContentForced = fakeCryptoAPi.body!!.messages!!["@alice:example.com"]!!["MNQHVEISFQ"] as Map<*, *>
Assert.assertNotNull("Tracing id should have been added", modifiedContentForced["org.matrix.msgid"])
}
@Test
fun `tracing id should not be added to all verification to_device contents`() {
val fakeCryptoAPi = FakeCryptoApi()
val sendToDeviceTask = DefaultSendToDeviceTask(
cryptoApi = fakeCryptoAPi,
globalErrorReceiver = mockk(relaxed = true)
)
val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject("@alice:example.com", "MNQHVEISFQ", emptyMap<String, Any>())
val verificationEvents = listOf(
MessageType.MSGTYPE_VERIFICATION_REQUEST,
EventType.KEY_VERIFICATION_START,
EventType.KEY_VERIFICATION_ACCEPT,
EventType.KEY_VERIFICATION_KEY,
EventType.KEY_VERIFICATION_MAC,
EventType.KEY_VERIFICATION_CANCEL,
EventType.KEY_VERIFICATION_DONE,
EventType.KEY_VERIFICATION_READY
)
for (type in verificationEvents) {
val params = SendToDeviceTask.Params(
eventType = type,
contentMap = contentMap
)
runBlocking {
sendToDeviceTask.execute(params)
}
val modifiedContent = fakeCryptoAPi.body!!.messages!!["@alice:example.com"]!!["MNQHVEISFQ"] as Map<*, *>
Assert.assertNull("Tracing id should not have been added", modifiedContent["org.matrix.msgid"])
}
}
internal class FakeCryptoApi : CryptoApi {
override suspend fun getDevices(): DevicesListResponse {
throw java.lang.AssertionError("Should not be called")
}
override suspend fun getDeviceInfo(deviceId: String): DeviceInfo {
throw java.lang.AssertionError("Should not be called")
}
override suspend fun uploadKeys(body: KeysUploadBody): KeysUploadResponse {
throw java.lang.AssertionError("Should not be called")
}
override suspend fun downloadKeysForUsers(params: KeysQueryBody): KeysQueryResponse {
throw java.lang.AssertionError("Should not be called")
}
override suspend fun uploadSigningKeys(params: UploadSigningKeysBody): KeysQueryResponse {
throw java.lang.AssertionError("Should not be called")
}
override suspend fun uploadSignatures(params: Map<String, Any>?): SignatureUploadResponse {
throw java.lang.AssertionError("Should not be called")
}
override suspend fun claimOneTimeKeysForUsersDevices(body: KeysClaimBody): KeysClaimResponse {
throw java.lang.AssertionError("Should not be called")
}
var body: SendToDeviceBody? = null
override suspend fun sendToDevice(eventType: String, transactionId: String, body: SendToDeviceBody) {
this.body = body
}
override suspend fun deleteDevice(deviceId: String, params: DeleteDeviceParams) {
throw java.lang.AssertionError("Should not be called")
}
override suspend fun deleteDevices(params: DeleteDevicesParams) {
throw java.lang.AssertionError("Should not be called")
}
override suspend fun updateDeviceInfo(deviceId: String, params: UpdateDeviceInfoBody) {
throw java.lang.AssertionError("Should not be called")
}
override suspend fun getKeyChanges(oldToken: String, newToken: String): KeyChangesResponse {
throw java.lang.AssertionError("Should not be called")
}
}
}

View file

@ -47,7 +47,7 @@ import org.matrix.android.sdk.test.fakes.FakeRealm
import org.matrix.android.sdk.test.fakes.givenEqualTo
import org.matrix.android.sdk.test.fakes.givenFindFirst
class PollAggregationProcessorTest {
class DefaultPollAggregationProcessorTest {
private val pollAggregationProcessor: PollAggregationProcessor = DefaultPollAggregationProcessor()
private val realm = FakeRealm()

View file

@ -87,7 +87,7 @@ object PollEventsTestData {
)
internal val A_POLL_START_EVENT = Event(
type = EventType.POLL_START.stable,
type = EventType.POLL_START.unstable,
eventId = AN_EVENT_ID,
originServerTs = 1652435922563,
senderId = A_USER_ID_1,
@ -96,7 +96,7 @@ object PollEventsTestData {
)
internal val A_POLL_RESPONSE_EVENT = Event(
type = EventType.POLL_RESPONSE.stable,
type = EventType.POLL_RESPONSE.unstable,
eventId = AN_EVENT_ID,
originServerTs = 1652435922563,
senderId = A_USER_ID_1,
@ -105,7 +105,7 @@ object PollEventsTestData {
)
internal val A_POLL_END_EVENT = Event(
type = EventType.POLL_END.stable,
type = EventType.POLL_END.unstable,
eventId = AN_EVENT_ID,
originServerTs = 1652435922563,
senderId = A_USER_ID_1,

View file

@ -69,7 +69,7 @@ class DefaultGetActiveBeaconInfoForUserTaskTest {
result shouldBeEqualTo currentStateEvent
fakeStateEventDataSource.verifyGetStateEvent(
roomId = params.roomId,
eventType = EventType.STATE_ROOM_BEACON_INFO.stable,
eventType = EventType.STATE_ROOM_BEACON_INFO.unstable,
stateKey = QueryStringValue.Equals(A_USER_ID)
)
}

View file

@ -75,7 +75,7 @@ internal class DefaultStartLiveLocationShareTaskTest {
val expectedParams = SendStateTask.Params(
roomId = params.roomId,
stateKey = A_USER_ID,
eventType = EventType.STATE_ROOM_BEACON_INFO.stable,
eventType = EventType.STATE_ROOM_BEACON_INFO.unstable,
body = expectedBeaconContent
)
fakeSendStateTask.verifyExecuteRetry(

View file

@ -79,7 +79,7 @@ class DefaultStopLiveLocationShareTaskTest {
val expectedSendParams = SendStateTask.Params(
roomId = params.roomId,
stateKey = A_USER_ID,
eventType = EventType.STATE_ROOM_BEACON_INFO.stable,
eventType = EventType.STATE_ROOM_BEACON_INFO.unstable,
body = expectedBeaconContent
)
fakeSendStateTask.verifyExecuteRetry(

View file

@ -79,7 +79,7 @@ class LiveLocationShareRedactionEventProcessorTest {
@Test
fun `given a redacted live location share event when processing it then related summaries are deleted from database`() = runTest {
val event = Event(eventId = AN_EVENT_ID, redacts = A_REDACTED_EVENT_ID)
val redactedEventEntity = EventEntity(eventId = A_REDACTED_EVENT_ID, type = EventType.STATE_ROOM_BEACON_INFO.stable)
val redactedEventEntity = EventEntity(eventId = A_REDACTED_EVENT_ID, type = EventType.STATE_ROOM_BEACON_INFO.unstable)
fakeRealm.givenWhere<EventEntity>()
.givenEqualTo(EventEntityFields.EVENT_ID, A_REDACTED_EVENT_ID)
.givenFindFirst(redactedEventEntity)

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