Detailed changes
  
  
    
    @@ -1,7 +1,11 @@
 steps:
-    build:
-        image: codeberg.org/freeyourgadget/android-fdroid-tools:latest
-        commands:
-            - ./gradlew clean
-            - ./gradlew assembleConversationsFreeDebug
-            - ./gradlew assembleQuicksyFreeDebug
+  build:
+    when:
+      - branch: master
+        event: push
+      - event: tag
+    image: codeberg.org/freeyourgadget/android-fdroid-tools:latest
+    commands:
+      - ./gradlew clean
+      - ./gradlew assembleConversationsFreeDebug
+      - ./gradlew assembleQuicksyFreeDebug
  
  
  
    
    @@ -1,5 +1,47 @@
 # Changelog
 
+### Version 2.17.8
+
+* Fix some minor UI bugs
+* Fix connection issues with .onion domains on non-default ports
+
+### Version 2.17.7
+
+* Easier access to custom notification sounds via Contact details -> Overflow menu -> Custom notifications)
+* Fix direct share targets on new Android versions
+* Ability to restrict avatar visibility to contacts
+
+### Version 2.17.6
+
+* Add ability to show message bubbles left-aligned
+
+### Version 2.17.5
+
+* Move message bubbles closer together instead of merging them
+* Add ability to hide avatars in chat view when not strictly necessary (Settings -> Interface -> Chat Bubbles -> Show avatars)
+
+### Version 2.17.4
+
+* improve handling of some emoji reactions
+
+### Version 2.17.3
+
+* Always show call button
+* Various bug fixes
+
+### Version 2.17.2
+
+* Fix calls on Android 15
+* Fix rare crash / regression introduced with 2.17.0
+
+### Version 2.17.1
+
+* Fix UI glitch when showing multiple reactions
+
+### Version 2.17.0
+
+* Support Message Reactions
+
 ### Version 2.16.7
 
 * Add timeout to call initiation
  
  
  
    
    @@ -7,6 +7,7 @@ buildscript {
     }
     dependencies {
         classpath 'com.android.tools.build:gradle:8.5.2'
+        classpath "com.diffplug.spotless:spotless-plugin-gradle:6.25.0"
     }
 }
 
@@ -16,6 +17,7 @@ plugins {
 }
 
 apply plugin: 'com.android.application'
+apply plugin: "com.diffplug.spotless"
 
 repositories {
     google()
@@ -39,6 +41,15 @@ configurations {
     quicksyImplementation
 }
 
+spotless {
+    ratchetFrom '2.17.4'
+    java {
+        target '**/*.java'
+        googleJavaFormat().aosp().reflowLongStrings()
+    }
+}
+
+
 dependencies {
     androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
     androidTestImplementation 'junit:junit:4.13.2'
@@ -48,14 +59,14 @@ dependencies {
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
 
     implementation "androidx.core:core:1.10.1"
-    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.2'
+    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.3'
 
     implementation project(':libs:annotation')
     annotationProcessor project(':libs:annotation-processor')
 
-    implementation 'androidx.viewpager:viewpager:1.0.0'
+    implementation 'androidx.viewpager:viewpager:1.1.0'
 
-    playstoreImplementation('com.google.firebase:firebase-messaging:24.0.1') {
+    playstoreImplementation('com.google.firebase:firebase-messaging:24.1.0') {
         exclude group: 'com.google.firebase', module: 'firebase-core'
         exclude group: 'com.google.firebase', module: 'firebase-analytics'
         exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
@@ -66,12 +77,14 @@ dependencies {
     quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.1.0'
     implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1'
     implementation("com.github.CanHub:Android-Image-Cropper:2.0.0")
+    implementation "androidx.sharetarget:sharetarget:1.2.0"
+
     implementation 'androidx.appcompat:appcompat:1.7.0'
     implementation 'androidx.exifinterface:exifinterface:1.3.7'
     implementation 'androidx.cardview:cardview:1.0.0'
     implementation "androidx.preference:preference:1.2.1"
     implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
-    implementation 'com.google.android.material:material:1.13.0-alpha06'
+    implementation 'com.google.android.material:material:1.13.0-alpha09'
     implementation 'androidx.work:work-runtime:2.9.1'
 
     implementation "androidx.emoji2:emoji2:1.5.0"
@@ -87,10 +100,7 @@ dependencies {
     implementation "com.wefika:flowlayout:0.4.1"
 
     //noinspection GradleDependency
-    implementation('com.github.natario1:Transcoder:v0.9.1') {
-        exclude group: 'com.otaliastudios.opengl', module: 'egloo'
-    }
-    implementation 'com.github.natario1:Egloo:v0.4.0'
+    implementation 'io.deepmedia.community:transcoder-android:0.11.2'
 
     implementation 'org.jxmpp:jxmpp-jid:1.0.3'
     implementation 'org.jxmpp:jxmpp-stringprep-libidn:1.0.3'
  
  
  
    
    @@ -0,0 +1 @@
+* Unterstützung von Reaktionen auf Nachrichten
  
  
  
    
    @@ -0,0 +1 @@
+* Darstellungsfehler beim Anzeigen mehrerer Reaktionen behoben
  
  
  
    
    @@ -0,0 +1,2 @@
+* Anrufe auf Android 15 behoben
+* Seltener Absturz behoben / Regression die mit 2.17.0 aufgetreten ist
  
  
  
    
    @@ -0,0 +1,2 @@
+* Anruf-Taste immer anzeigen
+* Verschiedene Fehlerbehebungen
  
  
  
    
    @@ -0,0 +1 @@
+* Handhabung einiger Emoji-Reaktionen verbessert
  
  
  
    
    @@ -0,0 +1,2 @@
+* Nachrichten näher zusammengerückt, anstatt sie zu verschmelzen
+* Möglichkeit hinzugefügt, Profilbilder in Chat-Ansichten auszublenden, wenn sie nicht zwingend erforderlich sind (Einstellungen > Benutzeroberfläche > Sprechblasen > Profilbilder anzeigen)
  
  
  
    
    @@ -0,0 +1 @@
+* Möglichkeit hinzugefügt, Nachrichten linksbündig anzuzeigen
  
  
  
    
    @@ -0,0 +1,3 @@
+* Leichterer Zugang zu benutzerdefinierten Benachrichtigungstönen über Kontaktdetails > weiteres Menü > Benutzerdefinierte Benachrichtigungen
+* Direkte Freigabeziele auf neuen Android-Versionen korrigiert
+* Möglichkeit, die Sichtbarkeit von Profilbildern auf Kontakte zu beschränken
  
  
  
    
    @@ -0,0 +1,2 @@
+* Kleine UI-Bugs behoben
+* Verbindungsprobleme mit .onion-Domains auf nicht standardmäßigen Ports behoben
  
  
  
    
    @@ -0,0 +1 @@
+* Support Message Reactions
  
  
  
    
    @@ -0,0 +1 @@
+* Fix UI glitch when showing multiple reactions
  
  
  
    
    @@ -0,0 +1,2 @@
+* Fix calls on Android 15
+* Fix rare crash / regression introduced with 2.17.0
  
  
  
    
    @@ -0,0 +1,2 @@
+* Always show call button
+* Various bug fixes
  
  
  
    
    @@ -0,0 +1 @@
+* improve handling of some emoji reactions
  
  
  
    
    @@ -0,0 +1,2 @@
+* Move message bubbles closer together instead of merging them
+* Add ability to hide avatars in chat views when not stricly necessary (Settings -> Interface -> Chat Bubbles -> Show avatars)
  
  
  
    
    @@ -0,0 +1 @@
+* Add ability to show message bubbles left-aligned
  
  
  
    
    @@ -0,0 +1,3 @@
+* Easier access to custom notification sounds via Contact details -> Overflow menu -> Custom notifications)
+* Fix direct share targets on new Android versions
+* Ability to restrict avatar visibility to contacts
  
  
  
    
    @@ -0,0 +1,2 @@
+* Fix some minor UI bugs
+* Fix connection issues with .onion domains on non-default ports
  
  
  
    
    @@ -0,0 +1,3 @@
+* Ofrecer valores más grandes para la recepción automática de archivo 
+* Proporcionar más información en 'Información del servidor'
+* Varias correcciones de errores
  
  
  
    
    @@ -0,0 +1 @@
+* Agregar tiempo de espera al inicio de la llamada
  
  
  
    
    @@ -0,0 +1 @@
+* Reacciones a los mensajes de soporte
  
  
  
    
    @@ -0,0 +1 @@
+* Se solucionó un error de la interfaz de usuario al mostrar múltiples reacciones
  
  
  
    
    @@ -0,0 +1,2 @@
+* Arreglar llamadas en Android 15
+* Corrección de un error raro/ regresión introducida con 2.17.0
  
  
  
    
    @@ -0,0 +1,2 @@
+* Mostrar siempre el botón de llamada
+* Correcciones de errores varios
  
  
  
    
    @@ -0,0 +1 @@
+* mejorar el manejo de algunas reacciones con emojis
  
  
  
    
    @@ -0,0 +1,2 @@
+* Acercar burbujas de chat en vez de combinarlas en una sola
+* Posibilidad de ocultar imágenes de perfil cuando no es estrictamente necesario (Ajustes -> Interfaz -> Burbujas de chat -> Ver imágenes de perfil)
  
  
  
    
    @@ -0,0 +1 @@
+* Posibilidad de alinear burbujas de mensaje sobre el margen izquierdo
  
  
  
    
    @@ -0,0 +1,3 @@
+* Acceso más fácil a sonidos de notificación personalizados desde Detalles del contacto -> Menú emergente -> Notificaciones personalizadas
+* Corrección de compartición directa en nuevas versiones de Android
+* Posibilidad de limitar visibilidad de imagen de perfil sólo a contactos
  
  
  
    
    @@ -0,0 +1,2 @@
+* Se corrigieron algunos errores menores de la interfaz de usuario.
+* Solucionar problemas de conexión con dominios .onion (.cebolla) en puertos no predeterminados
  
  
  
    
    @@ -0,0 +1 @@
+* Eemaldasime Google Play versioonist kanalite tuvastuse
  
  
  
    
    @@ -0,0 +1 @@
+* Lülitasime välja võimaluse avada failihaldurist varukoopia faile (.ceb)
  
  
  
    
    @@ -0,0 +1 @@
+* Võtsime varukoopiate jaoks kasutusele uue failivormingu
  
  
  
    
    @@ -0,0 +1,2 @@
+* vestlusekohaste teavituste tugi
+* Android 10s kasutame opus-koodekit häälsõnumite jaoks
  
  
  
    
    @@ -0,0 +1,3 @@
+* libwebrtc teek sõltub nüüd versioonist M117 ja uuendasime libvpx teeki
+* Häälsõnumid kasutavad jälle AAC koodekit
+* Rakendustekohaste keeleeelistuste tugi
  
  
  
    
    @@ -0,0 +1,3 @@
+* Privaatsete nimeserverite tugi (DNS üle TLSi)
+* Võimalus valida käivitusikooni kujundust
+* Parandasime harvaesineva failide jagamise vea, mis esines Android 11+ puhul
  
  
  
    
    @@ -0,0 +1,4 @@
+* Lihtsam ligipääs valikule „Näita QR-koodi“
+* PEP-järjehoidjate tugi
+* Lisasime SDP pakkumine/vastus mudeli toe (seda kasutavad SIPi lüüsid)
+* Üldine API arvestab nüüd Android 14'ga
  
  
  
    
    @@ -0,0 +1,3 @@
+* P2P failide teisaldamise tugi kasutades WebRTC andmekanaleid
+* Parandasime Bind 2.0 ühilduvusvead, kui kasutusel on ejabberd
+* Lisasime rakendusele Let’s Encrypti juursertifikaadid, kui kasutusel on Android <= 7
  
  
  
    
    @@ -0,0 +1,2 @@
+* väikesed veaparandused
+* Quicksy liitumisloogika väikesed kohendused
  
  
  
    
    @@ -0,0 +1,2 @@
+* Play Store'i versioonis on nüüd lihtsam ligipääs privaatsuspoliitikale (Quicksy ja Conversations)
+* Conversationsi Play Store'i versioonist oleme eemaldanud lõimingu aadressiraamatuga
  
  
  
    
    @@ -0,0 +1 @@
+* Parandsime väikese regressiooni, mis tekkis versioonis 2.13.1
  
  
  
    
    @@ -0,0 +1 @@
+* Parandasime hääl- ja videokõnede lõimimise operatsioonisüsteemiga
  
  
  
    
    @@ -0,0 +1,3 @@
+* Parandasime hääl- ja videokõned Android 8's
+* Parandasime vea protsesside trügimisega lõimitud kõnede puhul
+* Parandasime vea kui videopakkimine jäi muutmatuks
  
  
  
    
    @@ -0,0 +1,2 @@
+* Taastasime Android 6+7 puhul ligipääsu kanalite tuvastamisele
+* Parandasime logimist, kui lõimitud kõned ei toimi
  
  
  
    
    @@ -0,0 +1,3 @@
+* Kasutame Material 3 kujundust
+* Korraldasime seadistused ümber
+* Sünkroniseerime lugemise olekuid eri seadmete vahel
  
  
  
    
    @@ -0,0 +1,2 @@
+* Näitame sõnumite olekuid ikoonidena
+* Lisasime sõnumimullide välimusele suurema kirjatüübi valimise võimaluse
  
  
  
    
    @@ -0,0 +1,2 @@
+* Parandasime Quicksy registreerimise Androidi versioonides 6/7
+* Saabuva kõne märguanne teavituskanalis
  
  
  
    
    @@ -0,0 +1,2 @@
+* Parandasime kõnede lõimimise vead mõnedes Android 14-põhistes nutiseadmetes
+* Lisasime seadistuse „Kutsed võõrastelt“
  
  
  
    
    @@ -0,0 +1,3 @@
+* Nüüd saab teha regulaarseid varukoopiaid
+* Välistame kõnede lõimimise kõikides Realme nutiseadmetes kuni Androidi versioonini 11
+* Väikesed kasutajaliidese parandused (kõnemullid)
  
  
  
    
    @@ -0,0 +1,2 @@
+* Parandasime vea, kus kõne summutamine lõppeb väljundseadmete vahetamisel
+* Välistame kõnede lõimimise kõikides Umidigi nutiseadmetes
  
  
  
    
    @@ -0,0 +1 @@
+* vältimaks varunduse peatumist peale kümmet minutit, käivitame ta alati esiplaanil
  
  
  
    
    @@ -0,0 +1,2 @@
+* välistasime vanemad Oppo telefonid kõnelõimingutest
+* mitmed veaparandused
  
  
  
    
    @@ -0,0 +1 @@
+* väikesed veaparandused
  
  
  
    
    @@ -0,0 +1,3 @@
+* muutsime vaikimisi allalaaditavate failide mahu suuremaks
+* näitame rohkem teavet valikus „Serveri info“
+* parandasime mitmeid vigu
  
  
  
    
    @@ -0,0 +1 @@
+* Lisasime kõne alustamisele aegumise
  
  
  
    
    @@ -0,0 +1 @@
+* Sõnumitest reageerimise tugi
  
  
  
    
    @@ -0,0 +1 @@
+* Parandasime kasutajaliidese vea, kui kuvamisel oli mitu reageerimist
  
  
  
    
    @@ -0,0 +1,2 @@
+* Parandasime kõnete vead Android 15's
+* Parandasime harvaesineva kokkujooksmise/regressiooni, mis tekkis alates versioonist 2.17.0
  
  
  
    
    @@ -0,0 +1,2 @@
+* näitame alati kõne alustamise ikooni
+* erinevad veaparandused
  
  
  
    
    @@ -0,0 +1 @@
+* parandasime mõnede emoji-põhiste reaktsioonide töötlemist
  
  
  
    
    @@ -0,0 +1,2 @@
+* Tõstsime jutumullid üksteisele lähemale vältides nende meldimist
+* Lisasime võimaluse peita vestlustes tunnuspilte, kui seda tõesti vaja pole (Seadistused -> Kasutajaliides -> Jutumullid -> Näita tunnuspilte)
  
  
  
    
    @@ -0,0 +1 @@
+* Lisasime võimaluse kuvada kõiki jutumulle joondatuna vasakule
  
  
  
    
    @@ -0,0 +1,3 @@
+* Lihtsam lihipääs sinu seadistatud teavitushelinatele Kontakti üksikasjad -> Jätkumenüü -> Kohandatud teavitused)
+* Parandasime otsejagamise linkide kasutamise uutes Androidi versioonides
+* Võimalus piirata tunnuspildi näitamist kontaktidele
  
  
  
    
    @@ -0,0 +1,2 @@
+* Parandasime mõned väiksemad kasutajaliidese vead
+* Parandasime .onion domeenide ühenduse vead mittestandardsete portide kasutamisel
  
  
  
    
    @@ -0,0 +1 @@
+* Compatibilidade con Reaccións ás Mensaxes
  
  
  
    
    @@ -0,0 +1 @@
+* Arranxo do problema na interface cando hai varias reaccións
  
  
  
    
    @@ -0,0 +1,2 @@
+* Arranxo das chamadas en Android 15
+* Arranxo do problema raro / regresión introducido en 2.17.0
  
  
  
    
    @@ -0,0 +1,2 @@
+* Mostra sempre o botón de chamada
+* Arranxo de varios problemas
  
  
  
    
    @@ -0,0 +1 @@
+* mellora na xestión da reacción con emojis
  
  
  
    
    @@ -0,0 +1,2 @@
+* Xuntar máis as burbullas das mensaxes no lugar de xuntalas
+* Poder agochar os avatares na vista de conversas cando non é estritamente necesario (Axustes -> Interface -> Burbullas -> Mostrar avatares)
  
  
  
    
    @@ -0,0 +1 @@
+* Podes enfeitar no lado esquerdo todas as mensaxes
  
  
  
    
    @@ -0,0 +1,3 @@
+* Acceso máis doado á personalización do son da notificación en Detalles do contacto -> Menú emerxente -> Notificacións personalizadas)
+* Arranxo das opcións directas para compartir nas novas versións de Android
+* Posibilidade de limitar a visibilidade do avatar só para contactos
  
  
  
    
    @@ -0,0 +1,2 @@
+* arranxo de problemas menores na interface
+* arranxo de problemas coa conexión a dominios .onion en portos non predeterminados
  
  
  
    
    @@ -0,0 +1 @@
+* Aggiunta scadenza per iniziazione della chiamata
  
  
  
    
    @@ -0,0 +1 @@
+* Supporto per le reazioni ai messaggi
  
  
  
    
    @@ -0,0 +1 @@
+* Corretto errore di interfaccia che mostrava reazioni multiple
  
  
  
    
    @@ -0,0 +1,2 @@
+* Correzione delle chiamate su Android 15
+* Corretto un raro crash / regressione introdotto nella 2.17.0
  
  
  
    
    @@ -0,0 +1 @@
+* Naprawienie litery ‚q’ nieprawidłowo rozpoznawanej jako cyrylica
  
  
  
    
    @@ -0,0 +1 @@
+* Usunięcie funkcjonalności odkrywania kanałów z wersji dla Google Play
  
  
  
    
    @@ -0,0 +1 @@
+* Wyłączenie otwierania plików kopii zapasowej (.ceb) z przeglądarki plików
  
  
  
    
    @@ -0,0 +1 @@
+* Wprowadzenie nowego formatu pliku kopii zapasowej
  
  
  
    
    @@ -0,0 +1,2 @@
+* Obsługa ustawień powiadomień dla danej rozmowy
+* Używanie Opus dla wiadomości głosowych w Androidzie 10
  
  
  
    
    @@ -0,0 +1,3 @@
+* Podwyższenie wymaganej wersji libwebrtc do M117 i podwyższenie wymaganej wersji libvpx
+* Powrót do AAC dla wiadomości głosowych
+* Obsługa ustawień języka dla aplikacji
  
  
  
    
    @@ -0,0 +1,3 @@
+* Obsługa prywatnego DNS (DNS over TLS)
+* Obsługa motywów ikony uruchamiania aplikacji
+* Naprawa rzadkiego problemu z uprawnieniami podczas udostępniania plików na Androidzie 11 i nowszych
  
  
  
    
    @@ -0,0 +1,4 @@
+* Łatwiejszy dostęp do „Pokaż kod QR”
+* Obsługa natywnych zakładek PEP
+* Obsługa modelu SDP Offer/Answer (używanego przez bramki SIP)
+* Podwyższenie docelowego API do Androida 14
  
  
  
    
    @@ -0,0 +1,3 @@
+* Obsługa przesyłanie plików P2P przez kanały danych WebRTC
+* Naprawiono problemy interoperacyjności z Bind 2.0 na ejabberd
+* Wbudowanie certyfikatów głównych Let’s Encrypt dla Androida 7 i starszych
  
  
  
    
    @@ -0,0 +1,2 @@
+* Drobne poprawki błędów
+* Małe zmiany we wprowadzeniu do Quicksy
  
  
  
    
    @@ -0,0 +1,2 @@
+* Łatwiejszy dostęp do polityki prywatności w wersji dla Sklepu Play (Quicksy i Conversations)
+* Usunięcie integracji z książką adresową w wersji Conversations dla Sklepu Play
  
  
  
    
    @@ -0,0 +1 @@
+* Naprawiono małą regresję wprowadzoną w 2.13.1
  
  
  
    
    @@ -0,0 +1 @@
+* Ulepszono integrację rozmów głosowych i wideo z systemem operacyjnym
  
  
  
    
    @@ -0,0 +1,3 @@
+* Naprawiono rozmowy głosowe i wideo w Androidzie 8
+* Naprawiono problemy typu race condition w nowej integracji rozmów
+* Naprawiono nieznikającą kompresję wideo
  
  
  
    
    @@ -0,0 +1,2 @@
+* Przywrócenie dostępu do odkrywania kanałów w Androidzie 6 i 7
+* Ulepszenie logowania nieudanej integracji rozmów
  
  
  
    
    @@ -0,0 +1 @@
+* Obsługa reakcji na wiadomości
  
  
  
    
    @@ -0,0 +1 @@
+* Naprawienie usterki interfejsu użytkownika podczas pokazywania wielu reakcji
  
  
  
    
    @@ -0,0 +1,2 @@
+* Naprawienie rozmów w Androidzie 15
+* Naprawienie rzadkiej awarii (regresji) wprowadzonej w 2.17.0
  
  
  
    
    @@ -0,0 +1,2 @@
+* Pokazywanie przycisku rozmowy zawsze
+* Różne poprawki błędów
  
  
  
    
    @@ -0,0 +1 @@
+* Poprawienie obsługi niektórych emoji reakcji
  
  
  
    
    @@ -0,0 +1,2 @@
+* Przeniesienie dymków wiadomości bliżej siebie zamiast ich łączenia
+* Dodanie możliwości ukrywania awatarów w widokach rozmów gdy nie są konieczne (Ustawienia → Interfejs → Dymki rozmów → Pokaż awatary)
  
  
  
    
    @@ -0,0 +1 @@
+* Dodanie możliwości pokazywania dymków wiadomości wyrównanych do lewej
  
  
  
    
    @@ -0,0 +1,3 @@
+* Łatwiejszy dostęp do własnych dźwięków powiadomień poprzez Szczegóły kontaktu → Więcej opcji → Własne powiadomienia
+* Naprawienie bezpośrednich celów udostępniania w nowszych wersjach Androida
+* Możliwość ograniczenia widoczności awatara do kontaktów
  
  
  
    
    @@ -0,0 +1,2 @@
+* Poprawienie drobnych błędów interfejsu użytkownika
+* Poprawienie problemów z połączeniem z domenami .onion na niedomyślnych portach
  
  
  
    
    @@ -0,0 +1,2 @@
+* Reparat notificările care nu apăreau în anumite condiții
+* Reparat probleme de compatibilitate și crăpări legate de apeluri A/V
  
  
  
    
    @@ -0,0 +1,3 @@
+* adăugat 'Întoarcere la discuție' pe ecranul de apel audio
+* îmbunătățit scurtăturile de tastatură
+* corecturi de erori
  
  
  
    
    @@ -0,0 +1,3 @@
+* Gestionare fișiere GPX
+* Îmbunătățit performanța pentru restaurarea copiilor de siguranță
+* Corecturi de erori
  
  
  
    
    @@ -0,0 +1,4 @@
+* Căutare în conversații individuale
+* Notifică utilizatorul dacă livrarea mesajului eșuează
+* Ține minte numele afișate (poreclele) de la utilizatorii Quicksy între reporniri
+* Adăugat buton pentru a porni Orbot (Tor) din notificare dacă este necesar
  
  
  
    
    @@ -0,0 +1,2 @@
+* corectat căutarea pe Android <= 5
+* optimizat consumul de memorie
  
  
  
    
    @@ -0,0 +1,3 @@
+* Oferă generarea de invitație facilă pe serverele care suportă
+* Afișează GIFuri trimise din Movim
+* stochează avatarii în cache
  
  
  
    
    @@ -0,0 +1,3 @@
+* Corectat probleme de conectivitate când conturi diferite foloseau mecanisme SCRAM diferite
+* Adăugat suport pentru SCRAM-SHA-512
+* Permis transfer de fișiere P2P (Jingle) cu contactul sine
  
  
  
    
    @@ -0,0 +1 @@
+* mici îmbunătățiri de stabilitate pentru apeluri A/V
  
  
  
    
    @@ -0,0 +1 @@
+* Quicksy: primește automat verificare SMS
  
  
  
    
    @@ -0,0 +1,3 @@
+* Arată butonul de apel pentru contacte deconectate dacă au anuțat anterior suportul
+* Butonul de revenire nu mai termină apelul când apelul este conectat
+* corecturi de erori
  
  
  
    
    @@ -0,0 +1,4 @@
+* Posibilitatea de a selecta tonuri de apel
+* Corectat descoperirea ID-ului cheii OpenPGP pentru OpenKeychain 5.6+
+* Verificarea corectă a certificatelor TLS punycode
+* Îmbunătățit stabilitatea creerii sesiunii RTP (apelare)
  
  
  
    
    @@ -0,0 +1,2 @@
+* Verifică apelurile A/V cu sesiuni OMEMO preexistente
+* Îmbunătățit compatibilitatea cu implementări WebRTC non libwebrtc
  
  
  
    
    @@ -0,0 +1,2 @@
+* Corectat felurite erori relative la suportul pentru Tor
+* Îmbunătățit compatibilitatea apelurilor cu Dino
  
  
  
    
    @@ -0,0 +1 @@
+* Corectat up/download HTTP pentru utilizatorii care nu au încredere în CA din sistem
  
  
  
    
    @@ -0,0 +1 @@
+* Rezolvat problemele 'lipsă conectivitate' pe Android 7.1
  
  
  
    
    @@ -1 +1 @@
-* Улучшена интеграция вызовов A/V с операционной системой
+* Улучшена интеграция аудио- и видеовызовов с операционной системой
  
  
  
    
    @@ -0,0 +1,3 @@
+* Исправлены аудио- и видеовызовы на Android 8
+* Исправлены приоритеты в новой интеграции вызовов
+* Исправлена проблема со сжатием видео
  
  
  
    
    @@ -0,0 +1,2 @@
+* Восстановлен доступ к поиску каналов для Android 6+7
+* Улучшено ведение журнала для неудачной интеграции вызовов
  
  
  
    
    @@ -0,0 +1,3 @@
+* Используется тема Material 3
+* Реорганизация настроек
+* Синхронизация состояния прочтения на разных устройствах
  
  
  
    
    @@ -0,0 +1,2 @@
+* Отображение статуса сообщения значком
+* Добавлен параметр "Крупный шрифт" для пузырей сообщений
  
  
  
    
    @@ -0,0 +1,2 @@
+* исправлена регистрация Quicksy на Android 6-7
+* проигрывание рингтона вызова через канал уведомлений
  
  
  
    
    @@ -0,0 +1,2 @@
+* исправлена интеграция звонков для устройств с Android 14
+* добавлена настройка «Приглашения от незнакомцев»
  
  
  
    
    @@ -0,0 +1,3 @@
+* Планировщик регулярного резервного копирования
+* Все устройства Realme до Android 11 исключены из интеграции вызовов
+* Незначительные улучшения интерфейса (пузыри сообщений)
  
  
  
    
    @@ -0,0 +1,2 @@
+* Исправлена ошибка, из-за которой звук вызова включался при переключении устройств вывода
+* Все устройства Umidigi исключены из интеграции вызовов
  
  
  
    
    @@ -0,0 +1 @@
+* Резервное копирование запускается как приоритетная служба, чтобы предотвратить остановку процесса через 10 минут
  
  
  
    
    @@ -0,0 +1,2 @@
+* старые устройства Oppo исключены из интеграции звонков
+* различные исправления
  
  
  
    
    @@ -0,0 +1 @@
+* Незначительные исправления
  
  
  
    
    @@ -0,0 +1,3 @@
+* Предлагаются более высокие значения автоматического приёма файлов
+* Отображается дополнительная информация в разделе "Информация о сервере"
+* Различные исправления ошибок
  
  
  
    
    @@ -0,0 +1 @@
+* Добавлено время истечения для попытки начала вызова
  
  
  
    
    @@ -0,0 +1 @@
+* Поддержка реакций сообщений
  
  
  
    
    @@ -0,0 +1 @@
+* Исправлен сбой интерфейса при отображении нескольких реакций
  
  
  
    
    @@ -0,0 +1,2 @@
+* Исправлены звонки на Android 15
+* Исправлен редкий сбой, появившийся в версии 2.17.0
  
  
  
    
    @@ -0,0 +1,2 @@
+* Постоянное отображение кнопки звонка
+* Различные исправления ошибок
  
  
  
    
    @@ -0,0 +1 @@
+* Улучшена обработка некоторых реакций на эмодзи
  
  
  
    
    @@ -0,0 +1,2 @@
+* Пузыри сообщений сдвигаются ближе друг к другу без объединения
+* Добавлена возможность скрывать аватары в беседах, если в них нет особой необходимости (Настройки → Интерфейс → Пузыри сообщений → Показывать аватары)
  
  
  
    
    @@ -0,0 +1 @@
+* Добавлена возможность показывать пузыри сообщений с выравниванием по левому краю
  
  
  
    
    @@ -0,0 +1,3 @@
+* Упрощён доступ к настройке звука уведомлений (через "Сведения о контакте" → Дополнительное меню → "Настраиваемые уведомления")
+* Исправлены получатели прямого обмена в новых версиях Android
+* Добавлена возможность ограничить отображение аватара для контактов
  
  
  
    
    @@ -0,0 +1,2 @@
+* Исправлены незначительные ошибки интерфейса
+* Исправлены проблемы с подключением к доменам .onion на нестандартных портах
  
  
  
    
    @@ -0,0 +1 @@
+* Mbulim për Reagime Ndaj Mesazhesh
  
  
  
    
    @@ -0,0 +1 @@
+* Ndreqje e një problemi të vockël në UI, kur shfaqen reagime të shumtë
  
  
  
    
    @@ -0,0 +1,2 @@
+* Ndreqje thirrjesh në Android 15
+* Ndreqje vithisjeje / prapakthimi të rrallë, sjellë me 2.17.0
  
  
  
    
    @@ -0,0 +1,2 @@
+* Shfaq përherë buton thirrjesh
+* Ndreqje të ndryshme të metash
  
  
  
    
    @@ -0,0 +1 @@
+* përmirësim i trajtimit të disa reagimeve me emoxhi
  
  
  
    
    @@ -0,0 +1,2 @@
+* Kalim i flluskave të mesazheve më afër njëra-tjetrës, në vend se përzierje e tyre
+* Shtim aftësie për fshehje avatarësh në pamje fjalosjesh, kur s’është domosdo e nevojshme (Rregullime -> Ndërfaqe -> Flluska Fjalosjesh -> Shfaq avatarë)
  
  
  
    
    @@ -0,0 +1 @@
+* Shtim aftësie për t’i shfaqur flluskat e mesazheve në të majtë
  
  
  
    
    @@ -0,0 +1,3 @@
+* Përdorim më i kollajtë i tingujsh vetjakë njoftimesh, përmes hollësish Kontakti -> Menu shtresë përsipër -> Njoftime vetjake)
+* Ndreqje objektivash ndarjeje të drejtpërdrejtë në versione të rinj Android
+* Aftësi për t’u kufizuar kontakteve dukshmëri avatarësh
  
  
  
    
    @@ -0,0 +1,2 @@
+* Ndreqje e disa të metave të vockla në UI
+* Ndreqje problemesh lidhjeje me përkatësi .onion në porta jo-parazgjedhje
  
  
  
    
    @@ -0,0 +1 @@
+* Erbjud att spela in ljudmeddelande när mottagaren av samtalet är upptagen
  
  
  
    
    @@ -0,0 +1,3 @@
+* Hantera GPX-filer
+* Förbättra prestanda för återställning av säkerhetskopia
+* bugg-fixar
  
  
  
    
    @@ -0,0 +1 @@
+* mindre stabilitetsförbättringar för A/V-samtal
  
  
  
    
    @@ -0,0 +1,2 @@
+* Diverse bugg-fixar runt stöd för Tor
+* Förbättra samtalskompabilitet med Dino
  
  
  
    
    @@ -0,0 +1,2 @@
+* Fixade krasch vid rendering av några citat
+* Fixade krasch i välkomstskärmen
  
  
  
    
    @@ -0,0 +1 @@
+* Fixade återgång med filöverföring via P2P
  
  
  
    
    @@ -0,0 +1 @@
+* Ta bort funktionen för att upptäcka kanaler från Google Play-versionen
  
  
  
    
    @@ -0,0 +1 @@
+* Inaktivera öppnandet av säkerhetskopieringsfiler (.ceb) från filhanteraren
  
  
  
    
    @@ -0,0 +1,2 @@
+* stöd för inställningar för meddelanden per konversation
+* använd opus för ljudmeddelanden i Android 10
  
  
  
    
    @@ -0,0 +1 @@
+* Fixade mindre återgångar som introducerades med 2.13.1
  
  
  
    
    @@ -0,0 +1 @@
+* Förbättra integrationen av A/V-samtal i operativsystemet
  
  
  
    
    @@ -0,0 +1,3 @@
+* Använd temat Material 3
+* Omorganisera inställningar
+* Synkronisera läs-status mellan enheter
  
  
  
    
    @@ -0,0 +1,2 @@
+* Visa meddelandestatus som ikoner
+* Introducera inställningen 'Stort typsnitt' för meddelandebubblor
  
  
  
    
    @@ -0,0 +1 @@
+* Kör säkerhetskopiering som en förgrundstjänst för att förhindra processer stoppas efter 10 minuter
  
  
  
    
    @@ -0,0 +1,2 @@
+* exkludera äldre Oppo-enheter från samtalsintegrering
+* diverse bugg-fixar
  
  
  
    
    @@ -0,0 +1 @@
+* Lägg till en tidsgräns för att påbörja samtalet
  
  
  
    
    @@ -0,0 +1 @@
+* Fixade en störning i det grafiska gränssnittet när flertalet reaktioner visas
  
  
  
    
    @@ -0,0 +1,2 @@
+* Visa alltid samtalsknapp
+* Diverse bugg-fixar
  
  
  
    
    @@ -0,0 +1 @@
+* förbättra hantering av en del emoji-reaktioner
  
  
  
    
    @@ -0,0 +1 @@
+* Lägg till förmåga att visa vänster-justerade meddelande-bubblor
  
  
  
    
    @@ -0,0 +1 @@
+* Підтримка реакцій на повідомлення
  
  
  
    
    @@ -0,0 +1 @@
+* Виправлено помилку інтерфейсу під час показу декількох реакцій
  
  
  
    
    @@ -0,0 +1,2 @@
+* Виправлено виклики на Android 15
+* Виправлено рідкісний збій / регресію, що з'явилися у 2.17.0
  
  
  
    
    @@ -0,0 +1,2 @@
+* Завжди показувати кнопку виклику
+* Виправлення різноманітних помилок
  
  
  
    
    @@ -0,0 +1 @@
+* Покращено обробку деяких реакцій емоджі
  
  
  
    
    @@ -0,0 +1,2 @@
+* Бульбашки повідомлень розташовуються ближче одна до одної, а не об'єднуються
+* Додано можливість приховати непотрібні аватари у розмовах (Налаштування -> Інтерфейс -> Вигляд повідомлень -> Показувати аватари)
  
  
  
    
    @@ -0,0 +1 @@
+* Додано можливість вирівнювати бульбашки повідомлень ліворуч
  
  
  
    
    @@ -0,0 +1,3 @@
+* Полегшено доступ до користувацьких звуків сповіщень (Деталі контакту -> Додатково -> Індивідуальні сповіщення)
+* Виправлення цілей прямого поширення на нових версіях Android
+* Можливість обмежити видимість аватара для контактів
  
  
  
    
    @@ -0,0 +1,2 @@
+* Виправлено деякі незначні помилки в інтерфейсі
+* Виправлено проблеми з'єднання з доменами .onion на портах не за замовчуванням
  
  
  
    
    @@ -0,0 +1 @@
+* 支持消息回应
  
  
  
    
    @@ -0,0 +1 @@
+* 修复显示多个回应时的 UI 故障
  
  
  
    
    @@ -0,0 +1,2 @@
+* 修复 Android 15 上的通话
+* 修复 2.17.0 引入的罕见崩溃问题
  
  
  
    
    @@ -0,0 +1,2 @@
+* 始终显示呼叫按钮
+* 各种错误修复
  
  
  
    
    @@ -0,0 +1 @@
+* 改进某些表情符号回应的处理
  
  
  
    
    @@ -0,0 +1,2 @@
+* 将消息气泡靠得更近而不是合并它们
+* 添加在聊天视图中隐藏头像的功能(设置 -> 界面 -> 消息气泡 -> 显示头像)
  
  
  
    
    @@ -0,0 +1 @@
+* 添加左对齐显示消息气泡的功能
  
  
  
    
    @@ -0,0 +1,3 @@
+* 通过(联系详情 -> 溢出菜单 -> 自定义通知)更轻松地访问自定义通知提示音
+* 修复新 Android 版本上的直接共享目标
+* 能够限制头像对联系人的可见性
  
  
  
    
    @@ -0,0 +1,2 @@
+* 修复一些小的 UI 错误
+* 修复非默认端口上 .onion 域的连接问题
  
  
  
    
    @@ -1,2 +1,2 @@
 * 重做憑證登錄的使用者介面
-* 新增置頂聊天的功能(加入最愛)
+* 新增釘選聊天的功能(加入最愛)
  
  
  
    
    @@ -0,0 +1,3 @@
+* 安排定期備份
+* 從通話集成中排除所有 Android 11 及以下的 realme 裝置
+* Minor UI(消息氣泡)改進
  
  
  
    
    @@ -0,0 +1,2 @@
+* 修復了切換輸出設備時通話被取消靜音的問題
+* 從呼叫集成中排除所有 Umidigi 裝置
  
  
  
    
    @@ -0,0 +1 @@
+* 將備份作為前台服務運行,以防止進程在 10 分鐘後停止
  
  
  
    
    @@ -0,0 +1,2 @@
+* 從通話集成中排除較舊的 Oppo裝置
+* 各種錯誤修復
  
  
  
    
    @@ -0,0 +1 @@
+* 小錯誤修復
  
  
  
    
    @@ -0,0 +1,3 @@
+* 提供更高的自動檔案接受值
+* 在「伺服器資訊」中提供更多資訊
+* 各種錯誤修復
  
  
  
    
    @@ -0,0 +1 @@
+* 增加調用發起超時
  
  
  
    
    @@ -0,0 +1 @@
+* 支援消息回應
  
  
  
    
    @@ -0,0 +1 @@
+* 修復顯示多個反應時的 UI 故障
  
  
  
    
    @@ -0,0 +1,2 @@
+* 修復 Android 15 上的通話
+* 修復 2.17.0 中引入的罕見崩潰/回歸問題
  
  
  
    
    @@ -4,17 +4,17 @@ import com.google.auto.service.AutoService;
 import com.google.common.base.CaseFormat;
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
+import com.google.common.collect.ComparisonChain;
 import com.google.common.collect.ImmutableMap;
-
+import com.google.common.collect.ImmutableSortedMap;
 import im.conversations.android.annotation.XmlElement;
 import im.conversations.android.annotation.XmlPackage;
-
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
+import javax.annotation.Nonnull;
 import javax.annotation.processing.AbstractProcessor;
 import javax.annotation.processing.Processor;
 import javax.annotation.processing.RoundEnvironment;
@@ -38,7 +38,7 @@ public class XmlElementProcessor extends AbstractProcessor {
     public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
         final Set<? extends Element> elements =
                 roundEnvironment.getElementsAnnotatedWith(XmlElement.class);
-        final ImmutableMap.Builder<Id, String> builder = ImmutableMap.builder();
+        final ImmutableSortedMap.Builder<Id, String> builder = ImmutableSortedMap.naturalOrder();
         for (final Element element : elements) {
             if (element instanceof final TypeElement typeElement) {
                 final Id id = of(typeElement);
@@ -160,7 +160,7 @@ public class XmlElementProcessor extends AbstractProcessor {
         return false;
     }
 
-    public static class Id {
+    public static class Id implements Comparable<Id> {
         public final String name;
         public final String namespace;
 
@@ -181,5 +181,13 @@ public class XmlElementProcessor extends AbstractProcessor {
         public int hashCode() {
             return Objects.hashCode(name, namespace);
         }
+
+        @Override
+        public int compareTo(@Nonnull Id id) {
+            return ComparisonChain.start()
+                    .compare(namespace, id.namespace)
+                    .compare(name, id.name)
+                    .result();
+        }
     }
 }
  
  
  
    
    @@ -45,6 +45,11 @@
   !transient <fields>;
 }
 
+# Needed for proper GSON deserialization
+-keep class com.google.gson.reflect.TypeToken
+-keep class * extends com.google.gson.reflect.TypeToken
+-keep public class * implements java.lang.reflect.Type
+
 # Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
 # EnclosingMethod is required to use InnerClasses.
 -keepattributes Signature, InnerClasses, EnclosingMethod
  
  
  
    
    @@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
-	<share-target android:targetClass="eu.siacs.conversations.ui.ShareWithActivity">
-		<data android:mimeType="*/*" />
-		<category android:name="com.cheogram.android.SHARE_TARGET" />
-	</share-target>
-</shortcuts>
  
  
  
    
    @@ -1,39 +1,37 @@
-Fácil de usar, fiable y con poca batería. Con soporte integrado para imágenes, chats de grupo y cifrado e2e.
+Fácil de usar, confiable y amigable con la batería. Con soporte integrado para imágenes, chats grupales y cifrado de extremo a extremo.
 
 Principios de diseño:
 
-* Ser lo más bonito y fácil de usar posible sin sacrificar la seguridad ni la privacidad.
-* Basarse en protocolos existentes y bien establecidos.
-* No requerir una cuenta de Google o, específicamente, Google Cloud Messaging (GCM).
-* Requerir el menor número de permisos posible
-
+Ser tan bello y fácil de usar como sea posible, sin sacrificar la seguridad o la privacidad.
+Confiar en protocolos existentes y bien establecidos.
+No requerir una cuenta de Google ni Google Cloud Messaging (GCM).
+Requerir la menor cantidad de permisos posible.
 Características:
 
-* Cifrado de extremo a extremo con <a href="http://conversations.im/omemo/">OMEMO</a> o <a href="http://openpgp.org/about/">OpenPGP</a>.
-* Envío y recepción de imágenes
-* Llamadas de audio y vídeo cifradas (DTLS-SRTP)
-* Interfaz de usuario intuitiva que sigue las directrices de diseño de Android
-* Imágenes / Avatares para tus contactos
-* Sincronización con el cliente de escritorio
-* Conferencias (con soporte para marcadores)
-* Integración de la libreta de direcciones
-* Múltiples cuentas / bandeja de entrada unificada
-* Muy bajo impacto en la duración de la batería
-
-Conversations hace que sea muy fácil crear una cuenta en el servidor gratuito conversations.im. Sin embargo, Conversations también funciona con cualquier otro servidor XMPP. Muchos servidores XMPP están gestionados por voluntarios y son gratuitos.
+Cifrado de extremo a extremo con <a href="http://conversations.im/omemo/">OMEMO</a> o <a href="http://openpgp.org/about/">OpenPGP</a>.
+Envío y recepción de imágenes.
+Llamadas de audio y video cifradas (DTLS-SRTP).
+Interfaz intuitiva que sigue las pautas de diseño de Android.
+Imágenes / Avatares para tus contactos.
+Sincronización con el cliente de escritorio.
+Conferencias (con soporte para marcadores).
+Integración con la agenda de contactos.
+Múltiples cuentas / bandeja de entrada unificada.
+Muy bajo impacto en la duración de la batería.
+Conversations facilita mucho la creación de una cuenta en el servidor gratuito conversations.im. Sin embargo, Conversations también funcionará con cualquier otro servidor XMPP. Muchos servidores XMPP son gestionados por voluntarios y son gratuitos.
 
 Características de XMPP:
 
-Conversations funciona con todos los servidores XMPP existentes. Sin embargo, XMPP es un protocolo extensible. Estas extensiones también están estandarizadas en los llamados XEP. Conversations soporta un par de ellas para mejorar la experiencia general del usuario. Existe la posibilidad de que su actual servidor XMPP no soporte estas extensiones. Por lo tanto, para sacar el máximo provecho de Conversaciones deberías considerar o bien cambiar a un servidor XMPP que lo haga o - mejor aún - ejecutar tu propio servidor XMPP para ti y tus amigos.
+Conversations funciona con todos los servidores XMPP disponibles. Sin embargo, XMPP es un protocolo extensible. Estas extensiones también están estandarizadas en lo que se llaman XEP. Conversations admite algunas de estas para mejorar la experiencia general del usuario. Existe la posibilidad de que tu servidor XMPP actual no soporte estas extensiones. Por lo tanto, para aprovechar al máximo Conversations, deberías considerar cambiar a un servidor XMPP que sí lo haga o, aún mejor, configurar tu propio servidor XMPP para ti y tus amigos.
 
-Estos XEPs son (por el momento):
+Estos XEP son, hasta ahora:
 
-* XEP-0065: SOCKS5 Bytestreams (o mod_proxy65). Se utilizará para transferir archivos si ambas partes están detrás de un cortafuegos (NAT).
-* XEP-0163: Protocolo de Evento Personal para avatares
-* XEP-0191: El comando de bloqueo te permite hacer una lista negra de spammers o bloquear contactos sin eliminarlos de tu lista.
-* XEP-0198: Stream Management permite a XMPP sobrevivir a pequeños cortes de red y cambios de la conexión TCP subyacente.
-* XEP-0280: Message Carbons que sincroniza automáticamente los mensajes que envías a tu cliente de escritorio y por lo tanto te permite cambiar sin problemas de tu cliente móvil a tu cliente de escritorio y viceversa en una sola conversación.
-* XEP-0237: Versionado de listas, principalmente para ahorrar ancho de banda en conexiones móviles deficientes.
-* XEP-0313: Gestión de Archivo de Mensajes sincroniza el historial de mensajes con el servidor. Ponerse al día con los mensajes que fueron enviados mientras Conversaciones estaba fuera de línea.
-* XEP-0352: Indicación del Estado del Cliente permite al servidor saber si Conversaciones está o no en segundo plano. Permite al servidor ahorrar ancho de banda reteniendo paquetes sin importancia.
-* XEP-0363: Carga de Archivos HTTP permite compartir archivos en conferencias y con contactos sin conexión. Requiere un componente adicional en su servidor.
+XEP-0065: SOCKS5 Bytestreams (o mod_proxy65). Se utilizará para transferir archivos si ambas partes están detrás de un firewall o NAT.
+XEP-0163: Protocolo de Eventos Personales para avatares.
+XEP-0191: El comando de bloqueo te permite poner en la lista negra a spammers o bloquear contactos sin eliminarlos de tu lista de contactos.
+XEP-0198: La gestión de flujos permite que XMPP sobreviva a pequeñas interrupciones de red y cambios en la conexión TCP subyacente.
+XEP-0280: Message Carbons que sincroniza automáticamente los mensajes que envías a tu cliente de escritorio, lo que te permite cambiar sin problemas entre tu cliente móvil y el de escritorio dentro de una conversación.
+XEP-0237: Versionado de Roster, principalmente para ahorrar ancho de banda en conexiones móviles deficientes.
+XEP-0313: Gestión del Archivo de Mensajes que sincroniza el historial de mensajes con el servidor. Pone al día los mensajes que se enviaron mientras Conversations estaba fuera de línea.
+XEP-0352: Indicación de Estado del Cliente que informa al servidor si Conversations está en segundo plano o no. Permite al servidor ahorrar ancho de banda al retener paquetes no importantes.
+XEP-0363: Carga de Archivos HTTP que permite compartir archivos en conferencias y con contactos fuera de línea. Requiere un componente adicional en tu servidor.
  
  
  
    
    @@ -1 +1 @@
-Mensajería instantánea XMPP cifrada y fácil de usar para tu teléfono inteligente
+Mensajería instantánea XMPP cifrada y fácil de usar para tu dispositivo móvil
  
  
  
    
    @@ -0,0 +1,39 @@
+Lihtsaltkasutatav, töökindel ja akusõbralik. Piltide, jututubade ja läbiva krüptimise tugi.
+
+Eesmärgid:
+
+* Ilus ja lihtne rakendus ilma kahjustamata turvalisust ja privaatsust
+* Olemasolevate ja toimivate protokollide kasutamine
+* Peab toimima ilma Google kasutajakontota ning mitte vajama Google Cloud Messaging (GCM) teenuseid
+* Peab eeldama nutiseadmes võimalikult vähe õigusi
+
+Funktsionaalsus:
+
+* Läbiv krüptimine kas <a href="http://conversations.im/omemo/">OMEMO</a> või <a href="http://openpgp.org/about/">OpenPGP</a> abil
+* Võimalus saata ja vastu võtta pilte
+* Krüptitud hääl- ja videokõned (DTLS-SRTP)
+* Intuitiivne kasutajaliides, mis lähtub Androidi kujunduspõhimõtetest
+* Kontaktide pildid ja tunnuspildid
+* Sünkroniseerimine töölauakliendiga
+* Konverentsid/jututoad (järjehoidjate toega)
+* Lõimimine aadressiraamatuga
+* Mitme kasutajakonto kasutamise võimalus ühise sisendvooga
+* Üliminimaalne mõju akukasutusele
+
+Conversationsi abil on lihtne lisada kasutajakontot tasuta conversations.im serveris. Aga Conversations toimib hästi kõikide XMPP serveritega. Paljusid XMPP-servereid haldavad vabatahtlikud ning nende kasutamine on tasuta.
+
+XMPP funktsionaalsus:
+
+Conversations toimib igasuguste XMPP-serveritega. Aga XMPP on laiendatav protokoll ning need laiendused on standardiseeritud XEP'idena. Et üldine kasutajakogemus oleks sujuv, toetab Conversations neist paljusid. On võimalus, et sinu XMPP serveris pole mõni konkreetne laiendus kasutusel. Seega parima tulemuse saad siis, kui leiad serveri, mis toetab neist kõiki või paned enda ja oma sõprade jaoks püsti oma XMPP serveri.
+
+Hetkel on toetatud XEP'id:
+
+* XEP-0065: SOCKS5 Bytestreams (või mod_proxy65). On kasutusel failide teisaldamisel, kui kõik vestluse osapooled on tulemüüri taga või asuvad NATitud võrgus.
+* XEP-0163: Personal Eventing Protocol tunnuspiltide kasutamise jaoks.
+* XEP-0191: Blocking Command võimaldab blokeerida spämmereid ja muid mittesoovitud osapooli ilma kontaktiloendi muutmiseta.
+* XEP-0198: Stream Management võimaldab XMPP-ühendusel toimida väikeste võrgukatkestuste ja TCP-ühenduste muutuste puhul.
+* XEP-0280: Message Carbons automaatselt sünkroniseerib sõnumid nutiseadme ja töölauakliendi vahel ning võimaldab sul tõhusalt kasutada neist seda, mida parasjagu vaja.
+* XEP-0237: Roster Versioning võimaldab säästa ribalaiust kehvade sideühenduste puhul.
+* XEP-0313: Message Archive Management sünkroniseerib sõnumite ajalugu serveri ja klientide vahel. See tahab, et saad sõnumid ka sõnumid ajast, mil Conversations polnud võrgus.
+* XEP-0352: Client State Indication võimaldab serveril teada, kas Conversations töötab taustal. Võimaldab serveril jätta saatmata mittevajalikud paketid ja sellega säästa ribalaiust.
+* XEP-0363: HTTP File Upload võimaldab jagada faile jututubades ja vallasrežiimis klientidega. Eeldab täiendava mooduli kasutamist serveris.
  
  
  
    
    @@ -0,0 +1 @@
+Krüptitud ja lihtsaltkasutatav XMPP klient sinu nutiseadme jaoks
  
  
  
    
    @@ -28,7 +28,7 @@ Conversations funziona con tutti i server XMPP. Tuttavia XMPP è un protocollo e
 
 Queste XEP sono, ad oggi:
 
-* XEP-0065: SOCKS5 Bytestreams (o mod_proxy65). Usata per trasferire file se entrambe le parti sono dietro un firewall (NAT).
+* XEP-0065: SOCKS5 Bytestreams (o mod_proxy65). Usata per trasferire file se entrambe le parti sono dietro un firewall o NAT.
 * XEP-0163: Personal Eventing Protocol. Per gli avatar.
 * XEP-0191: Blocking command. Ti consente di bloccare lo spam o i contatti senza rimuoverli dal tuo elenco.
 * XEP-0198: Stream Management. Consente a XMPP di resistere a brevi disconnessioni e cambi della connessione TCP sottostante.
  
  
  
    
    @@ -1 +1 @@
-Зашифрованный и простой в использовании XMPP мессенджер для вашего мобильного
+Шифрующий и простой в использовании XMPP-мессенджер для мобильного устройства
  
  
  
    
    @@ -36,4 +36,4 @@ Këto XEP-e janë - deri sot:
 * XEP-0237: Roster Versioning kryesisht për të kursyer sasi trafiku në lidhje celulare të dobëta
 * XEP-0313: Message Archive Management njëkohëson historik mesazhesh me shërbyesin. Ndiqni mesazhet që qenë dërguar ndërkohë që Conversations s’qe në linjë.
 * XEP-0352: Client State Indication i lejon shërbyesit të dijë nëse është apo jo në prapaskenë Conversations. I lejon shërbyesit të kursejë sasi trafiku, duke mbajtur paketa pa rëndësi.
-* XEP-0363: HTTP File Upload ju lejon të ndani me të tjerë kartela në konferenca dhe me kontakte jo në linjë. Lyp një përbërë shtesë në shërbyesin tuaj.
+* XEP-0363: HTTP File Upload ju lejon të ndani me të tjerë kartela në konferenca dhe me kontakte jo në linjë. Lyp një përbërës shtesë në shërbyesin tuaj.
  
  
  
    
    @@ -28,7 +28,7 @@ Conversations ради уз сваки XMPP сервер. Међутим XMPP ј
 
 Ови XEP-ови су - за сада:
 
-* XEP-0065: SOCKS5 Bytestreams (или mod_proxy65). Користи се за пребацивање фајлова ако су обе стране иза firewall-а (NAT).
+* XEP-0065: SOCKS5 Bytestreams (или mod_proxy65). Користи се за пребацивање фајлова ако су обе стране иза firewall-а или NAT-а).
 * XEP-0163: Personal Eventing Protocol за аватаре
 * XEP-0191: Blocking command омогућава blacklist-овање спамера или блокирање контаката без њиховог уклањања из твог списка.
 * XEP-0198: Stream Management омогућава да XMPP преживи мање прекиде на мрежи и промене у TCP веза.
  
  
  
    
    @@ -28,7 +28,7 @@ Conversations 可以在所有 XMPP 伺服器上運作。然而,XMPP 是一個
 
 如下 XEP - 截止目前:
 
-* XEP-0065:SOCKS5 位元資料流 (或 mod_proxy65),將被用於傳輸檔案,如果雙方都在防火牆之後 (NAT)。
+* XEP-0065:SOCKS5 位元資料流 (或 mod_proxy65),將被用於傳輸檔案,如果雙方都在防火牆或NAT之後。
 * XEP-0163:用於虛擬化身的私人活動通訊協定
 * XEP-0191:封鎖命令可讓您將濫發垃圾郵件者列入黑名單,或封鎖聯絡人而不把他們從名冊中移除。
 * XEP-0198:串流管理允許 XMPP 在小型網路中斷和基礎 TCP 連線的變更中生存。
  
  
  
    
    @@ -4,12 +4,13 @@
     <string name="use_conversations.im">Käytä conversations.im:ää</string>
     <string name="create_new_account">Luo uusi tili</string>
     <string name="do_you_have_an_account">Onko sinulla jo XMPP-tunnus? Jos käytät jo toista XMPP-sovellusta tai olet käyttänyt Conversationsia aiemmin, niin voi olla. Jos ei, voit tehdä uuden XMPP-tilin saman tien.\nVinkki: Jotkin sähköpostipalvelut tarjoavat myös XMPP-tilin.</string>
-    <string name="server_select_text">XMPP on tietystä palveluntarjoasta riippumaton pikaviestiverkosto. Voit käyttää tätä asiakasohjelmaa minkä tahansa haluamasi XMPP-palvelimen kanssa.
-\nHelppouden nimissä olemme kuitenkin helpottaneet tilin luomista conversations.im:iin.</string>
+    <string name="server_select_text">XMPP on tietystä palveluntarjoasta riippumaton pikaviestiverkosto. Voit käyttää tätä sovellusta minkä tahansa haluamasi XMPP-palvelimen kanssa.\nHelppouden nimissä olemme kuitenkin helpottaneet tilin luomista conversations.im:iin.</string>
     <string name="magic_create_text_on_x">Sinut on kutsuttu %1$s:iin. Opastamme sinua tilin luomisen kanssa.\nValitessasi palvelimen %1$s palveluntarjoajaksesi voit jutella muiden palveluntajoajien käyttäjien kanssa kertomalla heille koko XMPP-osoitteesi.</string>
     <string name="magic_create_text_fixed">Sinut on kutsuttu palvelimelle %1$s. Käyttäjänimesi on valittu valmiiksi puolestasi. Opastamme sinua tilin luomisen kanssa.\nVoit jutella muiden palveluntarjoajien käyttäjien kanssa kertomalle heille koko XMPP-osoitteesi.</string>
     <string name="your_server_invitation">Kutsusi palvelimelle</string>
     <string name="improperly_formatted_provisioning">Virheellisesti muotoiltu koodi</string>
     <string name="if_contact_is_nearby_use_qr">Jos henkilö on lähellä, hän voi myös hyväksyä kutsun lukemalla allaolevan koodin.</string>
-    <string name="share_invite_with">Jaa kutsu sovelluksella...</string>
+    <string name="share_invite_with">Jaa kutsu sovelluksella…</string>
+    <string name="tap_share_button_send_invite">Napauta jakamispainiketta lähettääksesi kontaktillesi kutsun %1$s:een.</string>
+    <string name="easy_invite_share_text">Liity %1$s:hen ja pikakeskustele kanssani: %2$s</string>
 </resources>
  
  
  
    
    @@ -4,8 +4,7 @@
     <string name="use_conversations.im">A conversations.im használata</string>
     <string name="create_new_account">Új fiók létrehozása</string>
     <string name="do_you_have_an_account">Már rendelkezik XMPP-fiókkal? Ez az eset állhat fenn, ha már egy másik XMPP-klienst használ, vagy ha már korábban használta a Conversations alkalmazást. Ha nem, akkor most létrehozhat egy új XMPP-fiókot.\nTipp: egyes e-mail szolgáltatók is biztosítanak XMPP-fiókokat.</string>
-    <string name="server_select_text">Az XMPP egy szolgáltatófüggetlen, azonnali üzenetküldő hálózat. Ezt a kliensprogramot bármely XMPP-kiszolgálóhoz használhatja.
-\nAzonban a kényelem érdekében megkönnyítettük a conversations.im szolgáltatón való fióklétrehozást, ami kifejezetten a Conversations alkalmazással történő használatra lett tervezve.</string>
+    <string name="server_select_text">Az XMPP egy szolgáltatófüggetlen, azonnali üzenetküldő hálózat. Ezt az alkalmazást bármely XMPP-kiszolgálóhoz használhatja.\nAzonban a kényelem érdekében megkönnyítettük a conversations.im szolgáltatón való fióklétrehozást, ami kifejezetten a Conversations alkalmazással történő használatra lett tervezve.</string>
     <string name="magic_create_text_on_x">Meghívást kapott a(z) %1$s kiszolgálóra. Végig fogjuk vezetni egy fiók létrehozásának folyamatán.\nHa a(z) %1$s kiszolgálót választja szolgáltatóként, akkor képes lesz más szolgáltatók felhasználóival is kommunikálni, ha megadja nekik a teljes XMPP-címét.</string>
     <string name="magic_create_text_fixed">Meghívást kapott a(z) %1$s kiszolgálóra. Már kiválasztottak Önnek egy felhasználónevet. Végig fogjuk vezetni egy fiók létrehozásának folyamatán.\nKépes lesz más szolgáltatók felhasználóival is kommunikálni, ha megadja nekik a teljes XMPP-címét.</string>
     <string name="your_server_invitation">Az Ön kiszolgálómeghívása</string>
  
  
  
    
    @@ -4,7 +4,7 @@
     <string name="use_conversations.im">Использовать conversations.im</string>
     <string name="create_new_account">Создать новый аккаунт</string>
     <string name="do_you_have_an_account">У вас есть аккаунт XMPP? Если вы использовали Conversations или другой XMPP-клиент в прошлом, то скорее всего, он у вас есть. Если у вас нет аккаунта, вы можете создать его прямо сейчас.
-\nПодсказка: некоторые провайдеры электронной почты также регистрируют аккаунты XMPP.</string>
+\nПодсказка: некоторые провайдеры эл. почты также предоставляют аккаунты XMPP.</string>
     <string name="server_select_text">XMPP - это независимая сеть обмена сообщениями. Это приложение позволяет подключиться к любому XMPP-серверу на ваш выбор.
 \nЕсли у вас нет сервера, предлагаем вам зарегистрировать аккаунт на conversations.im, сервере, специально предназначенном для работы с Conversations.</string>
     <string name="magic_create_text_on_x">Вас пригласили на %1$s. Мы проведём вас через процесс создания аккаунта. 
@@ -13,8 +13,8 @@
 \nЭтот аккаунт позволит вам общаться с пользователями и на этом, и на других серверах, используя ваш полный XMPP-адрес.</string>
     <string name="your_server_invitation">Ваше приглашение</string>
     <string name="improperly_formatted_provisioning">Неправильный формат кода</string>
-    <string name="tap_share_button_send_invite">Нажмите кнопку «Поделиться», чтобы отправить вашему контакту приглашение в %1$s.</string>
+    <string name="tap_share_button_send_invite">Нажмите кнопку \"Поделиться\", чтобы отправить вашему контакту приглашение в %1$s.</string>
     <string name="if_contact_is_nearby_use_qr">Если ваш контакт находится поблизости, он также может отсканировать приведённый ниже код, чтобы принять ваше приглашение.</string>
     <string name="easy_invite_share_text">Присоединяйтесь к %1$s и пообщайтесь со мной: %2$s</string>
-    <string name="share_invite_with">Поделиться приглашением с…</string>
+    <string name="share_invite_with">Поделиться приглашением…</string>
 </resources>
  
  
  
    
    @@ -1,20 +1,16 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <string name="pick_a_server">选择您的 XMPP 提供者</string>
+    <string name="pick_a_server">选择 XMPP 提供者</string>
     <string name="use_conversations.im">使用 conversations.im</string>
     <string name="create_new_account">创建新账号</string>
-    <string name="do_you_have_an_account">您已经有 XMPP 账号了吗?如果您之前使用过 Conversations 或其他 XMPP 客户端,那么您已经有账号了。如果没有,您可以立即创建一个。
-\n提示:一些电子邮件服务也提供 XMPP 账号。</string>
-    <string name="server_select_text">XMPP 是独立于提供者的即时通讯网络。您选择的任何 XMPP 服务器都可以使用此应用。
-\n不过,您可以轻松地在 conversations.im 上创建账号;特别适合与 Conversations 使用的提供者。</string>
-    <string name="magic_create_text_on_x">您已受邀加入 %1$s。我们将指导您创建账号。
-\n当选择 %1$s 作为提供者时,向其他 XMPP 用户提供您的完整地址,就能和对方交流。</string>
-    <string name="magic_create_text_fixed">您已受邀加入 %1$s。已为您选择了用户名。我们将指导您创建账号。
-\n向其他 XMPP 用户提供您的完整地址,就能和对方交流。</string>
+    <string name="do_you_have_an_account">您已经有 XMPP 账号了吗?如果之前使用过 Conversations 或其他 XMPP 客户端,那么已经有账号了。如果没有,现在可以创建账号。\n提示:一些邮件服务提供者也提供 XMPP 账号。</string>
+    <string name="server_select_text">XMPP 是独立于提供者的即时通讯网络。您选择的任何 XMPP 服务器都可以使用此应用。\n不过,您可以轻松地在 conversations.im 上创建账号,这是专门适用于 Conversations 的提供者。</string>
+    <string name="magic_create_text_on_x">您已受邀加入 %1$s。我们将指导您创建账号。\n选择 %1$s 作为提供者时,向别人提供您的完整 XMPP 地址,就能和对方交流。</string>
+    <string name="magic_create_text_fixed">您已受邀加入 %1$s。已为您选择了用户名。我们将指导您创建账号。\n向别人提供您的完整 XMPP 地址,就能和对方交流。</string>
     <string name="your_server_invitation">您的服务器邀请</string>
     <string name="improperly_formatted_provisioning">配置代码格式不正确</string>
-    <string name="tap_share_button_send_invite">点击分享按钮,向您的联系人发送加入 %1$s 的邀请。</string>
-    <string name="if_contact_is_nearby_use_qr">如果您的联系人在附近,对方也可以扫描下方二维码接受邀请。</string>
+    <string name="tap_share_button_send_invite">点按分享按钮,向您的联系人发送 %1$s 的邀请。</string>
+    <string name="if_contact_is_nearby_use_qr">如果联系人在附近,也可以扫描下方二维码接受邀请。</string>
     <string name="easy_invite_share_text">加入 %1$s 和我聊天:%2$s</string>
-    <string name="share_invite_with">分享邀请至…</string>
+    <string name="share_invite_with">分享邀请…</string>
 </resources>
  
  
  
    
    @@ -124,14 +124,6 @@
             android:name=".services.ImportBackupService"
             android:exported="false"
             android:foregroundServiceType="dataSync" />
-        <service
-            android:name=".services.ContactChooserTargetService"
-            android:exported="true"
-            android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
-            <intent-filter>
-                <action android:name="android.service.chooser.ChooserTargetService" />
-            </intent-filter>
-        </service>
 
         <service
             android:name=".services.CallIntegrationConnectionService"
@@ -198,7 +190,9 @@
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" />
+            <meta-data
+                android:name="android.app.shortcuts"
+                android:resource="@xml/shortcuts" />
         </activity>
         <activity
             android:name=".ui.ConversationsActivity"
@@ -364,10 +358,9 @@
                 <data android:mimeType="*/*" />
             </intent-filter>
 
-            <!-- the value here needs to be the full class name; independent of the configured applicationId -->
             <meta-data
                 android:name="android.service.chooser.chooser_target_service"
-                android:value="eu.siacs.conversations.services.ContactChooserTargetService" />
+                android:value="androidx.sharetarget.ChooserTargetServiceCompat" />
         </activity>
         <activity
             android:name=".ui.TrustKeysActivity"
@@ -392,8 +385,8 @@
 
         <activity
             android:name=".ui.MediaBrowserActivity"
-            android:label="@string/media_browser"
-            android:exported="false" />
+            android:label="@string/media_browser" />
+        <activity android:name=".ui.AddReactionActivity" />
 
         <provider
             android:name="androidx.core.content.FileProvider"
  
  
  
    
    @@ -3,13 +3,10 @@ package eu.siacs.conversations;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.net.Uri;
-
 import androidx.annotation.BoolRes;
 import androidx.annotation.NonNull;
 import androidx.preference.PreferenceManager;
-
 import com.google.common.base.Strings;
-
 import java.security.SecureRandom;
 
 public class AppSettings {
@@ -46,6 +43,9 @@ public class AppSettings {
     public static final String COLORFUL_CHAT_BUBBLES = "use_green_background";
     public static final String LARGE_FONT = "large_font";
     public static final String SHOW_LINK_PREVIEWS = "show_link_previews";
+    public static final String SHOW_AVATARS = "show_avatars";
+    public static final String CALL_INTEGRATION = "call_integration";
+    public static final String ALIGN_START = "align_start";
 
     private static final String ACCEPT_INVITES_FROM_STRANGERS = "accept_invites_from_strangers";
     private static final String INSTALLATION_ID = "im.conversations.android.install_id";
@@ -113,6 +113,18 @@ public class AppSettings {
         return getBooleanPreference(SHOW_LINK_PREVIEWS, R.bool.show_link_previews);
     }
 
+    public boolean isShowAvatars() {
+        return getBooleanPreference(SHOW_AVATARS, R.bool.show_avatars);
+    }
+
+    public boolean isCallIntegration() {
+        return getBooleanPreference(CALL_INTEGRATION, R.bool.call_integration);
+    }
+
+    public boolean isAlignStart() {
+        return getBooleanPreference(ALIGN_START, R.bool.align_start);
+    }
+
     public boolean isUseTor() {
         return getBooleanPreference(USE_TOR, R.bool.use_tor);
     }
  
  
  
    
    @@ -2,11 +2,9 @@ package eu.siacs.conversations;
 
 import android.graphics.Bitmap;
 import android.net.Uri;
-
 import eu.siacs.conversations.crypto.XmppDomainVerifier;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.chatstate.ChatState;
-
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -82,7 +80,6 @@ public final class Config {
     public static final int CONNECT_DISCO_TIMEOUT = 20;
     public static final int MINI_GRACE_PERIOD = 750;
 
-
     // media file formats. Homogenous Android or Conversations only deployments can switch to opus
     // and webp
     public static final int AVATAR_SIZE = 192;
@@ -95,7 +92,7 @@ public final class Config {
 
     public static final boolean USE_OPUS_VOICE_MESSAGES = true;
 
-    public static final int MESSAGE_MERGE_WINDOW = 20;
+    public static final int MESSAGE_MERGE_WINDOW = 90_000;
 
     public static final int PAGE_SIZE = 50;
     public static final int MAX_NUM_PAGES = 3;
  
  
  
    
    @@ -1,20 +1,15 @@
 package eu.siacs.conversations.crypto.sasl;
 
 import android.util.Log;
-
 import com.google.common.base.CaseFormat;
-import com.google.common.base.Preconditions;
 import com.google.common.base.Predicates;
 import com.google.common.base.Strings;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableBiMap;
-
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.utils.SSLSockets;
-import eu.siacs.conversations.xml.Element;
-import eu.siacs.conversations.xml.Namespace;
-
+import im.conversations.android.xmpp.model.cb.SaslChannelBinding;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -35,20 +30,13 @@ public enum ChannelBinding {
         SHORT_NAMES = builder.build();
     }
 
-    public static Collection<ChannelBinding> of(final Element channelBinding) {
-        Preconditions.checkArgument(
-                channelBinding == null
-                        || ("sasl-channel-binding".equals(channelBinding.getName())
-                                && Namespace.CHANNEL_BINDING.equals(channelBinding.getNamespace())),
-                "pass null or a valid channel binding stream feature");
+    public static Collection<ChannelBinding> of(final SaslChannelBinding channelBinding) {
+        if (channelBinding == null) {
+            return Collections.emptyList();
+        }
         return Collections2.filter(
                 Collections2.transform(
-                        Collections2.filter(
-                                channelBinding == null
-                                        ? Collections.emptyList()
-                                        : channelBinding.getChildren(),
-                                c -> c != null && "channel-binding".equals(c.getName())),
-                        c -> c == null ? null : ChannelBinding.of(c.getAttribute("type"))),
+                        channelBinding.getChannelBindings(), cb -> ChannelBinding.of(cb.getType())),
                 Predicates.notNull());
     }
 
  
  
  
    
    @@ -0,0 +1,98 @@
+package eu.siacs.conversations.crypto.sasl;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Ordering;
+import java.util.Collection;
+
+public class DowngradeProtection {
+
+    private static final char SEPARATOR = ',';
+    private static final char SEPARATOR_MECHANISM_AND_BINDING = '|';
+
+    public final ImmutableList<String> mechanisms;
+    public final ImmutableList<String> channelBindings;
+
+    public DowngradeProtection(
+            final Collection<String> mechanisms, final Collection<String> channelBindings) {
+        this.mechanisms = Ordering.natural().immutableSortedCopy(mechanisms);
+        this.channelBindings = Ordering.natural().immutableSortedCopy(channelBindings);
+    }
+
+    public DowngradeProtection(final Collection<String> mechanisms) {
+        this.mechanisms = Ordering.natural().immutableSortedCopy(mechanisms);
+        this.channelBindings = null;
+    }
+
+    public String asDString() {
+        ensureSaslMechanismFormat(this.mechanisms);
+        ensureNoSeparators(this.mechanisms);
+        if (this.channelBindings != null) {
+            ensureNoSeparators(this.channelBindings);
+            ensureBindingFormat(this.channelBindings);
+            final var builder = new StringBuilder();
+            Joiner.on(SEPARATOR).appendTo(builder, mechanisms);
+            builder.append(SEPARATOR_MECHANISM_AND_BINDING);
+            Joiner.on(SEPARATOR).appendTo(builder, channelBindings);
+            return builder.toString();
+        } else {
+            return Joiner.on(SEPARATOR).join(mechanisms);
+        }
+    }
+
+    private static void ensureNoSeparators(final Iterable<String> list) {
+        for (final String item : list) {
+            if (item.indexOf(SEPARATOR) >= 0
+                    || item.indexOf(SEPARATOR_MECHANISM_AND_BINDING) >= 0) {
+                throw new SecurityException("illegal chars found in list");
+            }
+        }
+    }
+
+    private static void ensureSaslMechanismFormat(final Iterable<String> names) {
+        for (final String name : names) {
+            ensureSaslMechanismFormat(name);
+        }
+    }
+
+    private static void ensureSaslMechanismFormat(final String name) {
+        if (Strings.isNullOrEmpty(name)) {
+            throw new SecurityException("Empty sasl mechanism names are not permitted");
+        }
+        // https://www.rfc-editor.org/rfc/rfc4422.html#section-3.1
+        if (name.length() <= 20
+                && CharMatcher.inRange('A', 'Z')
+                        .or(CharMatcher.inRange('0', '9'))
+                        .or(CharMatcher.is('-'))
+                        .or(CharMatcher.is('_'))
+                        .matchesAllOf(name)
+                && !Character.isDigit(name.charAt(0))) {
+            return;
+        }
+        throw new SecurityException("Encountered illegal sasl name");
+    }
+
+    private static void ensureBindingFormat(final Iterable<String> names) {
+        for (final String name : names) {
+            ensureBindingFormat(name);
+        }
+    }
+
+    private static void ensureBindingFormat(final String name) {
+        if (Strings.isNullOrEmpty(name)) {
+            throw new SecurityException("Empty binding names are not permitted");
+        }
+        // https://www.rfc-editor.org/rfc/rfc5056.html#section-7d
+        if (CharMatcher.inRange('A', 'Z')
+                .or(CharMatcher.inRange('a', 'z'))
+                .or(CharMatcher.inRange('0', '9'))
+                .or(CharMatcher.is('.'))
+                .or(CharMatcher.is('-'))
+                .matchesAllOf(name)) {
+            return;
+        }
+        throw new SecurityException("Encountered illegal binding name");
+    }
+}
  
  
  
    
    @@ -1,20 +1,15 @@
 package eu.siacs.conversations.crypto.sasl;
 
 import android.util.Log;
-
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
-import com.google.common.collect.Collections2;
-
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.utils.SSLSockets;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
-
 import java.util.Collection;
 import java.util.Collections;
-
 import javax.net.ssl.SSLSocket;
 
 public abstract class SaslMechanism {
@@ -53,18 +48,7 @@ public abstract class SaslMechanism {
         return "";
     }
 
-    public static Collection<String> mechanisms(final Element authElement) {
-        if (authElement == null) {
-            return Collections.emptyList();
-        }
-        return Collections2.transform(
-                Collections2.filter(
-                        authElement.getChildren(),
-                        c -> c != null && "mechanism".equals(c.getName())),
-                c -> c == null ? null : c.getContent());
-    }
-
-    protected enum State {
+    public enum State {
         INITIAL,
         AUTH_TEXT_SENT,
         RESPONSE_SENT,
@@ -76,14 +60,11 @@ public abstract class SaslMechanism {
         SASL_2;
 
         public static Version of(final Element element) {
-            switch (Strings.nullToEmpty(element.getNamespace())) {
-                case Namespace.SASL:
-                    return SASL;
-                case Namespace.SASL_2:
-                    return SASL_2;
-                default:
-                    throw new IllegalArgumentException("Unrecognized SASL namespace");
-            }
+            return switch (Strings.nullToEmpty(element.getNamespace())) {
+                case Namespace.SASL -> SASL;
+                case Namespace.SASL_2 -> SASL_2;
+                default -> throw new IllegalArgumentException("Unrecognized SASL namespace");
+            };
         }
     }
 
  
  
  
    
    @@ -1,24 +1,27 @@
 package eu.siacs.conversations.crypto.sasl;
 
-import android.util.Base64;
-
 import com.google.common.base.CaseFormat;
+import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.hash.HashFunction;
-
-import java.nio.charset.Charset;
+import com.google.common.io.BaseEncoding;
+import com.google.common.primitives.Ints;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.utils.CryptoHelper;
 import java.security.InvalidKeyException;
+import java.util.Arrays;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
-
 import javax.crypto.SecretKey;
 import javax.net.ssl.SSLSocket;
 
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.utils.CryptoHelper;
-
-abstract class ScramMechanism extends SaslMechanism {
+public abstract class ScramMechanism extends SaslMechanism {
 
     public static final SecretKey EMPTY_KEY =
             new SecretKey() {
@@ -46,8 +49,9 @@ abstract class ScramMechanism extends SaslMechanism {
     private final String gs2Header;
     private final String clientNonce;
     protected State state = State.INITIAL;
-    private String clientFirstMessageBare;
+    private final String clientFirstMessageBare;
     private byte[] serverSignature = null;
+    private DowngradeProtection downgradeProtection = null;
 
     ScramMechanism(final Account account, final ChannelBinding channelBinding) {
         super(account);
@@ -67,28 +71,41 @@ abstract class ScramMechanism extends SaslMechanism {
         }
         // This nonce should be different for each authentication attempt.
         this.clientNonce = CryptoHelper.random(100);
-        clientFirstMessageBare = "";
+        this.clientFirstMessageBare =
+                String.format(
+                        "n=%s,r=%s",
+                        CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername())),
+                        this.clientNonce);
+    }
+
+    public void setDowngradeProtection(final DowngradeProtection downgradeProtection) {
+        Preconditions.checkState(
+                this.state == State.INITIAL, "setting downgrade protection in invalid state");
+        this.downgradeProtection = downgradeProtection;
     }
 
     protected abstract HashFunction getHMac(final byte[] key);
 
     protected abstract HashFunction getDigest();
 
-    private KeyPair getKeyPair(final String password, final String salt, final int iterations)
+    private KeyPair getKeyPair(final String password, final byte[] salt, final int iterations)
             throws ExecutionException {
-        return CACHE.get(
-                new CacheKey(getMechanism(), password, salt, iterations),
-                () -> {
-                    final byte[] saltedPassword, serverKey, clientKey;
-                    saltedPassword =
-                            hi(
-                                    password.getBytes(),
-                                    Base64.decode(salt, Base64.DEFAULT),
-                                    iterations);
-                    serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
-                    clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
-                    return new KeyPair(clientKey, serverKey);
-                });
+        final var key = new CacheKey(getMechanism(), password, salt, iterations);
+        return CACHE.get(key, () -> calculateKeyPair(password, salt, iterations));
+    }
+
+    private KeyPair calculateKeyPair(final String password, final byte[] salt, final int iterations)
+            throws InvalidKeyException {
+        final byte[] saltedPassword, serverKey, clientKey;
+        saltedPassword = hi(password.getBytes(), salt, iterations);
+        serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
+        clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
+        return new KeyPair(clientKey, serverKey);
+    }
+
+    @Override
+    public String getMechanism() {
+        return "";
     }
 
     private byte[] hmac(final byte[] key, final byte[] input) throws InvalidKeyException {
@@ -119,152 +136,167 @@ abstract class ScramMechanism extends SaslMechanism {
 
     @Override
     public String getClientFirstMessage(final SSLSocket sslSocket) {
-        if (clientFirstMessageBare.isEmpty() && state == State.INITIAL) {
-            clientFirstMessageBare =
-                    "n="
-                            + CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername()))
-                            + ",r="
-                            + this.clientNonce;
-            state = State.AUTH_TEXT_SENT;
-        }
-        return Base64.encodeToString(
-                (gs2Header + clientFirstMessageBare).getBytes(Charset.defaultCharset()),
-                Base64.NO_WRAP);
+        Preconditions.checkState(
+                this.state == State.INITIAL, "Calling getClientFirstMessage from invalid state");
+        this.state = State.AUTH_TEXT_SENT;
+        final byte[] message = (gs2Header + clientFirstMessageBare).getBytes();
+        return BaseEncoding.base64().encode(message);
     }
 
     @Override
     public String getResponse(final String challenge, final SSLSocket socket)
             throws AuthenticationException {
-        switch (state) {
-            case AUTH_TEXT_SENT:
-                if (challenge == null) {
-                    throw new AuthenticationException("challenge can not be null");
-                }
-                byte[] serverFirstMessage;
-                try {
-                    serverFirstMessage = Base64.decode(challenge, Base64.DEFAULT);
-                } catch (IllegalArgumentException e) {
-                    throw new AuthenticationException("Unable to decode server challenge", e);
-                }
-                final Tokenizer tokenizer = new Tokenizer(serverFirstMessage);
-                String nonce = "";
-                int iterationCount = -1;
-                String salt = "";
-                for (final String token : tokenizer) {
-                    if (token.length() > 1 && token.charAt(1) == '=') {
-                        switch (token.charAt(0)) {
-                            case 'i':
-                                try {
-                                    iterationCount = Integer.parseInt(token.substring(2));
-                                } catch (final NumberFormatException e) {
-                                    throw new AuthenticationException(e);
-                                }
-                                break;
-                            case 's':
-                                salt = token.substring(2);
-                                break;
-                            case 'r':
-                                nonce = token.substring(2);
-                                break;
-                            case 'm':
-                                /*
-                                 * RFC 5802:
-                                 * m: This attribute is reserved for future extensibility.  In this
-                                 * version of SCRAM, its presence in a client or a server message
-                                 * MUST cause authentication failure when the attribute is parsed by
-                                 * the other end.
-                                 */
-                                throw new AuthenticationException(
-                                        "Server sent reserved token: `m'");
-                        }
-                    }
-                }
+        return switch (state) {
+            case AUTH_TEXT_SENT -> processServerFirstMessage(challenge, socket);
+            case RESPONSE_SENT -> processServerFinalMessage(challenge);
+            default -> throw new InvalidStateException(state);
+        };
+    }
 
-                if (iterationCount < 0) {
-                    throw new AuthenticationException("Server did not send iteration count");
-                }
-                if (nonce.isEmpty() || !nonce.startsWith(clientNonce)) {
-                    throw new AuthenticationException(
-                            "Server nonce does not contain client nonce: " + nonce);
-                }
-                if (salt.isEmpty()) {
-                    throw new AuthenticationException("Server sent empty salt");
-                }
+    private String processServerFirstMessage(final String challenge, final SSLSocket socket)
+            throws AuthenticationException {
+        if (Strings.isNullOrEmpty(challenge)) {
+            throw new AuthenticationException("challenge can not be null");
+        }
+        byte[] serverFirstMessage;
+        try {
+            serverFirstMessage = BaseEncoding.base64().decode(challenge);
+        } catch (final IllegalArgumentException e) {
+            throw new AuthenticationException("Unable to decode server challenge", e);
+        }
+        final Map<String, String> attributes;
+        try {
+            attributes = splitToAttributes(new String(serverFirstMessage));
+        } catch (final IllegalArgumentException e) {
+            throw new AuthenticationException("Duplicate attributes");
+        }
+        if (attributes.containsKey("m")) {
+            /*
+             * RFC 5802:
+             * m: This attribute is reserved for future extensibility.  In this
+             * version of SCRAM, its presence in a client or a server message
+             * MUST cause authentication failure when the attribute is parsed by
+             * the other end.
+             */
+            throw new AuthenticationException("Server sent reserved token: 'm'");
+        }
+        final String i = attributes.get("i");
+        final String s = attributes.get("s");
+        final String nonce = attributes.get("r");
+        final String d = attributes.get("d");
+        if (Strings.isNullOrEmpty(s) || Strings.isNullOrEmpty(nonce) || Strings.isNullOrEmpty(i)) {
+            throw new AuthenticationException("Missing attributes from server first message");
+        }
+        final Integer iterationCount = Ints.tryParse(i);
 
-                final byte[] channelBindingData = getChannelBindingData(socket);
-
-                final int gs2Len = this.gs2Header.getBytes().length;
-                final byte[] cMessage = new byte[gs2Len + channelBindingData.length];
-                System.arraycopy(this.gs2Header.getBytes(), 0, cMessage, 0, gs2Len);
-                System.arraycopy(
-                        channelBindingData, 0, cMessage, gs2Len, channelBindingData.length);
-
-                final String clientFinalMessageWithoutProof =
-                        "c=" + Base64.encodeToString(cMessage, Base64.NO_WRAP) + ",r=" + nonce;
-
-                final byte[] authMessage =
-                        (clientFirstMessageBare
-                                        + ','
-                                        + new String(serverFirstMessage)
-                                        + ','
-                                        + clientFinalMessageWithoutProof)
-                                .getBytes();
-
-                final KeyPair keys;
-                try {
-                    keys =
-                            getKeyPair(
-                                    CryptoHelper.saslPrep(account.getPassword()),
-                                    salt,
-                                    iterationCount);
-                } catch (ExecutionException e) {
-                    throw new AuthenticationException("Invalid keys generated");
-                }
-                final byte[] clientSignature;
-                try {
-                    serverSignature = hmac(keys.serverKey, authMessage);
-                    final byte[] storedKey = digest(keys.clientKey);
+        if (iterationCount == null || iterationCount < 0) {
+            throw new AuthenticationException("Server did not send iteration count");
+        }
+        if (!nonce.startsWith(clientNonce)) {
+            throw new AuthenticationException(
+                    "Server nonce does not contain client nonce: " + nonce);
+        }
 
-                    clientSignature = hmac(storedKey, authMessage);
+        final byte[] salt;
 
-                } catch (final InvalidKeyException e) {
-                    throw new AuthenticationException(e);
-                }
+        try {
+            salt = BaseEncoding.base64().decode(s);
+        } catch (final IllegalArgumentException e) {
+            throw new AuthenticationException("Invalid salt in server first message");
+        }
 
-                final byte[] clientProof = new byte[keys.clientKey.length];
+        if (d != null && this.downgradeProtection != null) {
+            final String asSeenInFeatures;
+            try {
+                asSeenInFeatures = downgradeProtection.asDString();
+            } catch (final SecurityException e) {
+                throw new AuthenticationException(e);
+            }
+            final var hashed = BaseEncoding.base64().encode(digest(asSeenInFeatures.getBytes()));
+            if (!hashed.equals(d)) {
+                throw new AuthenticationException("Mismatch in SSDP");
+            }
+        }
 
-                if (clientSignature.length < keys.clientKey.length) {
-                    throw new AuthenticationException(
-                            "client signature was shorter than clientKey");
-                }
+        final byte[] channelBindingData = getChannelBindingData(socket);
 
-                for (int i = 0; i < clientProof.length; i++) {
-                    clientProof[i] = (byte) (keys.clientKey[i] ^ clientSignature[i]);
-                }
+        final int gs2Len = this.gs2Header.getBytes().length;
+        final byte[] cMessage = new byte[gs2Len + channelBindingData.length];
+        System.arraycopy(this.gs2Header.getBytes(), 0, cMessage, 0, gs2Len);
+        System.arraycopy(channelBindingData, 0, cMessage, gs2Len, channelBindingData.length);
 
-                final String clientFinalMessage =
-                        clientFinalMessageWithoutProof
-                                + ",p="
-                                + Base64.encodeToString(clientProof, Base64.NO_WRAP);
-                state = State.RESPONSE_SENT;
-                return Base64.encodeToString(clientFinalMessage.getBytes(), Base64.NO_WRAP);
-            case RESPONSE_SENT:
-                try {
-                    final String clientCalculatedServerFinalMessage =
-                            "v=" + Base64.encodeToString(serverSignature, Base64.NO_WRAP);
-                    if (!clientCalculatedServerFinalMessage.equals(
-                            new String(Base64.decode(challenge, Base64.DEFAULT)))) {
-                        throw new Exception();
-                    }
-                    state = State.VALID_SERVER_RESPONSE;
-                    return "";
-                } catch (Exception e) {
-                    throw new AuthenticationException(
-                            "Server final message does not match calculated final message");
-                }
-            default:
-                throw new InvalidStateException(state);
+        final String clientFinalMessageWithoutProof =
+                String.format("c=%s,r=%s", BaseEncoding.base64().encode(cMessage), nonce);
+
+        final var authMessage =
+                Joiner.on(',')
+                        .join(
+                                clientFirstMessageBare,
+                                new String(serverFirstMessage),
+                                clientFinalMessageWithoutProof);
+
+        final KeyPair keys;
+        try {
+            keys = getKeyPair(CryptoHelper.saslPrep(account.getPassword()), salt, iterationCount);
+        } catch (final ExecutionException e) {
+            throw new AuthenticationException("Invalid keys generated");
         }
+        final byte[] clientSignature;
+        try {
+            serverSignature = hmac(keys.serverKey, authMessage.getBytes());
+            final byte[] storedKey = digest(keys.clientKey);
+
+            clientSignature = hmac(storedKey, authMessage.getBytes());
+
+        } catch (final InvalidKeyException e) {
+            throw new AuthenticationException(e);
+        }
+
+        final byte[] clientProof = new byte[keys.clientKey.length];
+
+        if (clientSignature.length < keys.clientKey.length) {
+            throw new AuthenticationException("client signature was shorter than clientKey");
+        }
+
+        for (int j = 0; j < clientProof.length; j++) {
+            clientProof[j] = (byte) (keys.clientKey[j] ^ clientSignature[j]);
+        }
+
+        final var clientFinalMessage =
+                String.format(
+                        "%s,p=%s",
+                        clientFinalMessageWithoutProof, BaseEncoding.base64().encode(clientProof));
+        this.state = State.RESPONSE_SENT;
+        return BaseEncoding.base64().encode(clientFinalMessage.getBytes());
+    }
+
+    private Map<String, String> splitToAttributes(final String message) {
+        final ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
+        for (final String token : Splitter.on(',').split(message)) {
+            final var tuple = Splitter.on('=').limit(2).splitToList(token);
+            if (tuple.size() == 2) {
+                builder.put(tuple.get(0), tuple.get(1));
+            }
+        }
+        return builder.buildOrThrow();
+    }
+
+    private String processServerFinalMessage(final String challenge)
+            throws AuthenticationException {
+        final String serverFinalMessage;
+        try {
+            serverFinalMessage = new String(BaseEncoding.base64().decode(challenge));
+        } catch (final IllegalArgumentException e) {
+            throw new AuthenticationException("Invalid base64 in server final message", e);
+        }
+        final var clientCalculatedServerFinalMessage =
+                String.format("v=%s", BaseEncoding.base64().encode(serverSignature));
+        if (clientCalculatedServerFinalMessage.equals(serverFinalMessage)) {
+            this.state = State.VALID_SERVER_RESPONSE;
+            return "";
+        }
+        throw new AuthenticationException(
+                "Server final message does not match calculated final message");
     }
 
     protected byte[] getChannelBindingData(final SSLSocket sslSocket)
@@ -276,12 +308,16 @@ abstract class ScramMechanism extends SaslMechanism {
     }
 
     private static class CacheKey {
-        final String algorithm;
-        final String password;
-        final String salt;
-        final int iterations;
-
-        private CacheKey(String algorithm, String password, String salt, int iterations) {
+        private final String algorithm;
+        private final String password;
+        private final byte[] salt;
+        private final int iterations;
+
+        private CacheKey(
+                final String algorithm,
+                final String password,
+                final byte[] salt,
+                final int iterations) {
             this.algorithm = algorithm;
             this.password = password;
             this.salt = salt;
@@ -289,19 +325,20 @@ abstract class ScramMechanism extends SaslMechanism {
         }
 
         @Override
-        public boolean equals(Object o) {
+        public boolean equals(final Object o) {
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
             CacheKey cacheKey = (CacheKey) o;
             return iterations == cacheKey.iterations
                     && Objects.equal(algorithm, cacheKey.algorithm)
                     && Objects.equal(password, cacheKey.password)
-                    && Objects.equal(salt, cacheKey.salt);
+                    && Arrays.equals(salt, cacheKey.salt);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hashCode(algorithm, password, salt, iterations);
+            final int result = Objects.hashCode(algorithm, password, iterations);
+            return 31 * result + Arrays.hashCode(salt);
         }
     }
 
  
  
  
    
    @@ -7,6 +7,7 @@ import android.os.SystemClock;
 import android.util.Log;
 
 import androidx.core.graphics.ColorUtils;
+import androidx.annotation.NonNull;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
@@ -340,11 +341,12 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
         this.password = password;
     }
 
+    @NonNull
     public String getHostname() {
         return Strings.nullToEmpty(this.hostname);
     }
 
-    public void setHostname(String hostname) {
+    public void setHostname(final String hostname) {
         this.hostname = hostname;
     }
 
@@ -432,7 +434,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
     }
 
     public HashedToken getFastMechanism() {
-        final HashedToken.Mechanism fastMechanism = HashedToken.Mechanism.ofOrNull(this.fastMechanism);
+        final HashedToken.Mechanism fastMechanism =
+                HashedToken.Mechanism.ofOrNull(this.fastMechanism);
         final String token = this.fastToken;
         if (fastMechanism == null || Strings.isNullOrEmpty(token)) {
             return null;
@@ -841,11 +844,12 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
 
     public enum State {
         DISABLED(false, false),
-        LOGGED_OUT(false,false),
+        LOGGED_OUT(false, false),
         OFFLINE(false),
         CONNECTING(false),
         ONLINE(false),
         NO_INTERNET(false),
+        CONNECTION_TIMEOUT,
         UNAUTHORIZED,
         TEMPORARY_AUTH_FAILURE,
         SERVER_NOT_FOUND,
@@ -915,6 +919,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
                     return R.string.account_status_not_found;
                 case NO_INTERNET:
                     return R.string.account_status_no_internet;
+                case CONNECTION_TIMEOUT:
+                    return R.string.account_status_connection_timeout;
                 case REGISTRATION_FAILED:
                     return R.string.account_status_regis_fail;
                 case REGISTRATION_WEB:
  
  
  
    
    @@ -237,7 +237,7 @@ public class Bookmark extends Element implements ListItem {
 	}
 
 	public String getNick() {
-		return this.findChildContent("nick");
+		return Strings.emptyToNull(this.findChildContent("nick"));
 	}
 
 	public void setNick(String nick) {
@@ -315,7 +315,6 @@ public class Bookmark extends Element implements ListItem {
 			this.conversation = null;
 		} else {
 			this.conversation = new WeakReference<>(conversation);
-			conversation.getMucOptions().notifyOfBookmarkNick(getNick());
 		}
 	}
 
  
  
  
    
    @@ -32,7 +32,6 @@ import java.util.Objects;
 
 import eu.siacs.conversations.BuildConfig;
 import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
 import eu.siacs.conversations.android.AbstractPhoneContact;
 import eu.siacs.conversations.android.JabberIdContact;
 import eu.siacs.conversations.persistance.FileBackend;
@@ -639,7 +638,7 @@ public class Contact implements ListItem, Blockable {
     public synchronized boolean unsetPhoneContact(Class<? extends AbstractPhoneContact> clazz) {
         resetOption(getOption(clazz));
         boolean changed = false;
-        if (!getOption(Options.SYNCED_VIA_ADDRESSBOOK) && !getOption(Options.SYNCED_VIA_OTHER)) {
+        if (!getOption(Options.SYNCED_VIA_ADDRESS_BOOK) && !getOption(Options.SYNCED_VIA_OTHER)) {
             setSystemAccount(null);
             changed |= setPhotoUri(null);
             changed |= setSystemName(null);
@@ -713,7 +712,7 @@ public class Contact implements ListItem, Blockable {
 
     public static int getOption(Class<? extends AbstractPhoneContact> clazz) {
         if (clazz == JabberIdContact.class) {
-            return Options.SYNCED_VIA_ADDRESSBOOK;
+            return Options.SYNCED_VIA_ADDRESS_BOOK;
         } else {
             return Options.SYNCED_VIA_OTHER;
         }
@@ -756,7 +755,7 @@ public class Contact implements ListItem, Blockable {
         public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
         public static final int DIRTY_PUSH = 6;
         public static final int DIRTY_DELETE = 7;
-        private static final int SYNCED_VIA_ADDRESSBOOK = 8;
+        private static final int SYNCED_VIA_ADDRESS_BOOK = 8;
         public static final int SYNCED_VIA_OTHER = 9;
     }
 }
  
  
  
    
    @@ -1,5 +1,7 @@
 package eu.siacs.conversations.entities;
 
+import static eu.siacs.conversations.entities.Bookmark.printableValue;
+
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -75,11 +77,12 @@ import com.google.android.material.color.MaterialColors;
 import com.google.android.material.tabs.TabLayout;
 import com.google.android.material.textfield.TextInputLayout;
 import com.google.common.base.Optional;
+import com.google.common.base.Strings;
 import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
-import com.google.common.collect.HashMultimap;
 
 import io.ipfs.cid.Cid;
 
@@ -156,12 +159,21 @@ import eu.siacs.conversations.xmpp.chatstate.ChatState;
 import eu.siacs.conversations.xmpp.forms.Data;
 import eu.siacs.conversations.xmpp.forms.Option;
 import eu.siacs.conversations.xmpp.mam.MamReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
 
 import static eu.siacs.conversations.entities.Bookmark.printableValue;
 
 import im.conversations.android.xmpp.model.stanza.Iq;
 
-public class Conversation extends AbstractEntity implements Blockable, Comparable<Conversation>, Conversational, AvatarService.Avatarable {
+public class Conversation extends AbstractEntity
+        implements Blockable, Comparable<Conversation>, Conversational, AvatarService.Avatarable {
     public static final String TABLENAME = "conversations";
 
     public static final int STATUS_AVAILABLE = 0;
@@ -180,7 +192,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
     public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify";
     public static final String ATTRIBUTE_NOTIFY_REPLIES = "notify_replies";
     public static final String ATTRIBUTE_LAST_CLEAR_HISTORY = "last_clear_history";
-    public static final String ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS = "formerly_private_non_anonymous";
+    public static final String ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS =
+            "formerly_private_non_anonymous";
     public static final String ATTRIBUTE_PINNED_ON_TOP = "pinned_on_top";
     static final String ATTRIBUTE_MUC_PASSWORD = "muc_password";
     static final String ATTRIBUTE_MEMBERS_ONLY = "members_only";
@@ -202,7 +215,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
     private int status;
     private final long created;
     private int mode;
-    private JSONObject attributes;
+    private final JSONObject attributes;
     private Jid nextCounterpart;
     private transient MucOptions mucOptions = null;
     private boolean messagesLeftOnServer = true;
@@ -220,17 +233,31 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
     private String displayState = null;
     protected boolean anyMatchSpam = false;
 
-    public Conversation(final String name, final Account account, final Jid contactJid,
-                        final int mode) {
-        this(java.util.UUID.randomUUID().toString(), name, null, account
-                        .getUuid(), contactJid, System.currentTimeMillis(),
-                STATUS_AVAILABLE, mode, "");
+    public Conversation(
+            final String name, final Account account, final Jid contactJid, final int mode) {
+        this(
+                java.util.UUID.randomUUID().toString(),
+                name,
+                null,
+                account.getUuid(),
+                contactJid,
+                System.currentTimeMillis(),
+                STATUS_AVAILABLE,
+                mode,
+                "");
         this.account = account;
     }
 
-    public Conversation(final String uuid, final String name, final String contactUuid,
-                        final String accountUuid, final Jid contactJid, final long created, final int status,
-                        final int mode, final String attributes) {
+    public Conversation(
+            final String uuid,
+            final String name,
+            final String contactUuid,
+            final String accountUuid,
+            final Jid contactJid,
+            final long created,
+            final int status,
+            final int mode,
+            final String attributes) {
         this.uuid = uuid;
         this.name = name;
         this.contactUuid = contactUuid;
@@ -239,26 +266,37 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         this.created = created;
         this.status = status;
         this.mode = mode;
-        try {
-            this.attributes = new JSONObject(attributes == null ? "" : attributes);
-        } catch (JSONException e) {
-            this.attributes = new JSONObject();
+        this.attributes = parseAttributes(attributes);
+    }
+
+    private static JSONObject parseAttributes(final String attributes) {
+        if (Strings.isNullOrEmpty(attributes)) {
+            return new JSONObject();
+        } else {
+            try {
+                return new JSONObject(attributes);
+            } catch (final JSONException e) {
+                return new JSONObject();
+            }
         }
     }
 
-    public static Conversation fromCursor(Cursor cursor) {
-        return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)),
-                cursor.getString(cursor.getColumnIndex(NAME)),
-                cursor.getString(cursor.getColumnIndex(CONTACT)),
-                cursor.getString(cursor.getColumnIndex(ACCOUNT)),
-                JidHelper.parseOrFallbackToInvalid(cursor.getString(cursor.getColumnIndex(CONTACTJID))),
-                cursor.getLong(cursor.getColumnIndex(CREATED)),
-                cursor.getInt(cursor.getColumnIndex(STATUS)),
-                cursor.getInt(cursor.getColumnIndex(MODE)),
-                cursor.getString(cursor.getColumnIndex(ATTRIBUTES)));
+    public static Conversation fromCursor(final Cursor cursor) {
+        return new Conversation(
+                cursor.getString(cursor.getColumnIndexOrThrow(UUID)),
+                cursor.getString(cursor.getColumnIndexOrThrow(NAME)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CONTACT)),
+                cursor.getString(cursor.getColumnIndexOrThrow(ACCOUNT)),
+                JidHelper.parseOrFallbackToInvalid(
+                        cursor.getString(cursor.getColumnIndexOrThrow(CONTACTJID))),
+                cursor.getLong(cursor.getColumnIndexOrThrow(CREATED)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(MODE)),
+                cursor.getString(cursor.getColumnIndexOrThrow(ATTRIBUTES)));
     }
 
-    public static Message getLatestMarkableMessage(final List<Message> messages, boolean isPrivateAndNonAnonymousMuc) {
+    public static Message getLatestMarkableMessage(
+            final List<Message> messages, boolean isPrivateAndNonAnonymousMuc) {
         for (int i = messages.size() - 1; i >= 0; --i) {
             final Message message = messages.get(i);
             if (message.getStatus() <= Message.STATUS_RECEIVED
@@ -279,10 +317,13 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         }
         final String contact = conversation.getJid().getDomain().toEscapedString();
         final String account = conversation.getAccount().getServer();
-        if (Config.OMEMO_EXCEPTIONS.matchesContactDomain(contact) || Config.OMEMO_EXCEPTIONS.ACCOUNT_DOMAINS.contains(account)) {
+        if (Config.OMEMO_EXCEPTIONS.matchesContactDomain(contact)
+                || Config.OMEMO_EXCEPTIONS.ACCOUNT_DOMAINS.contains(account)) {
             return false;
         }
-        return conversation.isSingleOrPrivateAndNonAnonymous() || conversation.getBooleanAttribute(ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS, false);
+        return conversation.isSingleOrPrivateAndNonAnonymous()
+                || conversation.getBooleanAttribute(
+                        ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS, false);
     }
 
     public boolean hasMessagesLeftOnServer() {
@@ -334,7 +375,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
     public int countFailedDeliveries() {
         int count = 0;
         synchronized (this.messages) {
-            for(final Message message : this.messages) {
+            for (final Message message : this.messages) {
                 if (message.getStatus() == Message.STATUS_SEND_FAILED) {
                     ++count;
                 }
@@ -358,12 +399,12 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         return null;
     }
 
-
     public Message findUnsentMessageWithUuid(String uuid) {
         synchronized (this.messages) {
             for (final Message message : this.messages) {
                 final int s = message.getStatus();
-                if ((s == Message.STATUS_UNSEND || s == Message.STATUS_WAITING) && message.getUuid().equals(uuid)) {
+                if ((s == Message.STATUS_UNSEND || s == Message.STATUS_WAITING)
+                        && message.getUuid().equals(uuid)) {
                     return message;
                 }
             }
@@ -404,10 +445,16 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         synchronized (this.messages) {
             for (final Message message : this.messages) {
                 final Transferable transferable = message.getTransferable();
-                final boolean unInitiatedButKnownSize = MessageUtils.unInitiatedButKnownSize(message);
+                final boolean unInitiatedButKnownSize =
+                        MessageUtils.unInitiatedButKnownSize(message);
                 if (message.getUuid().equals(uuid)
                         && message.getEncryption() != Message.ENCRYPTION_PGP
-                        && (message.isFileOrImage() || message.treatAsDownloadable() || unInitiatedButKnownSize || (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING))) {
+                        && (message.isFileOrImage()
+                                || message.treatAsDownloadable()
+                                || unInitiatedButKnownSize
+                                || (transferable != null
+                                        && transferable.getStatus()
+                                                != Transferable.STATUS_UPLOADING))) {
                     return message;
                 }
             }
@@ -434,7 +481,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                 if (uuids.contains(message.getUuid())) {
                     message.setDeleted(true);
                     deleted = true;
-                    if (message.getEncryption() == Message.ENCRYPTION_PGP && pgpDecryptionService != null) {
+                    if (message.getEncryption() == Message.ENCRYPTION_PGP
+                            && pgpDecryptionService != null) {
                         pgpDecryptionService.discard(message);
                     }
                 }
@@ -452,7 +500,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                     if (file.uuid.toString().equals(message.getUuid())) {
                         message.setDeleted(file.deleted);
                         changed = true;
-                        if (file.deleted && message.getEncryption() == Message.ENCRYPTION_PGP && pgpDecryptionService != null) {
+                        if (file.deleted
+                                && message.getEncryption() == Message.ENCRYPTION_PGP
+                                && pgpDecryptionService != null) {
                             pgpDecryptionService.discard(message);
                         }
                     }
@@ -480,7 +530,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
     }
 
     public boolean setOutgoingChatState(ChatState state) {
-        if (mode == MODE_SINGLE && !getContact().isSelf() || (isPrivateAndNonAnonymous() && getNextCounterpart() == null)) {
+        if (mode == MODE_SINGLE && !getContact().isSelf()
+                || (isPrivateAndNonAnonymous() && getNextCounterpart() == null)) {
             if (this.mOutgoingChatState != state) {
                 this.mOutgoingChatState = state;
                 return true;
@@ -513,7 +564,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         final ArrayList<Message> results = new ArrayList<>();
         synchronized (this.messages) {
             for (Message message : this.messages) {
-                if ((message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost()) && message.getStatus() == Message.STATUS_UNSEND) {
+                if ((message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost())
+                        && message.getStatus() == Message.STATUS_UNSEND) {
                     results.add(message);
                 }
             }
@@ -528,7 +580,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
             for (Message message : this.messages) {
                 if (id.equals(message.getUuid())
                         || (message.getStatus() >= Message.STATUS_SEND
-                        && id.equals(message.getRemoteMsgId()))) {
+                                && id.equals(message.getRemoteMsgId()))) {
                     return message;
                 }
             }
@@ -556,7 +608,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                     continue;
                 }
                 if (counterpart == null || mcp.equals(counterpart) || mcp.asBareJid().equals(counterpart)) {
-                    final boolean idMatch = id.equals(message.getUuid()) || id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id) || (getMode() == MODE_MULTI && id.equals(message.getServerMsgId()));
+                    final boolean idMatch = id.equals(message.getUuid()) || id.equals(message.getRemoteMsgId()) || (getMode() == MODE_MULTI && id.equals(message.getServerMsgId()));
                     if (idMatch) return message;
                 }
             }
@@ -590,7 +642,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
     public Message findReceivedWithRemoteId(final String id) {
         synchronized (this.messages) {
             for (final Message message : this.messages) {
-                if (message.getStatus() == Message.STATUS_RECEIVED && id.equals(message.getRemoteMsgId())) {
+                if (message.getStatus() == Message.STATUS_RECEIVED
+                        && id.equals(message.getRemoteMsgId())) {
                     return message;
                 }
             }
@@ -861,7 +914,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
     }
 
     public boolean setCorrectingMessage(Message correctingMessage) {
-        setAttribute(ATTRIBUTE_CORRECTING_MESSAGE, correctingMessage == null ? null : correctingMessage.getUuid());
+        setAttribute(
+                ATTRIBUTE_CORRECTING_MESSAGE,
+                correctingMessage == null ? null : correctingMessage.getUuid());
         return correctingMessage == null && draftMessage != null;
     }
 
@@ -877,7 +932,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
     @Override
     public int compareTo(@NonNull Conversation another) {
         return ComparisonChain.start()
-                .compareFalseFirst(another.getBooleanAttribute(ATTRIBUTE_PINNED_ON_TOP, false), getBooleanAttribute(ATTRIBUTE_PINNED_ON_TOP, false))
+                .compareFalseFirst(
+                        another.getBooleanAttribute(ATTRIBUTE_PINNED_ON_TOP, false),
+                        getBooleanAttribute(ATTRIBUTE_PINNED_ON_TOP, false))
                 .compare(another.getSortableTime(), getSortableTime())
                 .result();
     }
@@ -979,8 +1036,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         return message;
     }
 
-    public @NonNull
-    CharSequence getName() {
+    public @NonNull CharSequence getName() {
         if (getMode() == MODE_MULTI) {
             final String roomName = getMucOptions().getName();
             final String subject = getMucOptions().getSubject();
@@ -1000,6 +1056,10 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                     return contactJid.getLocal() != null ? contactJid.getLocal() : contactJid;
                 }
             }
+        } else if ((QuickConversationsService.isConversations()
+                        || !Config.QUICKSY_DOMAIN.equals(contactJid.getDomain()))
+                && isWithStranger()) {
+            return contactJid;
         } else {
             return this.getContact().getDisplayName();
         }
@@ -1071,9 +1131,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         this.mode = mode;
     }
 
-    /**
-     * short for is Private and Non-anonymous
-     */
+    /** short for is Private and Non-anonymous */
     public boolean isSingleOrPrivateAndNonAnonymous() {
         return mode == MODE_SINGLE || isPrivateAndNonAnonymous();
     }
@@ -1110,7 +1168,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
             return Message.ENCRYPTION_NONE;
         }
         if (OmemoSetting.isAlways()) {
-            return suitableForOmemoByDefault(this) ? Message.ENCRYPTION_AXOLOTL : Message.ENCRYPTION_NONE;
+            return suitableForOmemoByDefault(this)
+                    ? Message.ENCRYPTION_AXOLOTL
+                    : Message.ENCRYPTION_NONE;
         }
         final int defaultEncryption;
         if (suitableForOmemoByDefault(this)) {
@@ -1135,8 +1195,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         return nextMessage == null ? "" : nextMessage;
     }
 
-    public @Nullable
-    Draft getDraft() {
+    public @Nullable Draft getDraft() {
         long timestamp = getLongAttribute(ATTRIBUTE_NEXT_MESSAGE_TIMESTAMP, 0);
         final long messageTime;
         synchronized (this.messages) {
@@ -1160,7 +1219,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         boolean changed = !getNextMessage().equals(message);
         this.setAttribute(ATTRIBUTE_NEXT_MESSAGE, message);
         if (changed) {
-            this.setAttribute(ATTRIBUTE_NEXT_MESSAGE_TIMESTAMP, message == null ? 0 : System.currentTimeMillis());
+            this.setAttribute(
+                    ATTRIBUTE_NEXT_MESSAGE_TIMESTAMP,
+                    message == null ? 0 : System.currentTimeMillis());
         }
         return changed;
     }
@@ -1188,7 +1249,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         synchronized (this.messages) {
             for (int i = this.messages.size() - 1; i >= 0; --i) {
                 Message message = this.messages.get(i);
-                if (message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) {
+                if (message.getStatus() == Message.STATUS_UNSEND
+                        || message.getStatus() == Message.STATUS_SEND) {
                     String otherBody;
                     if (message.hasFileOnRemoteHost()) {
                         otherBody = message.getFileParams().url;
@@ -1208,7 +1270,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         synchronized (this.messages) {
             for (int i = this.messages.size() - 1; i >= 0; --i) {
                 final Message message = this.messages.get(i);
-                if ((message.getStatus() == s) && (message.getType() == Message.TYPE_RTP_SESSION) && sessionId.equals(message.getRemoteMsgId())) {
+                if ((message.getStatus() == s)
+                        && (message.getType() == Message.TYPE_RTP_SESSION)
+                        && sessionId.equals(message.getRemoteMsgId())) {
                     return message;
                 }
             }
@@ -1222,7 +1286,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         }
         synchronized (this.messages) {
             for (Message message : this.messages) {
-                if (serverMsgId.equals(message.getServerMsgId()) || remoteMsgId.equals(message.getRemoteMsgId())) {
+                if (serverMsgId.equals(message.getServerMsgId())
+                        || remoteMsgId.equals(message.getRemoteMsgId())) {
                     return true;
                 }
             }
@@ -1237,10 +1302,14 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
             for (int i = this.messages.size() - 1; i >= 0; --i) {
                 final Message message = this.messages.get(i);
                 if (message.isPrivateMessage()) {
-                    continue; //it's unsafe to use private messages as anchor. They could be coming from user archive
-                }
-                if (message.getStatus() == Message.STATUS_RECEIVED || message.isCarbon() || message.getServerMsgId() != null) {
-                    lastReceived = new MamReference(message.getTimeSent(), message.getServerMsgId());
+                    continue; // it's unsafe to use private messages as anchor. They could be coming
+                    // from user archive
+                }
+                if (message.getStatus() == Message.STATUS_RECEIVED
+                        || message.isCarbon()
+                        || message.getServerMsgId() != null) {
+                    lastReceived =
+                            new MamReference(message.getTimeSent(), message.getServerMsgId());
                     break;
                 }
             }
@@ -1257,7 +1326,10 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
     }
 
     public boolean alwaysNotify() {
-        return mode == MODE_SINGLE || getBooleanAttribute(ATTRIBUTE_ALWAYS_NOTIFY, Config.ALWAYS_NOTIFY_BY_DEFAULT || isPrivateAndNonAnonymous());
+        return mode == MODE_SINGLE
+                || getBooleanAttribute(
+                        ATTRIBUTE_ALWAYS_NOTIFY,
+                        Config.ALWAYS_NOTIFY_BY_DEFAULT || isPrivateAndNonAnonymous());
     }
 
     public boolean notifyReplies() {
@@ -1338,11 +1410,11 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                     try {
                         list.add(Jid.of(array.getString(i)));
                     } catch (IllegalArgumentException e) {
-                        //ignored
+                        // ignored
                     }
                 }
             } catch (JSONException e) {
-                //ignored
+                // ignored
             }
         }
         return list;
@@ -1440,7 +1512,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
 
     public void expireOldMessages(long timestamp) {
         synchronized (this.messages) {
-            for (ListIterator<Message> iterator = this.messages.listIterator(); iterator.hasNext(); ) {
+            for (ListIterator<Message> iterator = this.messages.listIterator();
+                    iterator.hasNext(); ) {
                 if (iterator.next().getTimeSent() < timestamp) {
                     iterator.remove();
                 }
@@ -1451,15 +1524,17 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
 
     public void sort() {
         synchronized (this.messages) {
-            Collections.sort(this.messages, (left, right) -> {
-                if (left.getTimeSent() < right.getTimeSent()) {
-                    return -1;
-                } else if (left.getTimeSent() > right.getTimeSent()) {
-                    return 1;
-                } else {
-                    return 0;
-                }
-            });
+            Collections.sort(
+                    this.messages,
+                    (left, right) -> {
+                        if (left.getTimeSent() < right.getTimeSent()) {
+                            return -1;
+                        } else if (left.getTimeSent() > right.getTimeSent()) {
+                            return 1;
+                        } else {
+                            return 0;
+                        }
+                    });
             untieMessages();
         }
     }
@@ -1473,8 +1548,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
     public int unreadCount(XmppConnectionService xmppConnectionService) {
         synchronized (this.messages) {
             int count = 0;
-            for (int i = messages.size() - 1; i >= 0; --i) {
-                final Message message = messages.get(i);
+            for (final Message message : Lists.reverse(this.messages)) {
                 if (message.getSubject() != null && !message.isOOb() && (message.getRawBody() == null || message.getRawBody().length() == 0)) continue;
                 if (asReaction(message) != null) continue;
                 if ((message.getRawBody() == null || "".equals(message.getRawBody()) || " ".equals(message.getRawBody())) && message.getReply() != null && message.edited() && message.getHtml() != null) continue;
  
  
  
    
    @@ -183,7 +183,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
     }
 
     public Message(Conversational conversation, String body, int encryption, int status) {
-        this(conversation, java.util.UUID.randomUUID().toString(),
+        this(
+                conversation,
+                java.util.UUID.randomUUID().toString(),
                 conversation.getUuid(),
                 conversation.getJid() == null ? null : conversation.getJid().asBareJid(),
                 null,
@@ -214,7 +216,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
     }
 
     public Message(Conversation conversation, int status, int type, final String remoteMsgId) {
-        this(conversation, java.util.UUID.randomUUID().toString(),
+        this(
+                conversation,
+                java.util.UUID.randomUUID().toString(),
                 conversation.getUuid(),
                 conversation.getJid() == null ? null : conversation.getJid().asBareJid(),
                 null,
@@ -244,13 +248,33 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
                 null);
     }
 
-    protected Message(final Conversational conversation, final String uuid, final String conversationUUid, final Jid counterpart,
-                      final Jid trueCounterpart, final String body, final long timeSent,
-                      final int encryption, final int status, final int type, final boolean carbon,
-                      final String remoteMsgId, final String relativeFilePath,
-                      final String serverMsgId, final String fingerprint, final boolean read,
-                      final String edited, final boolean oob, final String errorMessage, final Set<ReadByMarker> readByMarkers,
-                      final boolean markable, final boolean deleted, final String bodyLanguage, final String occupantId, final Collection<Reaction> reactions, final long timeReceived, final String subject, final String fileParams, final List<Element> payloads) {
+    protected Message(
+            final Conversational conversation,
+            final String uuid,
+            final String conversationUUid,
+            final Jid counterpart,
+            final Jid trueCounterpart,
+            final String body,
+            final long timeSent,
+            final int encryption,
+            final int status,
+            final int type,
+            final boolean carbon,
+            final String remoteMsgId,
+            final String relativeFilePath,
+            final String serverMsgId,
+            final String fingerprint,
+            final boolean read,
+            final String edited,
+            final boolean oob,
+            final String errorMessage,
+            final Set<ReadByMarker> readByMarkers,
+            final boolean markable,
+            final boolean deleted,
+            final String bodyLanguage,
+            final String occupantId,
+            final Collection<Reaction> reactions,
+            final long timeReceived, final String subject, final String fileParams, final List<Element> payloads) {
         this.conversation = conversation;
         this.uuid = uuid;
         this.conversationUuid = conversationUUid;
@@ -396,7 +420,11 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         } else {
             values.put(TRUE_COUNTERPART, trueCounterpart.toString());
         }
-        values.put(BODY, body.length() > Config.MAX_STORAGE_MESSAGE_CHARS ? body.substring(0, Config.MAX_STORAGE_MESSAGE_CHARS) : body);
+        values.put(
+                BODY,
+                body.length() > Config.MAX_STORAGE_MESSAGE_CHARS
+                        ? body.substring(0, Config.MAX_STORAGE_MESSAGE_CHARS)
+                        : body);
         values.put(TIME_SENT, timeSent);
         values.put(ENCRYPTION, encryption);
         values.put(STATUS, status);
@@ -538,7 +566,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
             if (this.trueCounterpart == null) {
                 return null;
             } else {
-                return this.conversation.getAccount().getRoster()
+                return this.conversation
+                        .getAccount()
+                        .getRoster()
                         .getContactFromContactList(this.trueCounterpart);
             }
         }
@@ -716,8 +746,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
     }
 
     public boolean setErrorMessage(String message) {
-        boolean changed = (message != null && !message.equals(errorMessage))
-                || (message == null && errorMessage != null);
+        boolean changed =
+                (message != null && !message.equals(errorMessage))
+                        || (message == null && errorMessage != null);
         this.errorMessage = message;
         return changed;
     }
@@ -849,15 +880,6 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         }
     }
 
-    boolean remoteMsgIdMatchInEdit(String id) {
-        for (Edit edit : this.edits) {
-            if (id.equals(edit.getEditedId())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     public String getBodyLanguage() {
         return this.bodyLanguage;
     }
@@ -867,7 +889,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
     }
 
     public boolean edited() {
-        return this.edits.size() > 0;
+        return !this.edits.isEmpty();
     }
 
     public void setTrueCounterpart(Jid trueCounterpart) {
@@ -901,7 +923,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
                 Iterator<ReadByMarker> iterator = this.readByMarkers.iterator();
                 while (iterator.hasNext()) {
                     ReadByMarker marker = iterator.next();
-                    if (marker.getRealJid() == null && readByMarker.getFullJid().equals(marker.getFullJid())) {
+                    if (marker.getRealJid() == null
+                            && readByMarker.getFullJid().equals(marker.getFullJid())) {
                         iterator.remove();
                     }
                 }
@@ -933,7 +956,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
 
     boolean similar(Message message) {
         if (!isPrivateMessage() && this.serverMsgId != null && message.getServerMsgId() != null) {
-            return this.serverMsgId.equals(message.getServerMsgId()) || Edit.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId());
+            return this.serverMsgId.equals(message.getServerMsgId())
+                    || Edit.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId());
         } else if (Edit.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId())) {
             return true;
         } else if (this.body == null || this.counterpart == null) {
@@ -949,32 +973,36 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
             }
             final boolean matchingCounterpart = this.counterpart.equals(message.getCounterpart());
             if (message.getRemoteMsgId() != null) {
-                final boolean hasUuid = CryptoHelper.UUID_PATTERN.matcher(message.getRemoteMsgId()).matches();
-                if (hasUuid && matchingCounterpart && Edit.wasPreviouslyEditedRemoteMsgId(edits, message.getRemoteMsgId())) {
+                final boolean hasUuid =
+                        CryptoHelper.UUID_PATTERN.matcher(message.getRemoteMsgId()).matches();
+                if (hasUuid
+                        && matchingCounterpart
+                        && Edit.wasPreviouslyEditedRemoteMsgId(edits, message.getRemoteMsgId())) {
                     return true;
                 }
-                return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid))
+                return (message.getRemoteMsgId().equals(this.remoteMsgId)
+                                || message.getRemoteMsgId().equals(this.uuid))
                         && matchingCounterpart
-                        && (body.equals(otherBody) || (message.getEncryption() == Message.ENCRYPTION_PGP && hasUuid));
+                        && (body.equals(otherBody)
+                                || (message.getEncryption() == Message.ENCRYPTION_PGP && hasUuid));
             } else {
                 return this.remoteMsgId == null
                         && matchingCounterpart
                         && body.equals(otherBody)
-                        && Math.abs(this.getTimeSent() - message.getTimeSent()) < Config.MESSAGE_MERGE_WINDOW * 1000;
+                        && Math.abs(this.getTimeSent() - message.getTimeSent()) < 20_000;
             }
         }
     }
 
     public Message next() {
-        if (this.conversation instanceof Conversation) {
-            final Conversation conversation = (Conversation) this.conversation;
-            synchronized (conversation.messages) {
+        if (this.conversation instanceof Conversation c) {
+            synchronized (c.messages) {
                 if (this.mNextMessage == null) {
-                    int index = conversation.messages.indexOf(this);
-                    if (index < 0 || index >= conversation.messages.size() - 1) {
+                    int index = c.messages.indexOf(this);
+                    if (index < 0 || index >= c.messages.size() - 1) {
                         this.mNextMessage = null;
                     } else {
-                        this.mNextMessage = conversation.messages.get(index + 1);
+                        this.mNextMessage = c.messages.get(index + 1);
                     }
                 }
                 return this.mNextMessage;
@@ -985,15 +1013,14 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
     }
 
     public Message prev() {
-        if (this.conversation instanceof Conversation) {
-            final Conversation conversation = (Conversation) this.conversation;
-            synchronized (conversation.messages) {
+        if (this.conversation instanceof Conversation c) {
+            synchronized (c.messages) {
                 if (this.mPreviousMessage == null) {
-                    int index = conversation.messages.indexOf(this);
-                    if (index <= 0 || index > conversation.messages.size()) {
+                    int index = c.messages.indexOf(this);
+                    if (index <= 0 || index > c.messages.size()) {
                         this.mPreviousMessage = null;
                     } else {
-                        this.mPreviousMessage = conversation.messages.get(index - 1);
+                        this.mPreviousMessage = c.messages.get(index - 1);
                     }
                 }
             }
@@ -1018,26 +1045,6 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         return status != STATUS_RECEIVED && !isCarbon() && type != Message.TYPE_RTP_SESSION;
     }
 
-    public boolean mergeable(final Message message) {
-        return false; // Merging messages messes up reply, so disable for now
-    }
-
-    private static boolean isStatusMergeable(int a, int b) {
-        return a == b || (
-                (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_UNSEND)
-                        || (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_SEND)
-                        || (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_WAITING)
-                        || (a == Message.STATUS_SEND && b == Message.STATUS_UNSEND)
-                        || (a == Message.STATUS_SEND && b == Message.STATUS_WAITING)
-        );
-    }
-
-    private static boolean isEncryptionMergeable(final int a, final int b) {
-        return a == b
-                && Arrays.asList(ENCRYPTION_NONE, ENCRYPTION_DECRYPTED, ENCRYPTION_AXOLOTL)
-                        .contains(a);
-    }
-
     public void setCounterparts(List<MucOptions.User> counterparts) {
         this.counterparts = counterparts;
     }
@@ -1048,7 +1055,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
 
     @Override
     public int getAvatarBackgroundColor() {
-        if (type == Message.TYPE_STATUS && getCounterparts() != null && getCounterparts().size() > 1) {
+        if (type == Message.TYPE_STATUS
+                && getCounterparts() != null
+                && getCounterparts().size() > 1) {
             return Color.TRANSPARENT;
         } else {
             return UIHelper.getColorForName(UIHelper.getMessageDisplayName(this));
@@ -1099,9 +1108,6 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         this.reactions = reactions;
     }
 
-    public static class MergeSeparator {
-    }
-
     public SpannableStringBuilder getSpannableBody(GetThumbnailForCid thumbnailer, Drawable fallbackImg) {
         return getSpannableBody(thumbnailer, fallbackImg, true);
     }
@@ -1182,56 +1188,14 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         return spannableBody;
     }
 
-    public SpannableStringBuilder getMergedBody() {
-        return getMergedBody(null, null);
-    }
-
-    public SpannableStringBuilder getMergedBody(GetThumbnailForCid thumbnailer, Drawable fallbackImg) {
-        SpannableStringBuilder body = getSpannableBody(thumbnailer, fallbackImg);
-        Message current = this;
-        while (current.mergeable(current.next())) {
-            current = current.next();
-            if (current == null || current.getModerated() != null) {
-                break;
-            }
-            body.append("\n\n");
-            body.setSpan(new MergeSeparator(), body.length() - 2, body.length(),
-                    SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
-            body.append(current.getSpannableBody(thumbnailer, fallbackImg));
-        }
-        return body;
+    public SpannableStringBuilder getSpannableBody() {
+        return getSpannableBody(null, null);
     }
 
     public boolean hasMeCommand() {
         return this.body.trim().startsWith(ME_COMMAND);
     }
 
-    public int getMergedStatus() {
-        int status = this.status;
-        Message current = this;
-        while (current.mergeable(current.next())) {
-            current = current.next();
-            if (current == null) {
-                break;
-            }
-            status = current.status;
-        }
-        return status;
-    }
-
-    public long getMergedTimeSent() {
-        long time = this.timeSent;
-        Message current = this;
-        while (current.mergeable(current.next())) {
-            current = current.next();
-            if (current == null) {
-                break;
-            }
-            time = current.timeSent;
-        }
-        return time;
-    }
-
     public boolean wasMergedIntoPrevious(XmppConnectionService xmppConnectionService) {
         Message prev = this.prev();
         if (prev != null && getModerated() != null && prev.getModerated() != null) return true;
@@ -1239,24 +1203,27 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
             final boolean muted = getStatus() == Message.STATUS_RECEIVED && conversation.getMode() == Conversation.MODE_MULTI && xmppConnectionService.isMucUserMuted(new MucOptions.User(null, conversation.getJid(), getOccupantId(), null, null));
             if (prev != null && muted && getOccupantId().equals(prev.getOccupantId())) return true;
         }
-        return prev != null && prev.mergeable(this);
+        return false;
     }
 
     public boolean trusted() {
-        Contact contact = this.getContact();
-        return status > STATUS_RECEIVED || (contact != null && (contact.showInContactList() || contact.isSelf()));
+        final var contact = this.getContact();
+        return status > STATUS_RECEIVED
+                || (contact != null && (contact.showInContactList() || contact.isSelf()));
     }
 
     public boolean fixCounterpart() {
         final Presences presences = conversation.getContact().getPresences();
         if (counterpart != null && presences.has(Strings.nullToEmpty(counterpart.getResource()))) {
             return true;
-        } else if (presences.size() >= 1) {
-            counterpart = PresenceSelector.getNextCounterpart(getContact(), presences.toResourceArray()[0]);
-            return true;
-        } else {
+        } else if (presences.isEmpty()) {
             counterpart = null;
             return false;
+        } else {
+            counterpart =
+                    PresenceSelector.getNextCounterpart(
+                            getContact(), presences.toResourceArray()[0]);
+            return true;
         }
     }
 
@@ -1265,19 +1232,17 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
     }
 
     public String getEditedId() {
-        if (edits.size() > 0) {
-            return edits.get(edits.size() - 1).getEditedId();
-        } else {
-            throw new IllegalStateException("Attempting to store unedited message");
+        if (this.edits.isEmpty()) {
+            throw new IllegalStateException("Attempting to access unedited message");
         }
+        return edits.get(edits.size() - 1).getEditedId();
     }
 
     public String getEditedIdWireFormat() {
-        if (edits.size() > 0) {
-            return edits.get(Config.USE_LMC_VERSION_1_1 ? 0 : edits.size() - 1).getEditedId();
-        } else {
-            throw new IllegalStateException("Attempting to store unedited message");
+        if (this.edits.isEmpty()) {
+            throw new IllegalStateException("Attempting to access unedited message");
         }
+        return edits.get(0).getEditedId();
     }
 
     public List<URI> getLinks() {
@@ -1509,7 +1474,6 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         return type == TYPE_FILE || type == TYPE_IMAGE || type == TYPE_PRIVATE_FILE;
     }
 
-
     public boolean isTypeText() {
         return type == TYPE_TEXT || type == TYPE_PRIVATE;
     }
@@ -1793,7 +1757,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
 
     public boolean isTrusted() {
         final AxolotlService axolotlService = conversation.getAccount().getAxolotlService();
-        final FingerprintStatus s = axolotlService != null ? axolotlService.getFingerprintTrust(axolotlFingerprint) : null;
+        final FingerprintStatus s =
+                axolotlService != null
+                        ? axolotlService.getFingerprintTrust(axolotlFingerprint)
+                        : null;
         return s != null && s.isTrusted();
     }
 
@@ -1808,17 +1775,18 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
     }
 
     private int getNextEncryption() {
-        if (this.conversation instanceof Conversation) {
-            Conversation conversation = (Conversation) this.conversation;
+        if (this.conversation instanceof Conversation c) {
             for (Message iterator = this.next(); iterator != null; iterator = iterator.next()) {
                 if (iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED) {
                     continue;
                 }
                 return iterator.getEncryption();
             }
-            return conversation.getNextEncryption();
+            return c.getNextEncryption();
         } else {
-            throw new AssertionError("This should never be called since isInValidSession should be disabled for stubs");
+            throw new AssertionError(
+                    "This should never be called since isInValidSession should be disabled for"
+                            + " stubs");
         }
     }
 
@@ -1826,9 +1794,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         int pastEncryption = getCleanedEncryption(this.getPreviousEncryption());
         int futureEncryption = getCleanedEncryption(this.getNextEncryption());
 
-        boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE
-                || futureEncryption == ENCRYPTION_NONE
-                || pastEncryption != futureEncryption;
+        boolean inUnencryptedSession =
+                pastEncryption == ENCRYPTION_NONE
+                        || futureEncryption == ENCRYPTION_NONE
+                        || pastEncryption != futureEncryption;
 
         return inUnencryptedSession || getCleanedEncryption(this.getEncryption()) == pastEncryption;
     }
@@ -1837,7 +1806,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) {
             return ENCRYPTION_PGP;
         }
-        if (encryption == ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || encryption == ENCRYPTION_AXOLOTL_FAILED) {
+        if (encryption == ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE
+                || encryption == ENCRYPTION_AXOLOTL_FAILED) {
             return ENCRYPTION_AXOLOTL;
         }
         return encryption;
@@ -1875,7 +1845,11 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         return configurePrivateMessage(conversation, message, counterpart, false);
     }
 
-    private static boolean configurePrivateMessage(final Conversation conversation, final Message message, final Jid counterpart, final boolean isFile) {
+    private static boolean configurePrivateMessage(
+            final Conversation conversation,
+            final Message message,
+            final Jid counterpart,
+            final boolean isFile) {
         if (counterpart == null) {
             return false;
         }
  
  
  
    
    @@ -7,15 +7,12 @@ import android.text.TextUtils;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-
 import io.ipfs.cid.Cid;
 
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.services.AvatarService;
@@ -32,6 +29,7 @@ import eu.siacs.conversations.xml.Element;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -60,9 +58,7 @@ public class MucOptions {
     private User self;
     private String password = null;
 
-    private boolean tookProposedNickFromBookmark = false;
-
-    public MucOptions(Conversation conversation) {
+    public MucOptions(final Conversation conversation) {
         this.account = conversation.getAccount();
         this.conversation = conversation;
         final String nick = getProposedNick(conversation.getAttribute("mucNick"));
@@ -104,10 +100,14 @@ public class MucOptions {
         return mAutoPushConfiguration;
     }
 
-    public boolean isSelf(Jid counterpart) {
+    public boolean isSelf(final Jid counterpart) {
         return counterpart.equals(self.getFullJid());
     }
 
+    public boolean isSelf(final String occupantId) {
+        return occupantId.equals(self.getOccupantId());
+    }
+
     public void resetChatState() {
         synchronized (users) {
             for (User user : users) {
@@ -116,17 +116,6 @@ public class MucOptions {
         }
     }
 
-    public boolean isTookProposedNickFromBookmark() {
-        return tookProposedNickFromBookmark;
-    }
-
-    void notifyOfBookmarkNick(final String nick) {
-        final String normalized = normalize(account.getJid(),nick);
-        if (normalized != null && normalized.equals(getSelf().getNick())) {
-            this.tookProposedNickFromBookmark = true;
-        }
-    }
-
     public boolean mamSupport() {
         return MessageArchiveService.Version.has(getFeatures());
     }
@@ -138,8 +127,8 @@ public class MucOptions {
         if (roomConfigName != null) {
             name = roomConfigName.getValue();
         } else {
-            List<ServiceDiscoveryResult.Identity> identities = serviceDiscoveryResult.getIdentities();
-            String identityName = identities.size() > 0 ? identities.get(0).getName() : null;
+            final var identities = serviceDiscoveryResult.getIdentities();
+            final String identityName = !identities.isEmpty() ? identities.get(0).getName() : null;
             final Jid jid = conversation.getJid();
             if (identityName != null && !identityName.equals(jid == null ? null : jid.getEscapedLocal())) {
                 name = identityName;
@@ -156,7 +145,7 @@ public class MucOptions {
 
     private Data getRoomInfoForm() {
         final List<Data> forms = serviceDiscoveryResult == null ? Collections.emptyList() : serviceDiscoveryResult.forms;
-        return forms.size() == 0 ? new Data() : forms.get(0);
+        return forms.isEmpty() ? new Data() : forms.get(0);
     }
 
     public String getAvatar() {
@@ -360,29 +349,24 @@ public class MucOptions {
         return null;
     }
 
-    public User findUserByOccupantId(final String id, final Jid counterpart) {
-        if (id == null) {
-            return null;
-        }
-        synchronized (users) {
-            for (User user : users) {
-                if (id.equals(user.getOccupantId())) {
-                    return user;
-                }
-            }
+    public User findUserByOccupantId(final String occupantId, final Jid counterpart) {
+        synchronized (this.users) {
+            final var found = Strings.isNullOrEmpty(occupantId) ? null : Iterables.find(this.users, u -> occupantId.equals(u.occupantId),null);
+            if (Strings.isNullOrEmpty(occupantId) || found != null) return found;
+            final var user = new User(this, counterpart, occupantId, null, new HashSet<>());
+            user.setOnline(false);
+            return user;
         }
-        final var user = new User(this, counterpart, id, null, new HashSet<>());
-        user.setOnline(false);
-        return user;
     }
 
     public User findOrCreateUserByRealJid(Jid jid, Jid fullJid, final String occupantId) {
-        User user = findUserByRealJid(jid);
-        if (user == null) {
-            user = new User(this, fullJid, occupantId, null, new HashSet<>());
-            user.setRealJid(jid);
-            user.setOnline(false);
+        final User existing = findUserByRealJid(jid);
+        if (existing != null) {
+            return existing;
         }
+        final var user = new User(this, fullJid, occupantId, null, new HashSet<>());
+        user.setRealJid(jid);
+        user.setOnline(false);
         return user;
     }
 
@@ -396,6 +380,31 @@ public class MucOptions {
         }
     }
 
+    private User findUser(final Reaction reaction) {
+        if (reaction.trueJid != null) {
+            return findOrCreateUserByRealJid(reaction.trueJid.asBareJid(), reaction.from, reaction.occupantId);
+        }
+        final var existing = findUserByOccupantId(reaction.occupantId, reaction.from);
+        if (existing != null) {
+            return existing;
+        } else if (reaction.from != null) {
+            return new User(this,reaction.from,reaction.occupantId,null,new HashSet<>());
+        } else {
+            return null;
+        }
+    }
+
+    public List<User> findUsers(final Collection<Reaction> reactions) {
+        final ImmutableList.Builder<User> builder = new ImmutableList.Builder<>();
+        for(final Reaction reaction : reactions) {
+            final var user = findUser(reaction);
+            if (user != null) {
+                builder.add(user);
+            }
+        }
+        return builder.build();
+    }
+
     public boolean isContactInRoom(Contact contact) {
         return contact != null && isUserInRoom(findUserByRealJid(contact.getJid().asBareJid()));
     }
@@ -499,20 +508,31 @@ public class MucOptions {
         }
     }
 
-    public String getProposedNick() {
+    private String getProposedNick() {
         return getProposedNick(null);
     }
 
-    public String getProposedNick(final String mucNick) {
+    private String getProposedNick(final String mucNick) {
+        final Bookmark bookmark = this.conversation.getBookmark();
+        if (bookmark != null) {
+            // if we already have a bookmark we consider this the source of truth
+            return getProposedNickPure();
+        }
+        final var storedJid = conversation.getJid();
+        if (mucNick != null) {
+            return mucNick;
+        } else if (storedJid.isBareJid()) {
+            return defaultNick(account);
+        } else {
+            return storedJid.getResource();
+        }
+    }
+
+    public String getProposedNickPure() {
         final Bookmark bookmark = this.conversation.getBookmark();
         final String bookmarkedNick = normalize(account.getJid(), bookmark == null ? null : bookmark.getNick());
         if (bookmarkedNick != null) {
-            this.tookProposedNickFromBookmark = true;
             return bookmarkedNick;
-        } else if (mucNick != null) {
-            return mucNick;
-        } else if (!conversation.getJid().isBareJid()) {
-            return conversation.getJid().getResource();
         } else {
             return defaultNick(account);
         }
@@ -527,15 +547,15 @@ public class MucOptions {
         }
     }
 
-    private static String normalize(Jid account, String nick) {
-        if (account == null || TextUtils.isEmpty(nick)) {
+    private static String normalize(final Jid account, final String nick) {
+        if (account == null || Strings.isNullOrEmpty(nick)) {
             return null;
         }
 
         try {
             return account.withResource(nick).getResource();
-        } catch (IllegalArgumentException e) {
-            return nick;
+        } catch (final IllegalArgumentException e) {
+            return null;
         }
     }
 
  
  
  
    
    @@ -1,5 +1,7 @@
 package eu.siacs.conversations.entities;
 
+import android.util.Log;
+
 import androidx.annotation.NonNull;
 
 import com.cheogram.android.EmojiSearch;
@@ -13,21 +15,22 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimaps;
 import com.google.common.collect.Ordering;
-import com.google.gson.reflect.TypeToken;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.JsonSyntaxException;
 import com.google.gson.TypeAdapter;
+import com.google.gson.reflect.TypeToken;
 import com.google.gson.stream.JsonReader;
 import com.google.gson.stream.JsonToken;
 import com.google.gson.stream.JsonWriter;
 
 import io.ipfs.cid.Cid;
 
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.utils.Emoticons;
 import eu.siacs.conversations.xmpp.Jid;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -79,6 +82,10 @@ public class Reaction {
         this.envelopeId = envelopeId;
     }
 
+    public String normalizedReaction() {
+        return Emoticons.normalizeToVS16(this.reaction);
+    }
+
     public static String toString(final Collection<Reaction> reactions) {
         return (reactions == null || reactions.isEmpty()) ? null : GSON.toJson(reactions);
     }
@@ -88,8 +95,9 @@ public class Reaction {
             return Collections.emptyList();
         }
         try {
-            return GSON.fromJson(asString, new TypeToken<ArrayList<Reaction>>() {}.getType());
-        } catch (final JsonSyntaxException e) {
+            return GSON.fromJson(asString, new TypeToken<List<Reaction>>() {}.getType());
+        } catch (final IllegalArgumentException | JsonSyntaxException e) {
+            Log.e(Config.LOGTAG, "could not restore reactions", e);
             return Collections.emptyList();
         }
     }
@@ -228,7 +236,7 @@ public class Reaction {
                 ImmutableSet.copyOf(
                         Collections2.transform(
                                 Collections2.filter(reactions, r -> r.cid == null && !r.received),
-                                r -> r.reaction)));
+                                Reaction::normalizedReaction)));
     }
 
     public static final class Aggregated {
  
  
  
    
    @@ -1,12 +1,5 @@
 package eu.siacs.conversations.generator;
 
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
-
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
@@ -24,21 +17,34 @@ import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
 import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
 import eu.siacs.conversations.xmpp.jingle.Media;
 import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
+import im.conversations.android.xmpp.model.correction.Replace;
 import im.conversations.android.xmpp.model.reactions.Reaction;
 import im.conversations.android.xmpp.model.reactions.Reactions;
+import im.conversations.android.xmpp.model.unique.OriginId;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
 
 public class MessageGenerator extends AbstractGenerator {
-    private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo";
-    private static final String PGP_FALLBACK_MESSAGE = "I sent you a PGP encrypted message but your client doesn’t seem to support that.";
+    private static final String OMEMO_FALLBACK_MESSAGE =
+            "I sent you an OMEMO encrypted message but your client doesn’t seem to support that."
+                    + " Find more information on https://conversations.im/omemo";
+    private static final String PGP_FALLBACK_MESSAGE =
+            "I sent you a PGP encrypted message but your client doesn’t seem to support that.";
 
     public MessageGenerator(XmppConnectionService service) {
         super(service);
     }
 
-    private im.conversations.android.xmpp.model.stanza.Message preparePacket(Message message, boolean legacyEncryption) {
+    private im.conversations.android.xmpp.model.stanza.Message preparePacket(
+            final Message message, final boolean legacyEncryption) {
         Conversation conversation = (Conversation) message.getConversation();
         Account account = conversation.getAccount();
-        im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+        im.conversations.android.xmpp.model.stanza.Message packet =
+                new im.conversations.android.xmpp.model.stanza.Message();
         final boolean isWithSelf = conversation.getContact().isSelf();
         if (conversation.getMode() == Conversation.MODE_SINGLE) {
             packet.setTo(message.getCounterpart());
@@ -60,11 +66,13 @@ public class MessageGenerator extends AbstractGenerator {
         }
         packet.setFrom(account.getJid());
         packet.setId(message.getUuid());
-        if (conversation.getMode() == Conversational.MODE_SINGLE || message.isPrivateMessage() || !conversation.getMucOptions().stableId()) {
-            packet.addChild("origin-id", Namespace.STANZA_IDS).setAttribute("id", message.getUuid());
+        if (conversation.getMode() == Conversational.MODE_MULTI
+                && !message.isPrivateMessage()
+                && !conversation.getMucOptions().stableId()) {
+            packet.addExtension(new OriginId(message.getUuid()));
         }
         if (message.edited()) {
-            packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedIdWireFormat());
+            packet.addExtension(new Replace(message.getEditedIdWireFormat()));
         }
         if (!legacyEncryption) {
             if (message.getSubject() != null && message.getSubject().length() > 0) packet.addChild("subject").setContent(message.getSubject());
@@ -80,16 +88,18 @@ public class MessageGenerator extends AbstractGenerator {
         return packet;
     }
 
-    public void addDelay(im.conversations.android.xmpp.model.stanza.Message packet, long timestamp) {
-        final SimpleDateFormat mDateFormat = new SimpleDateFormat(
-                "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
+    public void addDelay(
+            im.conversations.android.xmpp.model.stanza.Message packet, long timestamp) {
+        final SimpleDateFormat mDateFormat =
+                new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
         mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
         Element delay = packet.addChild("delay", "urn:xmpp:delay");
         Date date = new Date(timestamp);
         delay.setAttribute("stamp", mDateFormat.format(date));
     }
 
-    public im.conversations.android.xmpp.model.stanza.Message generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) {
+    public im.conversations.android.xmpp.model.stanza.Message generateAxolotlChat(
+            Message message, XmppAxolotlMessage axolotlMessage) {
         im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message, true);
         if (axolotlMessage == null) {
             return null;
@@ -103,8 +113,10 @@ public class MessageGenerator extends AbstractGenerator {
         return packet;
     }
 
-    public im.conversations.android.xmpp.model.stanza.Message generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) {
-        im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+    public im.conversations.android.xmpp.model.stanza.Message generateKeyTransportMessage(
+            Jid to, XmppAxolotlMessage axolotlMessage) {
+        im.conversations.android.xmpp.model.stanza.Message packet =
+                new im.conversations.android.xmpp.model.stanza.Message();
         packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
         packet.setTo(to);
         packet.setAxolotlMessage(axolotlMessage.toElement());
@@ -166,23 +178,32 @@ public class MessageGenerator extends AbstractGenerator {
         return packet;
     }
 
-    public im.conversations.android.xmpp.model.stanza.Message generateChatState(Conversation conversation) {
+    public im.conversations.android.xmpp.model.stanza.Message generateChatState(
+            Conversation conversation) {
         final Account account = conversation.getAccount();
-        final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
-        packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
+        final im.conversations.android.xmpp.model.stanza.Message packet =
+                new im.conversations.android.xmpp.model.stanza.Message();
+        packet.setType(
+                conversation.getMode() == Conversation.MODE_MULTI
+                        ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT
+                        : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
         packet.setTo(conversation.getJid().asBareJid());
         packet.setFrom(account.getJid());
         packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
         packet.addChild("no-store", "urn:xmpp:hints");
-        packet.addChild("no-storage", "urn:xmpp:hints"); //wrong! don't copy this. Its *store*
+        packet.addChild("no-storage", "urn:xmpp:hints"); // wrong! don't copy this. Its *store*
         return packet;
     }
 
     public im.conversations.android.xmpp.model.stanza.Message confirm(final Message message) {
         final boolean groupChat = message.getConversation().getMode() == Conversational.MODE_MULTI;
         final Jid to = message.getCounterpart();
-        final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
-        packet.setType(groupChat ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
+        final im.conversations.android.xmpp.model.stanza.Message packet =
+                new im.conversations.android.xmpp.model.stanza.Message();
+        packet.setType(
+                groupChat
+                        ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT
+                        : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
         packet.setTo(groupChat ? to.asBareJid() : to);
         final Element displayed = packet.addChild("displayed", "urn:xmpp:chat-markers:0");
         if (groupChat) {
@@ -200,15 +221,22 @@ public class MessageGenerator extends AbstractGenerator {
         return packet;
     }
 
-    public im.conversations.android.xmpp.model.stanza.Message reaction(final Conversational conversation, final Message inReplyTo, final String reactingTo, final Collection<String> ourReactions) {
-        final boolean groupChat = conversation.getMode() == Conversational.MODE_MULTI;
-        final Jid to = conversation.getJid().asBareJid();
-        final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
-        packet.setType(groupChat ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
+    public im.conversations.android.xmpp.model.stanza.Message reaction(
+            final Jid to,
+            final boolean groupChat,
+            final Message inReplyTo,
+            final String reactingTo,
+            final Collection<String> ourReactions) {
+        final im.conversations.android.xmpp.model.stanza.Message packet =
+                new im.conversations.android.xmpp.model.stanza.Message();
+        packet.setType(
+                groupChat
+                        ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT
+                        : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
         packet.setTo(to);
         final var reactions = packet.addExtension(new Reactions());
         reactions.setId(reactingTo);
-        for(final String ourReaction : ourReactions) {
+        for (final String ourReaction : ourReactions) {
             reactions.addExtension(new Reaction(ourReaction));
         }
 
@@ -219,8 +247,10 @@ public class MessageGenerator extends AbstractGenerator {
         return packet;
     }
 
-    public im.conversations.android.xmpp.model.stanza.Message conferenceSubject(Conversation conversation, String subject) {
-        im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+    public im.conversations.android.xmpp.model.stanza.Message conferenceSubject(
+            Conversation conversation, String subject) {
+        im.conversations.android.xmpp.model.stanza.Message packet =
+                new im.conversations.android.xmpp.model.stanza.Message();
         packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT);
         packet.setTo(conversation.getJid().asBareJid());
         packet.addChild("subject").setContent(subject);
@@ -240,8 +270,10 @@ public class MessageGenerator extends AbstractGenerator {
         return packet;
     }
 
-    public im.conversations.android.xmpp.model.stanza.Message directInvite(final Conversation conversation, final Jid contact) {
-        im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+    public im.conversations.android.xmpp.model.stanza.Message directInvite(
+            final Conversation conversation, final Jid contact) {
+        im.conversations.android.xmpp.model.stanza.Message packet =
+                new im.conversations.android.xmpp.model.stanza.Message();
         packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.NORMAL);
         packet.setTo(contact);
         packet.setFrom(conversation.getAccount().getJid());
@@ -258,7 +290,8 @@ public class MessageGenerator extends AbstractGenerator {
         return packet;
     }
 
-    public im.conversations.android.xmpp.model.stanza.Message invite(final Conversation conversation, final Jid contact) {
+    public im.conversations.android.xmpp.model.stanza.Message invite(
+            final Conversation conversation, final Jid contact) {
         final var packet = new im.conversations.android.xmpp.model.stanza.Message();
         packet.setTo(conversation.getJid().asBareJid());
         packet.setFrom(conversation.getAccount().getJid());
@@ -271,9 +304,13 @@ public class MessageGenerator extends AbstractGenerator {
         return packet;
     }
 
-    public im.conversations.android.xmpp.model.stanza.Message received(Account account, final Jid from, final String id, ArrayList<String> namespaces, im.conversations.android.xmpp.model.stanza.Message.Type type) {
-        final var receivedPacket =
-                new im.conversations.android.xmpp.model.stanza.Message();
+    public im.conversations.android.xmpp.model.stanza.Message received(
+            Account account,
+            final Jid from,
+            final String id,
+            ArrayList<String> namespaces,
+            im.conversations.android.xmpp.model.stanza.Message.Type type) {
+        final var receivedPacket = new im.conversations.android.xmpp.model.stanza.Message();
         receivedPacket.setType(type);
         receivedPacket.setTo(from);
         receivedPacket.setFrom(account.getJid());
@@ -284,8 +321,10 @@ public class MessageGenerator extends AbstractGenerator {
         return receivedPacket;
     }
 
-    public im.conversations.android.xmpp.model.stanza.Message received(Account account, Jid to, String id) {
-        im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+    public im.conversations.android.xmpp.model.stanza.Message received(
+            Account account, Jid to, String id) {
+        im.conversations.android.xmpp.model.stanza.Message packet =
+                new im.conversations.android.xmpp.model.stanza.Message();
         packet.setFrom(account.getJid());
         packet.setTo(to);
         packet.addChild("received", "urn:xmpp:receipts").setAttribute("id", id);
@@ -295,7 +334,8 @@ public class MessageGenerator extends AbstractGenerator {
 
     public im.conversations.android.xmpp.model.stanza.Message sessionFinish(
             final Jid with, final String sessionId, final Reason reason) {
-        final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+        final im.conversations.android.xmpp.model.stanza.Message packet =
+                new im.conversations.android.xmpp.model.stanza.Message();
         packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
         packet.setTo(with);
         final Element finish = packet.addChild("finish", Namespace.JINGLE_MESSAGE);
@@ -306,24 +346,33 @@ public class MessageGenerator extends AbstractGenerator {
         return packet;
     }
 
-    public im.conversations.android.xmpp.model.stanza.Message sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) {
-        final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
-        packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
+    public im.conversations.android.xmpp.model.stanza.Message sessionProposal(
+            final JingleConnectionManager.RtpSessionProposal proposal) {
+        final im.conversations.android.xmpp.model.stanza.Message packet =
+                new im.conversations.android.xmpp.model.stanza.Message();
+        packet.setType(
+                im.conversations.android.xmpp.model.stanza.Message.Type
+                        .CHAT); // we want to carbon copy those
         packet.setTo(proposal.with);
         packet.setId(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX + proposal.sessionId);
         final Element propose = packet.addChild("propose", Namespace.JINGLE_MESSAGE);
         propose.setAttribute("id", proposal.sessionId);
         for (final Media media : proposal.media) {
-            propose.addChild("description", Namespace.JINGLE_APPS_RTP).setAttribute("media", media.toString());
+            propose.addChild("description", Namespace.JINGLE_APPS_RTP)
+                    .setAttribute("media", media.toString());
         }
         packet.addChild("request", "urn:xmpp:receipts");
         packet.addChild("store", "urn:xmpp:hints");
         return packet;
     }
 
-    public im.conversations.android.xmpp.model.stanza.Message sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) {
-        final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
-        packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
+    public im.conversations.android.xmpp.model.stanza.Message sessionRetract(
+            final JingleConnectionManager.RtpSessionProposal proposal) {
+        final im.conversations.android.xmpp.model.stanza.Message packet =
+                new im.conversations.android.xmpp.model.stanza.Message();
+        packet.setType(
+                im.conversations.android.xmpp.model.stanza.Message.Type
+                        .CHAT); // we want to carbon copy those
         packet.setTo(proposal.with);
         final Element propose = packet.addChild("retract", Namespace.JINGLE_MESSAGE);
         propose.setAttribute("id", proposal.sessionId);
@@ -332,9 +381,13 @@ public class MessageGenerator extends AbstractGenerator {
         return packet;
     }
 
-    public im.conversations.android.xmpp.model.stanza.Message sessionReject(final Jid with, final String sessionId) {
-        final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
-        packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
+    public im.conversations.android.xmpp.model.stanza.Message sessionReject(
+            final Jid with, final String sessionId) {
+        final im.conversations.android.xmpp.model.stanza.Message packet =
+                new im.conversations.android.xmpp.model.stanza.Message();
+        packet.setType(
+                im.conversations.android.xmpp.model.stanza.Message.Type
+                        .CHAT); // we want to carbon copy those
         packet.setTo(with);
         final Element propose = packet.addChild("reject", Namespace.JINGLE_MESSAGE);
         propose.setAttribute("id", sessionId);
  
  
  
    
    @@ -7,6 +7,7 @@ import android.util.Pair;
 import com.cheogram.android.BobTransfer;
 import com.cheogram.android.WebxdcUpdate;
 
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
 
 import java.io.File;
@@ -68,6 +69,7 @@ import eu.siacs.conversations.xmpp.pep.Avatar;
 import im.conversations.android.xmpp.model.Extension;
 import im.conversations.android.xmpp.model.carbons.Received;
 import im.conversations.android.xmpp.model.carbons.Sent;
+import im.conversations.android.xmpp.model.correction.Replace;
 import im.conversations.android.xmpp.model.forward.Forwarded;
 import im.conversations.android.xmpp.model.occupant.OccupantId;
 import im.conversations.android.xmpp.model.reactions.Reactions;
@@ -351,12 +353,13 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         mXmppConnectionService.processDeletedBookmarks(account, previous);
     }
 
-    private void setNick(Account account, Jid user, String nick) {
+    private void setNick(final Account account, final Jid user, final String nick) {
         if (user.asBareJid().equals(account.getJid().asBareJid())) {
             account.setDisplayName(nick);
             if (QuickConversationsService.isQuicksy()) {
                 mXmppConnectionService.getAvatarService().clear(account);
             }
+            mXmppConnectionService.checkMucRequiresRename();
         } else {
             Contact contact = account.getRoster().getContact(user);
             if (contact.setPresenceName(nick)) {
@@ -470,7 +473,9 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
         }
         final Element mucUserElement = packet.findChild("x", Namespace.MUC_USER);
+        final boolean isTypeGroupChat = packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT;
         final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
+
         Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
         Set<Message.FileParams> attachments = new LinkedHashSet<>();
         for (Element child : packet.getChildren()) {
@@ -505,6 +510,7 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         LocalizedContent body = packet.getBody();
 
         final var reactions = packet.getExtension(Reactions.class);
+
         final Element axolotlEncrypted = packet.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
         int status;
         final Jid counterpart;
@@ -528,12 +534,36 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             Log.e(Config.LOGTAG, "encountered invalid message from='" + from + "' to='" + to + "'");
             return;
         }
-
-        boolean isTypeGroupChat = packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT;
         if (query != null && !query.muc() && isTypeGroupChat) {
             Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping");
             return;
         }
+        final Jid mucTrueCounterPart;
+        final OccupantId occupant;
+        if (isTypeGroupChat) {
+            final Conversation conversation =
+                    mXmppConnectionService.find(account, from.asBareJid());
+            final Jid mucTrueCounterPartByPresence;
+            if (conversation != null) {
+                final var mucOptions = conversation.getMucOptions();
+                occupant = mucOptions.occupantId() ? packet.getExtension(OccupantId.class) : null;
+                final var user =
+                        occupant == null ? null : mucOptions.findUserByOccupantId(occupant.getId(), from);
+                mucTrueCounterPartByPresence = user == null ? null : user.getRealJid();
+            } else {
+                occupant = null;
+                mucTrueCounterPartByPresence = null;
+            }
+            mucTrueCounterPart =
+                    getTrueCounterpart(
+                            (query != null && query.safeToExtractTrueCounterpart())
+                                    ? mucUserElement
+                                    : null,
+                            mucTrueCounterPartByPresence);
+        } else {
+            mucTrueCounterPart = null;
+            occupant = null;
+        }
         boolean isMucStatusMessage = InvalidJid.hasValidFrom(packet) && from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status");
         boolean selfAddressed;
         if (packet.fromAccount(account)) {
@@ -622,7 +652,14 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
 
 
             if (selfAddressed) {
-                if (mXmppConnectionService.markMessage(conversation, remoteMsgId, Message.STATUS_SEND_RECEIVED, serverMsgId)) {
+                // don’t store serverMsgId on reflections for edits
+                final var reflectedServerMsgId =
+                        Strings.isNullOrEmpty(replacementId) ? serverMsgId : null;
+                if (mXmppConnectionService.markMessage(
+                        conversation,
+                        remoteMsgId,
+                        Message.STATUS_SEND_RECEIVED,
+                        reflectedServerMsgId)) {
                     return;
                 }
                 status = Message.STATUS_RECEIVED;
@@ -635,7 +672,10 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 if (conversation.getMucOptions().isSelf(counterpart)) {
                     status = Message.STATUS_SEND_RECEIVED;
                     isCarbon = true; //not really carbon but received from another resource
-                    if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, serverMsgId, body, html, packet.findChildContent("subject"), packet.findChild("thread"), attachments)) {
+                    // don’t store serverMsgId on reflections for edits
+                    final var reflectedServerMsgId =
+                            Strings.isNullOrEmpty(replacementId) ? serverMsgId : null;
+                    if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, reflectedServerMsgId, body, html, packet.findChildContent("subject"), packet.findChild("thread"), attachments)) {
                         return;
                     } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) {
                         if (body != null) {
@@ -774,10 +814,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             }
             if (conversationMultiMode) {
                 final var mucOptions = conversation.getMucOptions();
-                final var occupantId =
-                        mucOptions.occupantId() ? packet.getExtension(OccupantId.class) : null;
-                if (occupantId != null) {
-                    message.setOccupantId(occupantId.getId());
+                if (occupant != null) {
+                    message.setOccupantId(occupant.getId());
                 }
                 message.setMucUser(mucOptions.findUserByFullJid(counterpart));
                 final Jid fallback = mucOptions.getTrueCounterpart(counterpart);
@@ -808,6 +846,7 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
 
             if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
                 final Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId, counterpart);
+Log.d("WUT", "" + replacementId + "  " + replacedMessage);
                 if (replacedMessage != null) {
                     final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
                             || replacedMessage.getFingerprint().equals(message.getFingerprint());
@@ -858,14 +897,27 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                                 }
                             }
                             replacedMessage.setInReplyTo(message.getInReplyTo());
-                            if (replacedMessage.getServerMsgId() == null || message.getServerMsgId() != null) {
-                                replacedMessage.setServerMsgId(message.getServerMsgId());
-                            }
+
+                            // we store the IDs of the replacing message. This is essentially unused
+                            // today (only the fact that there are _some_ edits causes the edit icon
+                            // to appear)
+                            replacedMessage.putEdited(
+                                    message.getRemoteMsgId(), message.getServerMsgId());
+
+                            // we used to call
+                            // `replacedMessage.setServerMsgId(message.getServerMsgId());` so during
+                            // catchup we could start from the edit; not the original message
+                            // however this caused problems for things like reactions that refer to
+                            // the serverMsgId
+
                             replacedMessage.setEncryption(message.getEncryption());
                             if (replacedMessage.getStatus() == Message.STATUS_RECEIVED) {
                                 replacedMessage.markUnread();
                             }
-                            extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
+                            extractChatState(
+                                    mXmppConnectionService.find(account, counterpart.asBareJid()),
+                                    isTypeGroupChat,
+                                    packet);
                             mXmppConnectionService.updateMessage(replacedMessage, uuid);
                             if (mXmppConnectionService.confirmMessages()
                                     && replacedMessage.getStatus() == Message.STATUS_RECEIVED
@@ -1105,8 +1157,7 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                             // the 'ringing' response. Responding with delivery receipts predates
                             // the 'ringing' spec'd
                             final boolean sendReceipts =
-                                    (mXmppConnectionService.confirmMessages()
-                                                    && contact.showInContactList())
+                                    contact.showInContactList()
                                             || Config.JINGLE_MESSAGE_INIT_STRICT_OFFLINE_CHECK;
                             if (remoteMsgId != null && !contact.isSelf() && sendReceipts) {
                                 processMessageReceipts(account, packet, remoteMsgId, null);
@@ -1300,12 +1351,9 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             if (conversation != null && reactingTo != null) {
                 if (isTypeGroupChat && conversation.getMode() == Conversational.MODE_MULTI) {
                     final var mucOptions = conversation.getMucOptions();
-                    final var occupant =
-                            mucOptions.occupantId() ? packet.getExtension(OccupantId.class) : null;
                     final var occupantId = occupant == null ? null : occupant.getId();
                     if (occupantId != null) {
-                        // TODO use occupant id for isSelf assessment
-                        final boolean isReceived = !mucOptions.isSelf(counterpart);
+                        final boolean isReceived = !mucOptions.isSelf(occupantId);
                         final Message message;
                         final var inMemoryMessage =
                                 conversation.findMessageWithServerMsgId(reactingTo);
  
  
  
    
    @@ -92,11 +92,13 @@ public class PresenceParser extends AbstractParser implements Consumer<im.conver
                             if (mucOptions.setOnline()) {
                                 mXmppConnectionService.getAvatarService().clear(mucOptions);
                             }
+                            final var current = mucOptions.getSelf().getFullJid();
                             if (mucOptions.setSelf(user)) {
                                 Log.d(Config.LOGTAG,"role or affiliation changed");
                                 mXmppConnectionService.databaseBackend.updateConversation(conversation);
                             }
-                            mXmppConnectionService.persistSelfNick(user);
+                            final var modified = current == null || !current.equals(user.getFullJid());
+                            mXmppConnectionService.persistSelfNick(user, modified);
                             invokeRenameListener(mucOptions, true);
                         }
                         boolean isNew = mucOptions.updateUser(user);
  
  
  
    
    @@ -72,6 +72,30 @@ import eu.siacs.conversations.utils.Resolver;
 import eu.siacs.conversations.xmpp.InvalidJid;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.mam.MamReference;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.whispersystems.libsignal.IdentityKey;
+import org.whispersystems.libsignal.IdentityKeyPair;
+import org.whispersystems.libsignal.InvalidKeyException;
+import org.whispersystems.libsignal.SignalProtocolAddress;
+import org.whispersystems.libsignal.state.PreKeyRecord;
+import org.whispersystems.libsignal.state.SessionRecord;
+import org.whispersystems.libsignal.state.SignedPreKeyRecord;
 
 public class DatabaseBackend extends SQLiteOpenHelper {
 
@@ -80,117 +104,266 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 
     private static boolean requiresMessageIndexRebuild = false;
     private static DatabaseBackend instance = null;
-    private static final String CREATE_CONTATCS_STATEMENT = "create table "
-            + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
-            + Contact.SERVERNAME + " TEXT, " + Contact.SYSTEMNAME + " TEXT,"
-            + Contact.PRESENCE_NAME + " TEXT,"
-            + Contact.JID + " TEXT," + Contact.KEYS + " TEXT,"
-            + Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER,"
-            + Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, "
-            + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, "
-            + Contact.RTP_CAPABILITY + " TEXT,"
-            + Contact.GROUPS + " TEXT, FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES "
-            + Account.TABLENAME + "(" + Account.UUID
-            + ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", "
-            + Contact.JID + ") ON CONFLICT REPLACE);";
-
-    private static final String CREATE_DISCOVERY_RESULTS_STATEMENT = "create table "
-            + ServiceDiscoveryResult.TABLENAME + "("
-            + ServiceDiscoveryResult.HASH + " TEXT, "
-            + ServiceDiscoveryResult.VER + " TEXT, "
-            + ServiceDiscoveryResult.RESULT + " TEXT, "
-            + "UNIQUE(" + ServiceDiscoveryResult.HASH + ", "
-            + ServiceDiscoveryResult.VER + ") ON CONFLICT REPLACE);";
-
-    private static final String CREATE_PRESENCE_TEMPLATES_STATEMENT = "CREATE TABLE "
-            + PresenceTemplate.TABELNAME + "("
-            + PresenceTemplate.UUID + " TEXT, "
-            + PresenceTemplate.LAST_USED + " NUMBER,"
-            + PresenceTemplate.MESSAGE + " TEXT,"
-            + PresenceTemplate.STATUS + " TEXT,"
-            + "UNIQUE(" + PresenceTemplate.MESSAGE + "," + PresenceTemplate.STATUS + ") ON CONFLICT REPLACE);";
-
-    private static final String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
-            + SQLiteAxolotlStore.PREKEY_TABLENAME + "("
-            + SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
-            + SQLiteAxolotlStore.ID + " INTEGER, "
-            + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
-            + SQLiteAxolotlStore.ACCOUNT
-            + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
-            + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
-            + SQLiteAxolotlStore.ID
-            + ") ON CONFLICT REPLACE"
-            + ");";
-
-    private static final String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE "
-            + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
-            + SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
-            + SQLiteAxolotlStore.ID + " INTEGER, "
-            + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
-            + SQLiteAxolotlStore.ACCOUNT
-            + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
-            + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
-            + SQLiteAxolotlStore.ID
-            + ") ON CONFLICT REPLACE" +
-            ");";
-
-    private static final String CREATE_SESSIONS_STATEMENT = "CREATE TABLE "
-            + SQLiteAxolotlStore.SESSION_TABLENAME + "("
-            + SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
-            + SQLiteAxolotlStore.NAME + " TEXT, "
-            + SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
-            + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
-            + SQLiteAxolotlStore.ACCOUNT
-            + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
-            + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
-            + SQLiteAxolotlStore.NAME + ", "
-            + SQLiteAxolotlStore.DEVICE_ID
-            + ") ON CONFLICT REPLACE"
-            + ");";
-
-    private static final String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE "
-            + SQLiteAxolotlStore.IDENTITIES_TABLENAME + "("
-            + SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
-            + SQLiteAxolotlStore.NAME + " TEXT, "
-            + SQLiteAxolotlStore.OWN + " INTEGER, "
-            + SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
-            + SQLiteAxolotlStore.CERTIFICATE + " BLOB, "
-            + SQLiteAxolotlStore.TRUST + " TEXT, "
-            + SQLiteAxolotlStore.ACTIVE + " NUMBER, "
-            + SQLiteAxolotlStore.LAST_ACTIVATION + " NUMBER,"
-            + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
-            + SQLiteAxolotlStore.ACCOUNT
-            + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
-            + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
-            + SQLiteAxolotlStore.NAME + ", "
-            + SQLiteAxolotlStore.FINGERPRINT
-            + ") ON CONFLICT IGNORE"
-            + ");";
+    private static final String CREATE_CONTATCS_STATEMENT =
+            "create table "
+                    + Contact.TABLENAME
+                    + "("
+                    + Contact.ACCOUNT
+                    + " TEXT, "
+                    + Contact.SERVERNAME
+                    + " TEXT, "
+                    + Contact.SYSTEMNAME
+                    + " TEXT,"
+                    + Contact.PRESENCE_NAME
+                    + " TEXT,"
+                    + Contact.JID
+                    + " TEXT,"
+                    + Contact.KEYS
+                    + " TEXT,"
+                    + Contact.PHOTOURI
+                    + " TEXT,"
+                    + Contact.OPTIONS
+                    + " NUMBER,"
+                    + Contact.SYSTEMACCOUNT
+                    + " NUMBER, "
+                    + Contact.AVATAR
+                    + " TEXT, "
+                    + Contact.LAST_PRESENCE
+                    + " TEXT, "
+                    + Contact.LAST_TIME
+                    + " NUMBER, "
+                    + Contact.RTP_CAPABILITY
+                    + " TEXT,"
+                    + Contact.GROUPS
+                    + " TEXT, FOREIGN KEY("
+                    + Contact.ACCOUNT
+                    + ") REFERENCES "
+                    + Account.TABLENAME
+                    + "("
+                    + Account.UUID
+                    + ") ON DELETE CASCADE, UNIQUE("
+                    + Contact.ACCOUNT
+                    + ", "
+                    + Contact.JID
+                    + ") ON CONFLICT REPLACE);";
+
+    private static final String CREATE_DISCOVERY_RESULTS_STATEMENT =
+            "create table "
+                    + ServiceDiscoveryResult.TABLENAME
+                    + "("
+                    + ServiceDiscoveryResult.HASH
+                    + " TEXT, "
+                    + ServiceDiscoveryResult.VER
+                    + " TEXT, "
+                    + ServiceDiscoveryResult.RESULT
+                    + " TEXT, "
+                    + "UNIQUE("
+                    + ServiceDiscoveryResult.HASH
+                    + ", "
+                    + ServiceDiscoveryResult.VER
+                    + ") ON CONFLICT REPLACE);";
+
+    private static final String CREATE_PRESENCE_TEMPLATES_STATEMENT =
+            "CREATE TABLE "
+                    + PresenceTemplate.TABELNAME
+                    + "("
+                    + PresenceTemplate.UUID
+                    + " TEXT, "
+                    + PresenceTemplate.LAST_USED
+                    + " NUMBER,"
+                    + PresenceTemplate.MESSAGE
+                    + " TEXT,"
+                    + PresenceTemplate.STATUS
+                    + " TEXT,"
+                    + "UNIQUE("
+                    + PresenceTemplate.MESSAGE
+                    + ","
+                    + PresenceTemplate.STATUS
+                    + ") ON CONFLICT REPLACE);";
+
+    private static final String CREATE_PREKEYS_STATEMENT =
+            "CREATE TABLE "
+                    + SQLiteAxolotlStore.PREKEY_TABLENAME
+                    + "("
+                    + SQLiteAxolotlStore.ACCOUNT
+                    + " TEXT,  "
+                    + SQLiteAxolotlStore.ID
+                    + " INTEGER, "
+                    + SQLiteAxolotlStore.KEY
+                    + " TEXT, FOREIGN KEY("
+                    + SQLiteAxolotlStore.ACCOUNT
+                    + ") REFERENCES "
+                    + Account.TABLENAME
+                    + "("
+                    + Account.UUID
+                    + ") ON DELETE CASCADE, "
+                    + "UNIQUE( "
+                    + SQLiteAxolotlStore.ACCOUNT
+                    + ", "
+                    + SQLiteAxolotlStore.ID
+                    + ") ON CONFLICT REPLACE"
+                    + ");";
+
+    private static final String CREATE_SIGNED_PREKEYS_STATEMENT =
+            "CREATE TABLE "
+                    + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME
+                    + "("
+                    + SQLiteAxolotlStore.ACCOUNT
+                    + " TEXT,  "
+                    + SQLiteAxolotlStore.ID
+                    + " INTEGER, "
+                    + SQLiteAxolotlStore.KEY
+                    + " TEXT, FOREIGN KEY("
+                    + SQLiteAxolotlStore.ACCOUNT
+                    + ") REFERENCES "
+                    + Account.TABLENAME
+                    + "("
+                    + Account.UUID
+                    + ") ON DELETE CASCADE, "
+                    + "UNIQUE( "
+                    + SQLiteAxolotlStore.ACCOUNT
+                    + ", "
+                    + SQLiteAxolotlStore.ID
+                    + ") ON CONFLICT REPLACE"
+                    + ");";
+
+    private static final String CREATE_SESSIONS_STATEMENT =
+            "CREATE TABLE "
+                    + SQLiteAxolotlStore.SESSION_TABLENAME
+                    + "("
+                    + SQLiteAxolotlStore.ACCOUNT
+                    + " TEXT,  "
+                    + SQLiteAxolotlStore.NAME
+                    + " TEXT, "
+                    + SQLiteAxolotlStore.DEVICE_ID
+                    + " INTEGER, "
+                    + SQLiteAxolotlStore.KEY
+                    + " TEXT, FOREIGN KEY("
+                    + SQLiteAxolotlStore.ACCOUNT
+                    + ") REFERENCES "
+                    + Account.TABLENAME
+                    + "("
+                    + Account.UUID
+                    + ") ON DELETE CASCADE, "
+                    + "UNIQUE( "
+                    + SQLiteAxolotlStore.ACCOUNT
+                    + ", "
+                    + SQLiteAxolotlStore.NAME
+                    + ", "
+                    + SQLiteAxolotlStore.DEVICE_ID
+                    + ") ON CONFLICT REPLACE"
+                    + ");";
+
+    private static final String CREATE_IDENTITIES_STATEMENT =
+            "CREATE TABLE "
+                    + SQLiteAxolotlStore.IDENTITIES_TABLENAME
+                    + "("
+                    + SQLiteAxolotlStore.ACCOUNT
+                    + " TEXT,  "
+                    + SQLiteAxolotlStore.NAME
+                    + " TEXT, "
+                    + SQLiteAxolotlStore.OWN
+                    + " INTEGER, "
+                    + SQLiteAxolotlStore.FINGERPRINT
+                    + " TEXT, "
+                    + SQLiteAxolotlStore.CERTIFICATE
+                    + " BLOB, "
+                    + SQLiteAxolotlStore.TRUST
+                    + " TEXT, "
+                    + SQLiteAxolotlStore.ACTIVE
+                    + " NUMBER, "
+                    + SQLiteAxolotlStore.LAST_ACTIVATION
+                    + " NUMBER,"
+                    + SQLiteAxolotlStore.KEY
+                    + " TEXT, FOREIGN KEY("
+                    + SQLiteAxolotlStore.ACCOUNT
+                    + ") REFERENCES "
+                    + Account.TABLENAME
+                    + "("
+                    + Account.UUID
+                    + ") ON DELETE CASCADE, "
+                    + "UNIQUE( "
+                    + SQLiteAxolotlStore.ACCOUNT
+                    + ", "
+                    + SQLiteAxolotlStore.NAME
+                    + ", "
+                    + SQLiteAxolotlStore.FINGERPRINT
+                    + ") ON CONFLICT IGNORE"
+                    + ");";
 
     private static final String RESOLVER_RESULTS_TABLENAME = "resolver_results";
 
-    private static final String CREATE_RESOLVER_RESULTS_TABLE = "create table " + RESOLVER_RESULTS_TABLENAME + "("
-            + Resolver.Result.DOMAIN + " TEXT,"
-            + Resolver.Result.HOSTNAME + " TEXT,"
-            + Resolver.Result.IP + " BLOB,"
-            + Resolver.Result.PRIORITY + " NUMBER,"
-            + Resolver.Result.DIRECT_TLS + " NUMBER,"
-            + Resolver.Result.AUTHENTICATED + " NUMBER,"
-            + Resolver.Result.PORT + " NUMBER,"
-            + "UNIQUE(" + Resolver.Result.DOMAIN + ") ON CONFLICT REPLACE"
-            + ");";
-
-    private static final String CREATE_MESSAGE_TIME_INDEX = "CREATE INDEX message_time_index ON " + Message.TABLENAME + "(" + Message.TIME_SENT + ")";
-    private static final String CREATE_MESSAGE_CONVERSATION_INDEX = "CREATE INDEX message_conversation_index ON " + Message.TABLENAME + "(" + Message.CONVERSATION + ")";
-    private static final String CREATE_MESSAGE_DELETED_INDEX = "CREATE INDEX message_deleted_index ON " + Message.TABLENAME + "(" + Message.DELETED + ")";
-    private static final String CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX = "CREATE INDEX message_file_path_index ON " + Message.TABLENAME + "(" + Message.RELATIVE_FILE_PATH + ")";
-    private static final String CREATE_MESSAGE_TYPE_INDEX = "CREATE INDEX message_type_index ON " + Message.TABLENAME + "(" + Message.TYPE + ")";
-
-    private static final String CREATE_MESSAGE_INDEX_TABLE = "CREATE VIRTUAL TABLE messages_index USING fts4 (uuid,body,notindexed=\"uuid\",content=\"" + Message.TABLENAME + "\",tokenize='unicode61')";
-    private static final String CREATE_MESSAGE_INSERT_TRIGGER = "CREATE TRIGGER after_message_insert AFTER INSERT ON " + Message.TABLENAME + " BEGIN INSERT INTO messages_index(rowid,uuid,body) VALUES(NEW.rowid,NEW.uuid,NEW.body); END;";
-    private static final String CREATE_MESSAGE_UPDATE_TRIGGER = "CREATE TRIGGER after_message_update UPDATE OF uuid,body ON " + Message.TABLENAME + " BEGIN UPDATE messages_index SET body=NEW.body,uuid=NEW.uuid WHERE rowid=OLD.rowid; END;";
-    private static final String CREATE_MESSAGE_DELETE_TRIGGER = "CREATE TRIGGER after_message_delete AFTER DELETE ON " + Message.TABLENAME + " BEGIN DELETE FROM messages_index WHERE rowid=OLD.rowid; END;";
-    private static final String COPY_PREEXISTING_ENTRIES = "INSERT INTO messages_index(messages_index) VALUES('rebuild');";
+    private static final String CREATE_RESOLVER_RESULTS_TABLE =
+            "create table "
+                    + RESOLVER_RESULTS_TABLENAME
+                    + "("
+                    + Resolver.Result.DOMAIN
+                    + " TEXT,"
+                    + Resolver.Result.HOSTNAME
+                    + " TEXT,"
+                    + Resolver.Result.IP
+                    + " BLOB,"
+                    + Resolver.Result.PRIORITY
+                    + " NUMBER,"
+                    + Resolver.Result.DIRECT_TLS
+                    + " NUMBER,"
+                    + Resolver.Result.AUTHENTICATED
+                    + " NUMBER,"
+                    + Resolver.Result.PORT
+                    + " NUMBER,"
+                    + "UNIQUE("
+                    + Resolver.Result.DOMAIN
+                    + ") ON CONFLICT REPLACE"
+                    + ");";
+
+    private static final String CREATE_MESSAGE_TIME_INDEX =
+            "CREATE INDEX message_time_index ON "
+                    + Message.TABLENAME
+                    + "("
+                    + Message.TIME_SENT
+                    + ")";
+    private static final String CREATE_MESSAGE_CONVERSATION_INDEX =
+            "CREATE INDEX message_conversation_index ON "
+                    + Message.TABLENAME
+                    + "("
+                    + Message.CONVERSATION
+                    + ")";
+    private static final String CREATE_MESSAGE_DELETED_INDEX =
+            "CREATE INDEX message_deleted_index ON "
+                    + Message.TABLENAME
+                    + "("
+                    + Message.DELETED
+                    + ")";
+    private static final String CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX =
+            "CREATE INDEX message_file_path_index ON "
+                    + Message.TABLENAME
+                    + "("
+                    + Message.RELATIVE_FILE_PATH
+                    + ")";
+    private static final String CREATE_MESSAGE_TYPE_INDEX =
+            "CREATE INDEX message_type_index ON " + Message.TABLENAME + "(" + Message.TYPE + ")";
+
+    private static final String CREATE_MESSAGE_INDEX_TABLE =
+            "CREATE VIRTUAL TABLE messages_index USING fts4"
+                    + " (uuid,body,notindexed=\"uuid\",content=\""
+                    + Message.TABLENAME
+                    + "\",tokenize='unicode61')";
+    private static final String CREATE_MESSAGE_INSERT_TRIGGER =
+            "CREATE TRIGGER after_message_insert AFTER INSERT ON "
+                    + Message.TABLENAME
+                    + " BEGIN INSERT INTO messages_index(rowid,uuid,body)"
+                    + " VALUES(NEW.rowid,NEW.uuid,NEW.body); END;";
+    private static final String CREATE_MESSAGE_UPDATE_TRIGGER =
+            "CREATE TRIGGER after_message_update UPDATE OF uuid,body ON "
+                    + Message.TABLENAME
+                    + " BEGIN UPDATE messages_index SET body=NEW.body,uuid=NEW.uuid WHERE"
+                    + " rowid=OLD.rowid; END;";
+    private static final String CREATE_MESSAGE_DELETE_TRIGGER =
+            "CREATE TRIGGER after_message_delete AFTER DELETE ON "
+                    + Message.TABLENAME
+                    + " BEGIN DELETE FROM messages_index WHERE rowid=OLD.rowid; END;";
+    private static final String COPY_PREEXISTING_ENTRIES =
+            "INSERT INTO messages_index(messages_index) VALUES('rebuild');";
 
     protected Context context;
 
@@ -200,7 +373,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
         setWriteAheadLoggingEnabled(true);
     }
 
-    private static ContentValues createFingerprintStatusContentValues(FingerprintStatus.Trust trust, boolean active) {
+    private static ContentValues createFingerprintStatusContentValues(
+            FingerprintStatus.Trust trust, boolean active) {
         ContentValues values = new ContentValues();
         values.put(SQLiteAxolotlStore.TRUST, trust.toString());
         values.put(SQLiteAxolotlStore.ACTIVE, active ? 1 : 0);
@@ -215,7 +389,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
         final SQLiteDatabase db = getWritableDatabase();
         final Stopwatch stopwatch = Stopwatch.createStarted();
         db.execSQL(COPY_PREEXISTING_ENTRIES);
-        Log.d(Config.LOGTAG,"rebuilt message index in "+ stopwatch.stop().toString());
+        Log.d(Config.LOGTAG, "rebuilt message index in " + stopwatch.stop().toString());
     }
 
     public static synchronized DatabaseBackend getInstance(Context context) {
@@ -387,57 +561,132 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 
     @Override
     public void onCreate(SQLiteDatabase db) {
-        db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID + " TEXT PRIMARY KEY,"
-                + Account.USERNAME + " TEXT,"
-                + Account.SERVER + " TEXT,"
-                + Account.PASSWORD + " TEXT,"
-                + Account.DISPLAY_NAME + " TEXT, "
-                + Account.STATUS + " TEXT,"
-                + Account.STATUS_MESSAGE + " TEXT,"
-                + Account.ROSTERVERSION + " TEXT,"
-                + Account.OPTIONS + " NUMBER, "
-                + Account.AVATAR + " TEXT, "
-                + Account.KEYS + " TEXT, "
-                + Account.HOSTNAME + " TEXT, "
-                + Account.RESOURCE + " TEXT,"
-                + Account.PINNED_MECHANISM + " TEXT,"
-                + Account.PINNED_CHANNEL_BINDING + " TEXT,"
-                + Account.FAST_MECHANISM + " TEXT,"
-                + Account.FAST_TOKEN + " TEXT,"
-                + Account.PORT + " NUMBER DEFAULT 5222)");
-        db.execSQL("create table " + Conversation.TABLENAME + " ("
-                + Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
-                + " TEXT, " + Conversation.CONTACT + " TEXT, "
-                + Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACTJID
-                + " TEXT, " + Conversation.CREATED + " NUMBER, "
-                + Conversation.STATUS + " NUMBER, " + Conversation.MODE
-                + " NUMBER, " + Conversation.ATTRIBUTES + " TEXT, FOREIGN KEY("
-                + Conversation.ACCOUNT + ") REFERENCES " + Account.TABLENAME
-                + "(" + Account.UUID + ") ON DELETE CASCADE);");
-        db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID
-                + " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, "
-                + Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART
-                + " TEXT, " + Message.TRUE_COUNTERPART + " TEXT,"
-                + Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, "
-                + Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
-                + Message.RELATIVE_FILE_PATH + " TEXT, "
-                + Message.SERVER_MSG_ID + " TEXT, "
-                + Message.FINGERPRINT + " TEXT, "
-                + Message.CARBON + " INTEGER, "
-                + Message.EDITED + " TEXT, "
-                + Message.READ + " NUMBER DEFAULT 1, "
-                + Message.OOB + " INTEGER, "
-                + Message.ERROR_MESSAGE + " TEXT,"
-                + Message.READ_BY_MARKERS + " TEXT,"
-                + Message.MARKABLE + " NUMBER DEFAULT 0,"
-                + Message.DELETED + " NUMBER DEFAULT 0,"
-                + Message.BODY_LANGUAGE + " TEXT,"
-                + Message.OCCUPANT_ID + " TEXT,"
-                + Message.REACTIONS + " TEXT,"
-                + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
-                + Message.CONVERSATION + ") REFERENCES "
-                + Conversation.TABLENAME + "(" + Conversation.UUID
-                + ") ON DELETE CASCADE);");
+        db.execSQL(
+                "create table "
+                        + Account.TABLENAME
+                        + "("
+                        + Account.UUID
+                        + " TEXT PRIMARY KEY,"
+                        + Account.USERNAME
+                        + " TEXT,"
+                        + Account.SERVER
+                        + " TEXT,"
+                        + Account.PASSWORD
+                        + " TEXT,"
+                        + Account.DISPLAY_NAME
+                        + " TEXT, "
+                        + Account.STATUS
+                        + " TEXT,"
+                        + Account.STATUS_MESSAGE
+                        + " TEXT,"
+                        + Account.ROSTERVERSION
+                        + " TEXT,"
+                        + Account.OPTIONS
+                        + " NUMBER, "
+                        + Account.AVATAR
+                        + " TEXT, "
+                        + Account.KEYS
+                        + " TEXT, "
+                        + Account.HOSTNAME
+                        + " TEXT, "
+                        + Account.RESOURCE
+                        + " TEXT,"
+                        + Account.PINNED_MECHANISM
+                        + " TEXT,"
+                        + Account.PINNED_CHANNEL_BINDING
+                        + " TEXT,"
+                        + Account.FAST_MECHANISM
+                        + " TEXT,"
+                        + Account.FAST_TOKEN
+                        + " TEXT,"
+                        + Account.PORT
+                        + " NUMBER DEFAULT 5222)");
+        db.execSQL(
+                "create table "
+                        + Conversation.TABLENAME
+                        + " ("
+                        + Conversation.UUID
+                        + " TEXT PRIMARY KEY, "
+                        + Conversation.NAME
+                        + " TEXT, "
+                        + Conversation.CONTACT
+                        + " TEXT, "
+                        + Conversation.ACCOUNT
+                        + " TEXT, "
+                        + Conversation.CONTACTJID
+                        + " TEXT, "
+                        + Conversation.CREATED
+                        + " NUMBER, "
+                        + Conversation.STATUS
+                        + " NUMBER, "
+                        + Conversation.MODE
+                        + " NUMBER, "
+                        + Conversation.ATTRIBUTES
+                        + " TEXT, FOREIGN KEY("
+                        + Conversation.ACCOUNT
+                        + ") REFERENCES "
+                        + Account.TABLENAME
+                        + "("
+                        + Account.UUID
+                        + ") ON DELETE CASCADE);");
+        db.execSQL(
+                "create table "
+                        + Message.TABLENAME
+                        + "( "
+                        + Message.UUID
+                        + " TEXT PRIMARY KEY, "
+                        + Message.CONVERSATION
+                        + " TEXT, "
+                        + Message.TIME_SENT
+                        + " NUMBER, "
+                        + Message.COUNTERPART
+                        + " TEXT, "
+                        + Message.TRUE_COUNTERPART
+                        + " TEXT,"
+                        + Message.BODY
+                        + " TEXT, "
+                        + Message.ENCRYPTION
+                        + " NUMBER, "
+                        + Message.STATUS
+                        + " NUMBER,"
+                        + Message.TYPE
+                        + " NUMBER, "
+                        + Message.RELATIVE_FILE_PATH
+                        + " TEXT, "
+                        + Message.SERVER_MSG_ID
+                        + " TEXT, "
+                        + Message.FINGERPRINT
+                        + " TEXT, "
+                        + Message.CARBON
+                        + " INTEGER, "
+                        + Message.EDITED
+                        + " TEXT, "
+                        + Message.READ
+                        + " NUMBER DEFAULT 1, "
+                        + Message.OOB
+                        + " INTEGER, "
+                        + Message.ERROR_MESSAGE
+                        + " TEXT,"
+                        + Message.READ_BY_MARKERS
+                        + " TEXT,"
+                        + Message.MARKABLE
+                        + " NUMBER DEFAULT 0,"
+                        + Message.DELETED
+                        + " NUMBER DEFAULT 0,"
+                        + Message.BODY_LANGUAGE
+                        + " TEXT,"
+                        + Message.OCCUPANT_ID
+                        + " TEXT,"
+                        + Message.REACTIONS
+                        + " TEXT,"
+                        + Message.REMOTE_MSG_ID
+                        + " TEXT, FOREIGN KEY("
+                        + Message.CONVERSATION
+                        + ") REFERENCES "
+                        + Conversation.TABLENAME
+                        + "("
+                        + Conversation.UUID
+                        + ") ON DELETE CASCADE);");
         db.execSQL(CREATE_MESSAGE_TIME_INDEX);
         db.execSQL(CREATE_MESSAGE_CONVERSATION_INDEX);
         db.execSQL(CREATE_MESSAGE_DELETED_INDEX);
@@ -460,54 +709,87 @@ public class DatabaseBackend extends SQLiteOpenHelper {
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
         if (oldVersion < 2 && newVersion >= 2) {
-            db.execSQL("update " + Account.TABLENAME + " set "
-                    + Account.OPTIONS + " = " + Account.OPTIONS + " | 8");
+            db.execSQL(
+                    "update "
+                            + Account.TABLENAME
+                            + " set "
+                            + Account.OPTIONS
+                            + " = "
+                            + Account.OPTIONS
+                            + " | 8");
         }
         if (oldVersion < 3 && newVersion >= 3) {
-            db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
-                    + Message.TYPE + " NUMBER");
+            db.execSQL(
+                    "ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.TYPE + " NUMBER");
         }
         if (oldVersion < 5 && newVersion >= 5) {
             db.execSQL("DROP TABLE " + Contact.TABLENAME);
             db.execSQL(CREATE_CONTATCS_STATEMENT);
-            db.execSQL("UPDATE " + Account.TABLENAME + " SET "
-                    + Account.ROSTERVERSION + " = NULL");
+            db.execSQL("UPDATE " + Account.TABLENAME + " SET " + Account.ROSTERVERSION + " = NULL");
         }
         if (oldVersion < 6 && newVersion >= 6) {
-            db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
-                    + Message.TRUE_COUNTERPART + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Message.TABLENAME
+                            + " ADD COLUMN "
+                            + Message.TRUE_COUNTERPART
+                            + " TEXT");
         }
         if (oldVersion < 7 && newVersion >= 7) {
-            db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
-                    + Message.REMOTE_MSG_ID + " TEXT");
-            db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
-                    + Contact.AVATAR + " TEXT");
-            db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN "
-                    + Account.AVATAR + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Message.TABLENAME
+                            + " ADD COLUMN "
+                            + Message.REMOTE_MSG_ID
+                            + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.AVATAR + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.AVATAR + " TEXT");
         }
         if (oldVersion < 8 && newVersion >= 8) {
-            db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN "
-                    + Conversation.ATTRIBUTES + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Conversation.TABLENAME
+                            + " ADD COLUMN "
+                            + Conversation.ATTRIBUTES
+                            + " TEXT");
         }
         if (oldVersion < 9 && newVersion >= 9) {
-            db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
-                    + Contact.LAST_TIME + " NUMBER");
-            db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
-                    + Contact.LAST_PRESENCE + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Contact.TABLENAME
+                            + " ADD COLUMN "
+                            + Contact.LAST_TIME
+                            + " NUMBER");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Contact.TABLENAME
+                            + " ADD COLUMN "
+                            + Contact.LAST_PRESENCE
+                            + " TEXT");
         }
         if (oldVersion < 10 && newVersion >= 10) {
-            db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
-                    + Message.RELATIVE_FILE_PATH + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Message.TABLENAME
+                            + " ADD COLUMN "
+                            + Message.RELATIVE_FILE_PATH
+                            + " TEXT");
         }
         if (oldVersion < 11 && newVersion >= 11) {
-            db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
-                    + Contact.GROUPS + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.GROUPS + " TEXT");
             db.execSQL("delete from " + Contact.TABLENAME);
             db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL");
         }
         if (oldVersion < 12 && newVersion >= 12) {
-            db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
-                    + Message.SERVER_MSG_ID + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Message.TABLENAME
+                            + " ADD COLUMN "
+                            + Message.SERVER_MSG_ID
+                            + " TEXT");
         }
         if (oldVersion < 13 && newVersion >= 13) {
             db.execSQL("delete from " + Contact.TABLENAME);
@@ -518,26 +800,60 @@ public class DatabaseBackend extends SQLiteOpenHelper {
         }
         if (oldVersion < 15 && newVersion >= 15) {
             recreateAxolotlDb(db);
-            db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
-                    + Message.FINGERPRINT + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Message.TABLENAME
+                            + " ADD COLUMN "
+                            + Message.FINGERPRINT
+                            + " TEXT");
         }
         if (oldVersion < 16 && newVersion >= 16) {
-            db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
-                    + Message.CARBON + " INTEGER");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Message.TABLENAME
+                            + " ADD COLUMN "
+                            + Message.CARBON
+                            + " INTEGER");
         }
         if (oldVersion < 19 && newVersion >= 19) {
-            db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.DISPLAY_NAME + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Account.TABLENAME
+                            + " ADD COLUMN "
+                            + Account.DISPLAY_NAME
+                            + " TEXT");
         }
         if (oldVersion < 20 && newVersion >= 20) {
-            db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT");
-            db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Account.TABLENAME
+                            + " ADD COLUMN "
+                            + Account.HOSTNAME
+                            + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Account.TABLENAME
+                            + " ADD COLUMN "
+                            + Account.PORT
+                            + " NUMBER DEFAULT 5222");
         }
         if (oldVersion < 26 && newVersion >= 26) {
-            db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS + " TEXT");
-            db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS_MESSAGE + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Account.TABLENAME
+                            + " ADD COLUMN "
+                            + Account.STATUS_MESSAGE
+                            + " TEXT");
         }
         if (oldVersion < 40 && newVersion >= 40) {
-            db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.RESOURCE + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Account.TABLENAME
+                            + " ADD COLUMN "
+                            + Account.RESOURCE
+                            + " TEXT");
         }
         /* Any migrations that alter the Account table need to happen BEFORE this migration, as it
          * depends on account de-serialization.
@@ -545,45 +861,67 @@ public class DatabaseBackend extends SQLiteOpenHelper {
         if (oldVersion < 17 && newVersion >= 17 && newVersion < 31) {
             List<Account> accounts = getAccounts(db);
             for (Account account : accounts) {
-                String ownDeviceIdString = account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID);
+                String ownDeviceIdString =
+                        account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID);
                 if (ownDeviceIdString == null) {
                     continue;
                 }
                 int ownDeviceId = Integer.valueOf(ownDeviceIdString);
-                SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), ownDeviceId);
+                SignalProtocolAddress ownAddress =
+                        new SignalProtocolAddress(
+                                account.getJid().asBareJid().toString(), ownDeviceId);
                 deleteSession(db, account, ownAddress);
                 IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account);
                 if (identityKeyPair != null) {
                     String[] selectionArgs = {
-                            account.getUuid(),
-                            CryptoHelper.bytesToHex(identityKeyPair.getPublicKey().serialize())
+                        account.getUuid(),
+                        CryptoHelper.bytesToHex(identityKeyPair.getPublicKey().serialize())
                     };
                     ContentValues values = new ContentValues();
                     values.put(SQLiteAxolotlStore.TRUSTED, 2);
-                    db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
-                            SQLiteAxolotlStore.ACCOUNT + " = ? AND "
-                                    + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
+                    db.update(
+                            SQLiteAxolotlStore.IDENTITIES_TABLENAME,
+                            values,
+                            SQLiteAxolotlStore.ACCOUNT
+                                    + " = ? AND "
+                                    + SQLiteAxolotlStore.FINGERPRINT
+                                    + " = ? ",
                             selectionArgs);
                 } else {
-                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not load own identity key pair");
+                    Log.d(
+                            Config.LOGTAG,
+                            account.getJid().asBareJid()
+                                    + ": could not load own identity key pair");
                 }
             }
         }
         if (oldVersion < 18 && newVersion >= 18) {
-            db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ + " NUMBER DEFAULT 1");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Message.TABLENAME
+                            + " ADD COLUMN "
+                            + Message.READ
+                            + " NUMBER DEFAULT 1");
         }
 
         if (oldVersion < 21 && newVersion >= 21) {
             List<Account> accounts = getAccounts(db);
             for (Account account : accounts) {
                 account.unsetPgpSignature();
-                db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
-                        + "=?", new String[]{account.getUuid()});
+                db.update(
+                        Account.TABLENAME,
+                        account.getContentValues(),
+                        Account.UUID + "=?",
+                        new String[] {account.getUuid()});
             }
         }
 
         if (oldVersion >= 15 && oldVersion < 22 && newVersion >= 22) {
-            db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE);
+            db.execSQL(
+                    "ALTER TABLE "
+                            + SQLiteAxolotlStore.IDENTITIES_TABLENAME
+                            + " ADD COLUMN "
+                            + SQLiteAxolotlStore.CERTIFICATE);
         }
 
         if (oldVersion < 23 && newVersion >= 23) {
@@ -591,11 +929,13 @@ public class DatabaseBackend extends SQLiteOpenHelper {
         }
 
         if (oldVersion < 24 && newVersion >= 24) {
-            db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT");
         }
 
         if (oldVersion < 25 && newVersion >= 25) {
-            db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OOB + " INTEGER");
+            db.execSQL(
+                    "ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OOB + " INTEGER");
         }
 
         if (oldVersion < 26 && newVersion >= 26) {
@@ -611,51 +951,116 @@ public class DatabaseBackend extends SQLiteOpenHelper {
         }
 
         if (oldVersion < 29 && newVersion >= 29) {
-            db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.ERROR_MESSAGE + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + Message.TABLENAME
+                            + " ADD COLUMN "
+                            + Message.ERROR_MESSAGE
+                            + " TEXT");
         }
         if (oldVersion >= 15 && oldVersion < 31 && newVersion >= 31) {
-            db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.TRUST + " TEXT");
-            db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.ACTIVE + " NUMBER");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + SQLiteAxolotlStore.IDENTITIES_TABLENAME
+                            + " ADD COLUMN "
+                            + SQLiteAxolotlStore.TRUST
+                            + " TEXT");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + SQLiteAxolotlStore.IDENTITIES_TABLENAME
+                            + " ADD COLUMN "
+                            + SQLiteAxolotlStore.ACTIVE
+                            + " NUMBER");
             HashMap<Integer, ContentValues> migration = new HashMap<>();
-            migration.put(0, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true));
-            migration.put(1, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true));
-            migration.put(2, createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, true));
-            migration.put(3, createFingerprintStatusContentValues(FingerprintStatus.Trust.COMPROMISED, false));
-            migration.put(4, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false));
-            migration.put(5, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false));
-            migration.put(6, createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, false));
-            migration.put(7, createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, true));
-            migration.put(8, createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, false));
+            migration.put(
+                    0, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true));
+            migration.put(
+                    1, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true));
+            migration.put(
+                    2,
+                    createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, true));
+            migration.put(
+                    3,
+                    createFingerprintStatusContentValues(
+                            FingerprintStatus.Trust.COMPROMISED, false));
+            migration.put(
+                    4,
+                    createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false));
+            migration.put(
+                    5,
+                    createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false));
+            migration.put(
+                    6,
+                    createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, false));
+            migration.put(
+                    7,
+                    createFingerprintStatusContentValues(
+                            FingerprintStatus.Trust.VERIFIED_X509, true));
+            migration.put(
+                    8,
+                    createFingerprintStatusContentValues(
+                            FingerprintStatus.Trust.VERIFIED_X509, false));
             for (Map.Entry<Integer, ContentValues> entry : migration.entrySet()) {
                 String whereClause = SQLiteAxolotlStore.TRUSTED + "=?";
                 String[] where = {String.valueOf(entry.getKey())};
-                db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, entry.getValue(), whereClause, where);
+                db.update(
+                        SQLiteAxolotlStore.IDENTITIES_TABLENAME,
+                        entry.getValue(),
+                        whereClause,
+                        where);
             }
-
         }
         if (oldVersion >= 15 && oldVersion < 32 && newVersion >= 32) {
-            db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.LAST_ACTIVATION + " NUMBER");
+            db.execSQL(
+                    "ALTER TABLE "
+                            + SQLiteAxolotlStore.IDENTITIES_TABLENAME
+                            + " ADD COLUMN "
+                            + SQLiteAxolotlStore.LAST_ACTIVATION
+                            + " NUMBER");
             ContentValues defaults = new ContentValues();
             defaults.put(SQLiteAxolotlStore.LAST_ACTIVATION, System.currentTimeMillis());
             db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, defaults, null, null);
         }
         if (oldVersion >= 15 && oldVersion < 33 && newVersion >= 33) {
             String whereClause = SQLiteAxolotlStore.OWN + "=1";
-            db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED, true), whereClause, null);
+            db.update(
+                    SQLiteAxolotlStore.IDENTITIES_TABLENAME,
+                    createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED, true),
+                    whereClause,
+                    null);
         }
 
         if (oldVersion < 34 && newVersion >= 34) {
             db.execSQL(CREATE_MESSAGE_TIME_INDEX);
 
-            final File oldPicturesDirectory = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/Conversations/");
-            final File oldFilesDirectory = new File(Environment.getExternalStorageDirectory() + "/Conversations/");
-            final File newFilesDirectory = new File(Environment.getExternalStorageDirectory() + "/Conversations/Media/Conversations Files/");
-            final File newVideosDirectory = new File(Environment.getExternalStorageDirectory() + "/Conversations/Media/Conversations Videos/");
+            final File oldPicturesDirectory =
+                    new File(
+                            Environment.getExternalStoragePublicDirectory(
+                                            Environment.DIRECTORY_PICTURES)
+                                    + "/Conversations/");
+            final File oldFilesDirectory =
+                    new File(Environment.getExternalStorageDirectory() + "/Conversations/");
+            final File newFilesDirectory =
+                    new File(
+                            Environment.getExternalStorageDirectory()
+                                    + "/Conversations/Media/Conversations Files/");
+            final File newVideosDirectory =
+                    new File(
+                            Environment.getExternalStorageDirectory()
+                                    + "/Conversations/Media/Conversations Videos/");
             if (oldPicturesDirectory.exists() && oldPicturesDirectory.isDirectory()) {
-                final File newPicturesDirectory = new File(Environment.getExternalStorageDirectory() + "/Conversations/Media/Conversations Images/");
+                final File newPicturesDirectory =
+                        new File(
+                                Environment.getExternalStorageDirectory()
+                                        + "/Conversations/Media/Conversations Images/");
                 newPicturesDirectory.getParentFile().mkdirs();
                 if (oldPicturesDirectory.renameTo(newPicturesDirectory)) {
-                    Log.d(Config.LOGTAG, "moved " + oldPicturesDirectory.getAbsolutePath() + " to " + newPicturesDirectory.getAbsolutePath());
+                    Log.d(
+                            Config.LOGTAG,
+                            "moved "
+                                    + oldPicturesDirectory.getAbsolutePath()
+                                    + " to "
+                                    + newPicturesDirectory.getAbsolutePath());
                 }
             }
             if (oldFilesDirectory.exists() && oldFilesDirectory.isDirectory()) {
  
  
  
    
    @@ -33,7 +33,6 @@ import android.util.DisplayMetrics;
 import android.util.Pair;
 import android.util.Log;
 import android.util.LruCache;
-
 import androidx.annotation.RequiresApi;
 import androidx.annotation.StringRes;
 import androidx.core.content.FileProvider;
@@ -170,7 +169,8 @@ public class FileBackend {
                     if (dimensions.getMin() > 720) {
                         Log.d(
                                 Config.LOGTAG,
-                                "do not consider video file with min width larger than 720 for size check");
+                                "do not consider video file with min width larger than 720 for size"
+                                        + " check");
                         continue;
                     }
                 } catch (final IOException | NotAVideoFile e) {
@@ -300,7 +300,8 @@ public class FileBackend {
         return inSampleSize;
     }
 
-    private static Dimensions getVideoDimensions(Context context, Uri uri) throws NotAVideoFile, IOException {
+    private static Dimensions getVideoDimensions(Context context, Uri uri)
+            throws NotAVideoFile, IOException {
         MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
         try {
             mediaMetadataRetriever.setDataSource(context, uri);
@@ -786,10 +787,10 @@ public class FileBackend {
         return is;
     }
 
-    public void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
+    public void copyFileToPrivateStorage(final File file, final Uri uri) throws FileCopyException {
         final var parentDirectory = file.getParentFile();
         if (parentDirectory != null && parentDirectory.mkdirs()) {
-            Log.d(Config.LOGTAG,"created directory "+parentDirectory.getAbsolutePath());
+            Log.d(Config.LOGTAG, "created directory " + parentDirectory.getAbsolutePath());
         }
         try {
             if (!file.createNewFile() && file.length() > 0) {
@@ -829,9 +830,10 @@ public class FileBackend {
         }
     }
 
-    public void copyFileToPrivateStorage(Message message, Uri uri, String type)
+    public void copyFileToPrivateStorage(final Message message, final Uri uri, final String type)
             throws FileCopyException {
-        final String mime = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
+        final String mime =
+                MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
         Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime=" + mime + ")");
         String extension = MimeUtils.guessExtensionFromMimeType(mime);
         if (extension == null) {
@@ -2003,7 +2005,7 @@ public class FileBackend {
         return calcSampleSize(options, size);
     }
 
-    public void updateFileParams(Message message) {
+    public void updateFileParams(final Message message) {
         updateFileParams(message, null);
     }
 
  
  
  
    
    @@ -5,18 +5,9 @@ import android.content.SharedPreferences;
 import android.net.Uri;
 import android.preference.PreferenceManager;
 import android.util.Log;
-
 import androidx.annotation.NonNull;
-
 import com.otaliastudios.transcoder.Transcoder;
 import com.otaliastudios.transcoder.TranscoderListener;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.Objects;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.crypto.PgpEngine;
@@ -26,6 +17,11 @@ import eu.siacs.conversations.persistance.FileBackend;
 import eu.siacs.conversations.ui.UiCallback;
 import eu.siacs.conversations.utils.MimeUtils;
 import eu.siacs.conversations.utils.TranscoderStrategies;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 
 public class AttachFileToConversationRunnable implements Runnable, TranscoderListener {
 
@@ -39,16 +35,26 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
     private final long originalFileSize;
     private int currentProgress = -1;
 
-    AttachFileToConversationRunnable(XmppConnectionService xmppConnectionService, Uri uri, String type, Message message, UiCallback<Message> callback) {
+    AttachFileToConversationRunnable(
+            XmppConnectionService xmppConnectionService,
+            Uri uri,
+            String type,
+            Message message,
+            UiCallback<Message> callback) {
         this.uri = uri;
         this.type = type;
         this.mXmppConnectionService = xmppConnectionService;
         this.message = message;
         this.callback = callback;
-        mimeType = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
-        final int autoAcceptFileSize = mXmppConnectionService.getResources().getInteger(R.integer.auto_accept_filesize);
+        mimeType =
+                MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
+        final int autoAcceptFileSize =
+                mXmppConnectionService.getResources().getInteger(R.integer.auto_accept_filesize);
         this.originalFileSize = FileBackend.getFileSize(mXmppConnectionService, uri);
-        this.isVideoMessage = (mimeType != null && mimeType.startsWith("video/")) && originalFileSize > autoAcceptFileSize && !"uncompressed".equals(getVideoCompression());
+        this.isVideoMessage =
+                (mimeType != null && mimeType.startsWith("video/"))
+                        && originalFileSize > autoAcceptFileSize
+                        && !"uncompressed".equals(getVideoCompression());
     }
 
     boolean isVideoMessage() {
@@ -75,7 +81,9 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
             }
         } else {
             try {
-                mXmppConnectionService.getFileBackend().copyFileToPrivateStorage(message, uri, type);
+                mXmppConnectionService
+                        .getFileBackend()
+                        .copyFileToPrivateStorage(message, uri, type);
                 mXmppConnectionService.getFileBackend().updateFileParams(message);
                 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
                     final PgpEngine pgpEngine = mXmppConnectionService.getPgpEngine();
@@ -87,16 +95,26 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
                 } else {
                     mXmppConnectionService.sendMessage(message, () -> callback.success(message));
                 }
-            } catch (FileBackend.FileCopyException e) {
+            } catch (final FileBackend.FileCopyException e) {
                 callback.error(e.getResId(), message);
             }
         }
     }
 
+    private void fallbackToProcessAsFile() {
+        final var file = mXmppConnectionService.getFileBackend().getFile(message);
+        if (file.exists() && file.delete()) {
+            Log.d(Config.LOGTAG, "deleted preexisting file " + file.getAbsolutePath());
+        }
+        XmppConnectionService.FILE_ATTACHMENT_EXECUTOR.execute(this::processAsFile);
+    }
+
     private void processAsVideo() throws FileNotFoundException {
         Log.d(Config.LOGTAG, "processing file as video");
         mXmppConnectionService.startOngoingVideoTranscodingForegroundNotification();
-        mXmppConnectionService.getFileBackend().setupRelativeFilePath(message, String.format("%s.%s", message.getUuid(), "mp4"));
+        mXmppConnectionService
+                .getFileBackend()
+                .setupRelativeFilePath(message, String.format("%s.%s", message.getUuid(), "mp4"));
         final DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
         if (Objects.requireNonNull(file.getParentFile()).mkdirs()) {
             Log.d(Config.LOGTAG, "created parent directory for video file");
@@ -106,16 +124,23 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
 
         final Future<Void> future;
         try {
-            future = Transcoder.into(file.getAbsolutePath()).
-                addDataSource(mXmppConnectionService, uri)
-                .setVideoTrackStrategy(highQuality ? TranscoderStrategies.VIDEO_720P : TranscoderStrategies.VIDEO_360P)
-                .setAudioTrackStrategy(highQuality ? TranscoderStrategies.AUDIO_HQ : TranscoderStrategies.AUDIO_MQ)
-                .setListener(this)
-                .transcode();
+            future =
+                    Transcoder.into(file.getAbsolutePath())
+                            .addDataSource(mXmppConnectionService, uri)
+                            .setVideoTrackStrategy(
+                                    highQuality
+                                            ? TranscoderStrategies.VIDEO_720P
+                                            : TranscoderStrategies.VIDEO_360P)
+                            .setAudioTrackStrategy(
+                                    highQuality
+                                            ? TranscoderStrategies.AUDIO_HQ
+                                            : TranscoderStrategies.AUDIO_MQ)
+                            .setListener(this)
+                            .transcode();
         } catch (final RuntimeException e) {
             // transcode can already throw if there is an invalid file format or a platform bug
             mXmppConnectionService.stopOngoingVideoTranscodingForegroundNotification();
-            processAsFile();
+            fallbackToProcessAsFile();
             return;
         }
         try {
@@ -125,9 +150,9 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
         } catch (final ExecutionException e) {
             if (e.getCause() instanceof Error) {
                 mXmppConnectionService.stopOngoingVideoTranscodingForegroundNotification();
-                processAsFile();
+                fallbackToProcessAsFile();
             } else {
-                Log.d(Config.LOGTAG, "ignoring execution exception. Should get handled by onTranscodeFiled() instead", e);
+                Log.d(Config.LOGTAG, "ignoring execution exception. Handled by onTranscodeFiled()");
             }
         }
     }
@@ -137,7 +162,9 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
         final int p = (int) Math.round(progress * 100);
         if (p > currentProgress) {
             currentProgress = p;
-            mXmppConnectionService.getNotificationService().updateFileAddingNotification(p, message);
+            mXmppConnectionService
+                    .getNotificationService()
+                    .updateFileAddingNotification(p, message);
         }
     }
 
@@ -146,11 +173,15 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
         mXmppConnectionService.stopOngoingVideoTranscodingForegroundNotification();
         final File file = mXmppConnectionService.getFileBackend().getFile(message);
         long convertedFileSize = mXmppConnectionService.getFileBackend().getFile(message).getSize();
-        Log.d(Config.LOGTAG, "originalFileSize=" + originalFileSize + " convertedFileSize=" + convertedFileSize);
+        Log.d(
+                Config.LOGTAG,
+                "originalFileSize=" + originalFileSize + " convertedFileSize=" + convertedFileSize);
         if (originalFileSize != 0 && convertedFileSize >= originalFileSize) {
             if (file.delete()) {
-                Log.d(Config.LOGTAG, "original file size was smaller. deleting and processing as file");
-                processAsFile();
+                Log.d(
+                        Config.LOGTAG,
+                        "original file size was smaller. deleting and processing as file");
+                fallbackToProcessAsFile();
                 return;
             } else {
                 Log.d(Config.LOGTAG, "unable to delete converted file");
@@ -167,14 +198,14 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
     @Override
     public void onTranscodeCanceled() {
         mXmppConnectionService.stopOngoingVideoTranscodingForegroundNotification();
-        processAsFile();
+        fallbackToProcessAsFile();
     }
 
     @Override
     public void onTranscodeFailed(@NonNull final Throwable exception) {
         mXmppConnectionService.stopOngoingVideoTranscodingForegroundNotification();
         Log.d(Config.LOGTAG, "video transcoding failed", exception);
-        processAsFile();
+        fallbackToProcessAsFile();
     }
 
     @Override
@@ -182,7 +213,7 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
         if (this.isVideoMessage()) {
             try {
                 processAsVideo();
-            } catch (FileNotFoundException e) {
+            } catch (final FileNotFoundException e) {
                 processAsFile();
             }
         } else {
@@ -195,7 +226,9 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
     }
 
     public static String getVideoCompression(final Context context) {
-        final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
-        return preferences.getString("video_compression", context.getResources().getString(R.string.video_compression));
+        final SharedPreferences preferences =
+                PreferenceManager.getDefaultSharedPreferences(context);
+        return preferences.getString(
+                "video_compression", context.getResources().getString(R.string.video_compression));
     }
 }
  
  
  
    
    @@ -2,7 +2,6 @@ package eu.siacs.conversations.services;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.ToneGenerator;
 import android.net.Uri;
@@ -12,22 +11,18 @@ import android.telecom.CallEndpoint;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.util.Log;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
-
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-
+import eu.siacs.conversations.AppSettings;
 import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
 import eu.siacs.conversations.ui.util.MainThreadExecutor;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
 import eu.siacs.conversations.xmpp.jingle.Media;
-
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -46,7 +41,7 @@ public class CallIntegration extends Connection {
      * SecurityException
      */
     private static final List<String> BROKEN_DEVICE_MODELS =
-            Arrays.asList("gtaxlwifi", "a5y17lte", "YT-X705F");
+            Arrays.asList("gtaxlwifi", "a5y17lte", "YT-X705F", "HWAGS2");
 
     /**
      * all Realme devices at least up to and including Android 11 are broken
@@ -63,7 +58,6 @@ public class CallIntegration extends Connection {
             Arrays.asList("realme", "oppo", "oneplus");
 
     public static final int DEFAULT_TONE_VOLUME = 60;
-    private static final int DEFAULT_MEDIA_PLAYER_VOLUME = 90;
 
     private final Context context;
 
@@ -383,7 +377,7 @@ public class CallIntegration extends Connection {
             }
         }
         if (state == STATE_ACTIVE) {
-            playConnectedSound();
+            startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_ANSWER, 100);
         } else if (state == STATE_DISCONNECTED) {
             final var audioManager = this.appRTCAudioManager;
             if (audioManager != null) {
@@ -392,26 +386,10 @@ public class CallIntegration extends Connection {
         }
     }
 
-    private void playConnectedSound() {
-        final var audioAttributes =
-                new AudioAttributes.Builder()
-                        .setLegacyStreamType(AudioManager.STREAM_VOICE_CALL)
-                        .build();
-        final var mediaPlayer =
-                MediaPlayer.create(
-                        context,
-                        R.raw.connected,
-                        audioAttributes,
-                        AudioManager.AUDIO_SESSION_ID_GENERATE);
-        mediaPlayer.setVolume(
-                DEFAULT_MEDIA_PLAYER_VOLUME / 100f, DEFAULT_MEDIA_PLAYER_VOLUME / 100f);
-        mediaPlayer.start();
-    }
-
     public void success() {
         Log.d(Config.LOGTAG, "CallIntegration.success()");
-        startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
-        this.destroyWithDelay(new DisconnectCause(DisconnectCause.LOCAL, null), 375);
+        startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_CONFIRM, 600);
+        this.destroyWithDelay(new DisconnectCause(DisconnectCause.LOCAL, null), 600);
     }
 
     public void accepted() {
@@ -425,8 +403,8 @@ public class CallIntegration extends Connection {
 
     public void error() {
         Log.d(Config.LOGTAG, "CallIntegration.error()");
-        startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
-        this.destroyWithDelay(new DisconnectCause(DisconnectCause.ERROR, null), 375);
+        startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_CONFIRM, 600);
+        this.destroyWithDelay(new DisconnectCause(DisconnectCause.ERROR, null), 600);
     }
 
     public void retracted() {
@@ -530,13 +508,17 @@ public class CallIntegration extends Connection {
         return selfManaged(context);
     }
 
-    public static boolean selfManaged(final Context context) {
+    public static boolean selfManagedAvailable(final Context context) {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
                 && Build.VERSION.SDK_INT < 35
                 && hasSystemFeature(context)
                 && isDeviceModelSupported();
     }
 
+    public static boolean selfManaged(final Context context) {
+        return selfManagedAvailable(context) && new AppSettings(context).isCallIntegration();
+    }
+
     public static boolean hasSystemFeature(final Context context) {
         final var packageManager = context.getPackageManager();
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@@ -562,6 +544,10 @@ public class CallIntegration extends Connection {
         if ("umidigi".equals(manufacturer) && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) {
             return false;
         }
+        // SailfishOS's AppSupport do not support Call Integration
+        if (Build.MODEL.endsWith("(AppSupport)")) {
+            return false;
+        }
         return true;
     }
 
  
  
  
    
    @@ -47,7 +47,6 @@ import androidx.core.app.RemoteInput;
 import androidx.core.content.ContextCompat;
 import androidx.core.content.pm.ShortcutInfoCompat;
 import androidx.core.graphics.drawable.IconCompat;
-
 import com.google.common.base.Joiner;
 import com.google.common.base.Optional;
 import com.google.common.base.Splitter;
@@ -55,7 +54,6 @@ import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.primitives.Ints;
-
 import eu.siacs.conversations.AppSettings;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
@@ -81,7 +79,6 @@ import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.XmppConnection;
 import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
 import eu.siacs.conversations.xmpp.jingle.Media;
-
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -95,6 +92,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -131,7 +129,7 @@ public class NotificationService {
     private static final String INCOMING_CALLS_NOTIFICATION_CHANNEL = "incoming_calls_channel";
     private static final String INCOMING_CALLS_NOTIFICATION_CHANNEL_PREFIX =
             "incoming_calls_channel#";
-    private static final String MESSAGES_NOTIFICATION_CHANNEL = "messages";
+    public static final String MESSAGES_NOTIFICATION_CHANNEL = "messages";
 
     NotificationService(final XmppConnectionService service) {
         this.mXmppConnectionService = service;
@@ -246,25 +244,8 @@ public class NotificationService {
         missedCallsChannel.setGroup("calls");
         notificationManager.createNotificationChannel(missedCallsChannel);
 
-        final NotificationChannel messagesChannel =
-                new NotificationChannel(
-                        MESSAGES_NOTIFICATION_CHANNEL,
-                        c.getString(R.string.messages_channel_name),
-                        NotificationManager.IMPORTANCE_HIGH);
-        messagesChannel.setShowBadge(true);
-        messagesChannel.setSound(
-                RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
-                new AudioAttributes.Builder()
-                        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                        .setUsage(AudioAttributes.USAGE_NOTIFICATION)
-                        .build());
-        messagesChannel.setLightColor(LED_COLOR);
-        final int dat = 70;
-        final long[] pattern = {0, 3 * dat, dat, dat};
-        messagesChannel.setVibrationPattern(pattern);
-        messagesChannel.enableVibration(true);
-        messagesChannel.enableLights(true);
-        messagesChannel.setGroup("chats");
+        final var messagesChannel =
+                prepareMessagesChannel(mXmppConnectionService, MESSAGES_NOTIFICATION_CHANNEL);
         notificationManager.createNotificationChannel(messagesChannel);
         final NotificationChannel silentMessagesChannel =
                 new NotificationChannel(
@@ -295,6 +276,41 @@ public class NotificationService {
         notificationManager.createNotificationChannel(deliveryFailedChannel);
     }
 
+    @RequiresApi(api = Build.VERSION_CODES.R)
+    public static void createConversationChannel(
+            final Context context, final ShortcutInfoCompat shortcut) {
+        final var messagesChannel = prepareMessagesChannel(context, UUID.randomUUID().toString());
+        messagesChannel.setName(shortcut.getShortLabel());
+        messagesChannel.setConversationId(MESSAGES_NOTIFICATION_CHANNEL, shortcut.getId());
+        final var notificationManager = context.getSystemService(NotificationManager.class);
+        notificationManager.createNotificationChannel(messagesChannel);
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.O)
+    private static NotificationChannel prepareMessagesChannel(
+            final Context context, final String id) {
+        final NotificationChannel messagesChannel =
+                new NotificationChannel(
+                        id,
+                        context.getString(R.string.messages_channel_name),
+                        NotificationManager.IMPORTANCE_HIGH);
+        messagesChannel.setShowBadge(true);
+        messagesChannel.setSound(
+                RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
+                new AudioAttributes.Builder()
+                        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                        .setUsage(AudioAttributes.USAGE_NOTIFICATION)
+                        .build());
+        messagesChannel.setLightColor(LED_COLOR);
+        final int dat = 70;
+        final long[] pattern = {0, 3 * dat, dat, dat};
+        messagesChannel.setVibrationPattern(pattern);
+        messagesChannel.enableVibration(true);
+        messagesChannel.enableLights(true);
+        messagesChannel.setGroup("chats");
+        return messagesChannel;
+    }
+
     @RequiresApi(api = Build.VERSION_CODES.O)
     private static void createInitialIncomingCallChannelIfNecessary(final Context context) {
         final var currentIteration = getCurrentIncomingCallChannelIteration(context);
@@ -562,7 +578,8 @@ public class NotificationService {
             Log.d(
                     Config.LOGTAG,
                     message.getConversation().getAccount().getJid().asBareJid()
-                            + ": suppressing failed delivery notification because conversation is open");
+                            + ": suppressing failed delivery notification because conversation is"
+                            + " open");
             return;
         }
         final PendingIntent pendingIntent = createContentIntent(conversation);
@@ -639,7 +656,7 @@ public class NotificationService {
                         mXmppConnectionService, channelId);
         final Contact contact = id.getContact();
         builder.addPerson(getPerson(contact));
-        ShortcutInfoCompat info = mXmppConnectionService.getShortcutService().getShortcutInfoCompat(contact);
+        ShortcutInfoCompat info = mXmppConnectionService.getShortcutService().getShortcutInfo(contact);
         builder.setShortcutInfo(info);
         if (Build.VERSION.SDK_INT >= 30) {
             mXmppConnectionService.getSystemService(ShortcutManager.class).pushDynamicShortcut(info.toShortcutInfo());
@@ -710,10 +727,11 @@ public class NotificationService {
                         .build());
         modifyIncomingCall(builder, id.account);
         final Notification notification = builder.build();
-        notification.audioAttributes = new AudioAttributes.Builder()
-                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
-                .build();
+        notification.audioAttributes =
+                new AudioAttributes.Builder()
+                        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                        .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+                        .build();
         notification.flags = notification.flags | Notification.FLAG_INSISTENT;
         notify(INCOMING_CALL_NOTIFICATION_ID, notification);
     }
@@ -795,7 +813,8 @@ public class NotificationService {
         if (jingleRtpConnection == null) {
             return false;
         }
-        final var notificationManager = mXmppConnectionService.getSystemService(NotificationManager.class);
+        final var notificationManager =
+                mXmppConnectionService.getSystemService(NotificationManager.class);
         if (Iterables.any(
                 Arrays.asList(notificationManager.getActiveNotifications()),
                 n -> n.getId() == INCOMING_CALL_NOTIFICATION_ID)) {
@@ -909,7 +928,8 @@ public class NotificationService {
                         Log.d(
                                 Config.LOGTAG,
                                 conversational.getAccount().getJid().asBareJid()
-                                        + ": dismissed missed call because call was picked up on other device");
+                                        + ": dismissed missed call because call was picked up on"
+                                        + " other device");
                         iterator.remove();
                     }
                 }
@@ -1458,12 +1478,15 @@ public class NotificationService {
             if (systemAccount != null) {
                 notificationBuilder.addPerson(systemAccount.toString());
             }
-            info = mXmppConnectionService.getShortcutService().getShortcutInfoCompat(contact);
+            info =
+                    mXmppConnectionService
+                            .getShortcutService()
+                            .getShortcutInfo(contact, conversation.getUuid());
         } else {
             info =
                     mXmppConnectionService
                             .getShortcutService()
-                            .getShortcutInfoCompat(conversation.getMucOptions());
+                            .getShortcutInfo(conversation.getMucOptions());
         }
         notificationBuilder.setWhen(conversation.getLatestMessage().getTimeSent());
         notificationBuilder.setSmallIcon(R.drawable.ic_notification);
@@ -1498,16 +1521,16 @@ public class NotificationService {
             }
             final BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();
             bigPictureStyle.bigPicture(bitmap);
-            if (tmp.size() > 0) {
-                CharSequence text = getMergedBodies(tmp);
-                bigPictureStyle.setSummaryText(text);
-                builder.setContentText(text);
-                builder.setTicker(text);
-            } else {
+            if (tmp.isEmpty()) {
                 final String description =
                         UIHelper.getFileDescriptionString(mXmppConnectionService, message);
                 builder.setContentText(description);
                 builder.setTicker(description);
+            } else {
+                final CharSequence text = getMergedBodies(tmp);
+                bigPictureStyle.setSummaryText(text);
+                builder.setContentText(text);
+                builder.setTicker(text);
             }
             builder.setStyle(bigPictureStyle);
         } catch (final IOException e) {
  
  
  
    
    @@ -2,17 +2,15 @@ package eu.siacs.conversations.services;
 
 import android.annotation.TargetApi;
 import android.content.Intent;
-import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
 import android.graphics.Bitmap;
-import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Build;
+import android.os.PersistableBundle;
 import android.util.Log;
-
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 import androidx.core.content.pm.ShortcutInfoCompat;
+import androidx.core.content.pm.ShortcutManagerCompat;
 import androidx.core.graphics.drawable.IconCompat;
 
 import java.util.ArrayList;
@@ -20,21 +18,30 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.MucOptions;
+import eu.siacs.conversations.ui.ConversationsActivity;
 import eu.siacs.conversations.ui.StartConversationActivity;
 import eu.siacs.conversations.ui.ConversationsActivity;
 import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor;
 import eu.siacs.conversations.xmpp.Jid;
+import java.util.Collection;
+import java.util.List;
 
 public class ShortcutService {
 
     private final XmppConnectionService xmppConnectionService;
-    private final ReplacingSerialSingleThreadExecutor replacingSerialSingleThreadExecutor = new ReplacingSerialSingleThreadExecutor(ShortcutService.class.getSimpleName());
+    private final ReplacingSerialSingleThreadExecutor replacingSerialSingleThreadExecutor =
+            new ReplacingSerialSingleThreadExecutor(ShortcutService.class.getSimpleName());
 
-    public ShortcutService(XmppConnectionService xmppConnectionService) {
+    public ShortcutService(final XmppConnectionService xmppConnectionService) {
         this.xmppConnectionService = xmppConnectionService;
     }
 
@@ -44,12 +51,7 @@ public class ShortcutService {
 
     public void refresh(final boolean forceUpdate) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
-            final Runnable r = new Runnable() {
-                @Override
-                public void run() {
-                    refreshImpl(forceUpdate);
-                }
-            };
+            final Runnable r = () -> refreshImpl(forceUpdate);
             replacingSerialSingleThreadExecutor.execute(r);
         }
     }
@@ -57,71 +59,94 @@ public class ShortcutService {
     @TargetApi(25)
     public void report(Contact contact) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
-            ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class);
+            ShortcutManager shortcutManager =
+                    xmppConnectionService.getSystemService(ShortcutManager.class);
             shortcutManager.reportShortcutUsed(getShortcutId(contact));
         }
     }
 
     @TargetApi(25)
-    private void refreshImpl(boolean forceUpdate) {
-        List<FrequentContact> frequentContacts = xmppConnectionService.databaseBackend.getFrequentContacts(30);
-        HashMap<String,Account> accounts = new HashMap<>();
-        for(Account account : xmppConnectionService.getAccounts()) {
-            accounts.put(account.getUuid(),account);
-        }
-        List<Contact> contacts = new ArrayList<>();
-        for(FrequentContact frequentContact : frequentContacts) {
-            Account account = accounts.get(frequentContact.account);
+    private void refreshImpl(final boolean forceUpdate) {
+        final var frequentContacts = xmppConnectionService.databaseBackend.getFrequentContacts(30);
+        final var accounts =
+                ImmutableMap.copyOf(
+                        Maps.uniqueIndex(xmppConnectionService.getAccounts(), Account::getUuid));
+        final var contactBuilder = new ImmutableMap.Builder<FrequentContact, Contact>();
+        for (final var frequentContact : frequentContacts) {
+            final Account account = accounts.get(frequentContact.account);
             if (account != null) {
-                contacts.add(account.getRoster().getContact(frequentContact.contact));
+                final var contact = account.getRoster().getContact(frequentContact.contact);
+                contactBuilder.put(frequentContact, contact);
             }
         }
-        ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class);
-        boolean needsUpdate = forceUpdate || contactsChanged(contacts,shortcutManager.getDynamicShortcuts());
+        final var contacts = contactBuilder.build();
+        final var current = ShortcutManagerCompat.getDynamicShortcuts(xmppConnectionService);
+        boolean needsUpdate = forceUpdate || contactsChanged(contacts.values(), current);
         if (!needsUpdate) {
-            Log.d(Config.LOGTAG,"skipping shortcut update");
+            Log.d(Config.LOGTAG, "skipping shortcut update");
             return;
         }
-        List<ShortcutInfo> newDynamicShortCuts = new ArrayList<>();
-        for (Contact contact : contacts) {
-            ShortcutInfo shortcut = getShortcutInfo(contact);
-            newDynamicShortCuts.add(shortcut);
+        final var newDynamicShortcuts = new ImmutableList.Builder<ShortcutInfoCompat>();
+        for (final var entry : contacts.entrySet()) {
+            final var contact = entry.getValue();
+            final var conversation = entry.getKey().conversation;
+            final var shortcut = getShortcutInfo(contact, conversation);
+            newDynamicShortcuts.add(shortcut);
         }
-        if (shortcutManager.setDynamicShortcuts(newDynamicShortCuts)) {
-            Log.d(Config.LOGTAG,"updated dynamic shortcuts");
+        if (ShortcutManagerCompat.setDynamicShortcuts(
+                xmppConnectionService, newDynamicShortcuts.build())) {
+            Log.d(Config.LOGTAG, "updated dynamic shortcuts");
         } else {
             Log.d(Config.LOGTAG, "unable to update dynamic shortcuts");
         }
     }
 
-    public ShortcutInfoCompat getShortcutInfoCompat(Contact contact) {
-        return new ShortcutInfoCompat.Builder(xmppConnectionService, getShortcutId(contact))
+    public ShortcutInfoCompat getShortcutInfo(final Contact contact) {
+        final var conversation = xmppConnectionService.find(contact);
+        final var uuid = conversation == null ? null : conversation.getUuid();
+        return getShortcutInfo(contact, uuid);
+    }
+
+    public ShortcutInfoCompat getShortcutInfo(final Contact contact, final String conversation) {
+        final ShortcutInfoCompat.Builder builder =
+                new ShortcutInfoCompat.Builder(xmppConnectionService, getShortcutId(contact))
                         .setShortLabel(contact.getDisplayName())
                         .setIntent(getShortcutIntent(contact))
-                        .setIcon(IconCompat.createWithBitmap(xmppConnectionService.getAvatarService().getRoundedShortcut(contact)))
-                        .setIsConversation()
-                        .setCategories(Set.of("com.cheogram.android.SHARE_TARGET"))
-                        .build();
+                        .setIsConversation();
+        builder.setIcon(
+                IconCompat.createWithBitmap(
+                        xmppConnectionService.getAvatarService().getRoundedShortcut(contact)));
+        if (conversation != null) {
+            setConversation(builder, conversation);
+        }
+        return builder.build();
     }
 
-    public ShortcutInfoCompat getShortcutInfoCompat(final MucOptions mucOptions) {
-        return new ShortcutInfoCompat.Builder(xmppConnectionService, getShortcutId(mucOptions))
+    public ShortcutInfoCompat getShortcutInfo(final MucOptions mucOptions) {
+        final ShortcutInfoCompat.Builder builder =
+                new ShortcutInfoCompat.Builder(xmppConnectionService, getShortcutId(mucOptions))
                         .setShortLabel(mucOptions.getConversation().getName())
                         .setIntent(getShortcutIntent(mucOptions))
-                        .setIcon(IconCompat.createWithBitmap(xmppConnectionService.getAvatarService().getRoundedShortcut(mucOptions)))
-                        .setIsConversation()
-                        .setCategories(Set.of("com.cheogram.android.SHARE_TARGET"))
-                        .build();
-    }
-
-    @TargetApi(Build.VERSION_CODES.N_MR1)
-    private ShortcutInfo getShortcutInfo(final Contact contact) {
-        return getShortcutInfoCompat(contact).toShortcutInfo();
-    }
-
-    private static boolean contactsChanged(List<Contact> needles, List<ShortcutInfo> haystack) {
-        for(Contact needle : needles) {
-            if(!contactExists(needle,haystack)) {
+                        .setIsConversation();
+        builder.setIcon(
+                IconCompat.createWithBitmap(
+                        xmppConnectionService.getAvatarService().getRoundedShortcut(mucOptions)));
+        setConversation(builder, mucOptions.getConversation().getUuid());
+        return builder.build();
+    }
+
+    private static void setConversation(
+            final ShortcutInfoCompat.Builder builder, @NonNull final String conversation) {
+        builder.setCategories(ImmutableSet.of("eu.siacs.conversations.category.SHARE_TARGET"));
+        final var extras = new PersistableBundle();
+        extras.putString(ConversationsActivity.EXTRA_CONVERSATION, conversation);
+        builder.setExtras(extras);
+    }
+
+    private static boolean contactsChanged(
+            final Collection<Contact> needles, final List<ShortcutInfoCompat> haystack) {
+        for (final Contact needle : needles) {
+            if (!contactExists(needle, haystack)) {
                 return true;
             }
         }
@@ -129,17 +154,22 @@ public class ShortcutService {
     }
 
     @TargetApi(25)
-    private static boolean contactExists(Contact needle, List<ShortcutInfo> haystack) {
-        for(ShortcutInfo shortcutInfo : haystack) {
-            if (getShortcutId(needle).equals(shortcutInfo.getId()) && needle.getDisplayName().equals(shortcutInfo.getShortLabel())) {
+    private static boolean contactExists(
+            final Contact needle, final List<ShortcutInfoCompat> haystack) {
+        for (final ShortcutInfoCompat shortcutInfo : haystack) {
+            final var label = shortcutInfo.getShortLabel();
+            if (getShortcutId(needle).equals(shortcutInfo.getId())
+                    && needle.getDisplayName().equals(label.toString())) {
                 return true;
             }
         }
         return false;
     }
 
-    private static String getShortcutId(Contact contact) {
-        return contact.getAccount().getJid().asBareJid().toEscapedString()+"#"+contact.getJid().asBareJid().toEscapedString();
+    private static String getShortcutId(final Contact contact) {
+        return contact.getAccount().getJid().asBareJid().toEscapedString()
+                + "#"
+                + contact.getJid().asBareJid().toEscapedString();
     }
 
     private static String getShortcutId(final MucOptions mucOptions) {
@@ -180,12 +210,13 @@ public class ShortcutService {
     }
 
     @NonNull
-    public Intent createShortcut(Contact contact, boolean legacy) {
+    public Intent createShortcut(final Contact contact, final boolean legacy) {
         Intent intent;
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !legacy) {
-            ShortcutInfo shortcut = getShortcutInfo(contact);
-            ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class);
-            intent = shortcutManager.createShortcutResultIntent(shortcut);
+            final var shortcut = getShortcutInfo(contact);
+            intent =
+                    ShortcutManagerCompat.createShortcutResultIntent(
+                            xmppConnectionService, shortcut);
         } else {
             intent = createShortcutResultIntent(contact);
         }
@@ -193,7 +224,7 @@ public class ShortcutService {
     }
 
     @NonNull
-    private Intent createShortcutResultIntent(Contact contact) {
+    private Intent createShortcutResultIntent(final Contact contact) {
         AvatarService avatarService = xmppConnectionService.getAvatarService();
         Bitmap icon = avatarService.getRoundedShortcutWithIcon(contact);
         Intent intent = new Intent();
@@ -204,13 +235,14 @@ public class ShortcutService {
     }
 
     public static class FrequentContact {
+        private final String conversation;
         private final String account;
         private final Jid contact;
 
-        public FrequentContact(String account, Jid contact) {
+        public FrequentContact(final String conversation, final String account, final Jid contact) {
+            this.conversation = conversation;
             this.account = account;
             this.contact = contact;
         }
     }
-
 }
  
  
  
    
    @@ -5,7 +5,6 @@ import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
 
 import android.Manifest;
 import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
 import android.app.AlarmManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
@@ -49,7 +48,6 @@ import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.LruCache;
 import android.util.Pair;
-
 import androidx.annotation.BoolRes;
 import androidx.annotation.IntegerRes;
 import androidx.annotation.NonNull;
@@ -64,8 +62,10 @@ import com.google.common.base.Objects;
 import com.google.common.base.Optional;
 import com.google.common.base.Strings;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 import com.google.common.io.Files;
 
@@ -168,6 +168,7 @@ import eu.siacs.conversations.utils.EasyOnboardingInvite;
 import eu.siacs.conversations.utils.ExceptionHelper;
 import eu.siacs.conversations.utils.FileUtils;
 import eu.siacs.conversations.utils.MessageUtils;
+import eu.siacs.conversations.utils.Emoticons;
 import eu.siacs.conversations.utils.MimeUtils;
 import eu.siacs.conversations.utils.PhoneHelper;
 import eu.siacs.conversations.utils.QuickLoader;
@@ -203,7 +204,38 @@ import eu.siacs.conversations.xmpp.mam.MamReference;
 import eu.siacs.conversations.xmpp.pep.Avatar;
 import eu.siacs.conversations.xmpp.pep.PublishOptions;
 import im.conversations.android.xmpp.model.stanza.Iq;
+import java.io.File;
+import java.security.Security;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 import me.leolin.shortcutbadger.ShortcutBadger;
+import org.conscrypt.Conscrypt;
+import org.jxmpp.stringprep.libidn.LibIdnXmppStringprep;
+import org.openintents.openpgp.IOpenPgpService2;
+import org.openintents.openpgp.util.OpenPgpApi;
+import org.openintents.openpgp.util.OpenPgpServiceConnection;
 
 import okhttp3.HttpUrl;
 import okhttp3.OkHttpClient;
@@ -214,7 +246,8 @@ public class XmppConnectionService extends Service {
     public static final String ACTION_MARK_AS_READ = "mark_as_read";
     public static final String ACTION_SNOOZE = "snooze";
     public static final String ACTION_CLEAR_MESSAGE_NOTIFICATION = "clear_message_notification";
-    public static final String ACTION_CLEAR_MISSED_CALL_NOTIFICATION = "clear_missed_call_notification";
+    public static final String ACTION_CLEAR_MISSED_CALL_NOTIFICATION =
+            "clear_missed_call_notification";
     public static final String ACTION_DISMISS_ERROR_NOTIFICATIONS = "dismiss_error";
     public static final String ACTION_TRY_AGAIN = "try_again";
 
@@ -228,22 +261,30 @@ public class XmppConnectionService extends Service {
     public static final String ACTION_END_CALL = "end_call";
     public static final String ACTION_STARTING_CALL = "starting_call";
     public static final String ACTION_PROVISION_ACCOUNT = "provision_account";
-    public static final String ACTION_CALL_INTEGRATION_SERVICE_STARTED = "call_integration_service_started";
-    private static final String ACTION_POST_CONNECTIVITY_CHANGE = "eu.siacs.conversations.POST_CONNECTIVITY_CHANGE";
-    public static final String ACTION_RENEW_UNIFIED_PUSH_ENDPOINTS = "eu.siacs.conversations.UNIFIED_PUSH_RENEW";
+    public static final String ACTION_CALL_INTEGRATION_SERVICE_STARTED =
+            "call_integration_service_started";
+    private static final String ACTION_POST_CONNECTIVITY_CHANGE =
+            "eu.siacs.conversations.POST_CONNECTIVITY_CHANGE";
+    public static final String ACTION_RENEW_UNIFIED_PUSH_ENDPOINTS =
+            "eu.siacs.conversations.UNIFIED_PUSH_RENEW";
     public static final String ACTION_QUICK_LOG = "eu.siacs.conversations.QUICK_LOG";
 
     private static final String SETTING_LAST_ACTIVITY_TS = "last_activity_timestamp";
 
     public final CountDownLatch restoredFromDatabaseLatch = new CountDownLatch(1);
-    private final static Executor FILE_OBSERVER_EXECUTOR = Executors.newSingleThreadExecutor();
-    private final static Executor FILE_ATTACHMENT_EXECUTOR = Executors.newSingleThreadExecutor();
-
-    private final ScheduledExecutorService internalPingExecutor = Executors.newSingleThreadScheduledExecutor();
-    private final static SerialSingleThreadExecutor VIDEO_COMPRESSION_EXECUTOR = new SerialSingleThreadExecutor("VideoCompression");
-    private final SerialSingleThreadExecutor mDatabaseWriterExecutor = new SerialSingleThreadExecutor("DatabaseWriter");
-    private final SerialSingleThreadExecutor mDatabaseReaderExecutor = new SerialSingleThreadExecutor("DatabaseReader");
-    private final SerialSingleThreadExecutor mNotificationExecutor = new SerialSingleThreadExecutor("NotificationExecutor");
+    private static final Executor FILE_OBSERVER_EXECUTOR = Executors.newSingleThreadExecutor();
+    public static final Executor FILE_ATTACHMENT_EXECUTOR = Executors.newSingleThreadExecutor();
+
+    private final ScheduledExecutorService internalPingExecutor =
+            Executors.newSingleThreadScheduledExecutor();
+    private static final SerialSingleThreadExecutor VIDEO_COMPRESSION_EXECUTOR =
+            new SerialSingleThreadExecutor("VideoCompression");
+    private final SerialSingleThreadExecutor mDatabaseWriterExecutor =
+            new SerialSingleThreadExecutor("DatabaseWriter");
+    private final SerialSingleThreadExecutor mDatabaseReaderExecutor =
+            new SerialSingleThreadExecutor("DatabaseReader");
+    private final SerialSingleThreadExecutor mNotificationExecutor =
+            new SerialSingleThreadExecutor("NotificationExecutor");
     private final ReplacingTaskManager mRosterSyncTaskManager = new ReplacingTaskManager();
     private final IBinder mBinder = new XmppConnectionBinder();
     private final List<Conversation> conversations = new CopyOnWriteArrayList<>();
@@ -251,15 +292,16 @@ public class XmppConnectionService extends Service {
     private final Set<String> mInProgressAvatarFetches = new HashSet<>();
     private final Set<String> mOmittedPepAvatarFetches = new HashSet<>();
     private final HashSet<Jid> mLowPingTimeoutMode = new HashSet<>();
-    private final Consumer<Iq> mDefaultIqHandler = (packet) -> {
-        if (packet.getType() != Iq.Type.RESULT) {
-            final var error = packet.getError();
-            String text = error != null ? error.findChildContent("text") : null;
-            if (text != null) {
-                Log.d(Config.LOGTAG, "received iq error: " + text);
-            }
-        }
-    };
+    private final Consumer<Iq> mDefaultIqHandler =
+            (packet) -> {
+                if (packet.getType() != Iq.Type.RESULT) {
+                    final var error = packet.getError();
+                    String text = error != null ? error.findChildContent("text") : null;
+                    if (text != null) {
+                        Log.d(Config.LOGTAG, "received iq error: " + text);
+                    }
+                }
+            };
     public DatabaseBackend databaseBackend;
     private Multimap<String, String> mutedMucUsers;
     private final ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor("ContactMerger");
@@ -273,70 +315,80 @@ public class XmppConnectionService extends Service {
     private MemorizingTrustManager mMemorizingTrustManager;
     private final NotificationService mNotificationService = new NotificationService(this);
     private final UnifiedPushBroker unifiedPushBroker = new UnifiedPushBroker(this);
-    private final ChannelDiscoveryService mChannelDiscoveryService = new ChannelDiscoveryService(this);
+    private final ChannelDiscoveryService mChannelDiscoveryService =
+            new ChannelDiscoveryService(this);
     private final ShortcutService mShortcutService = new ShortcutService(this);
     private final AtomicBoolean mInitialAddressbookSyncCompleted = new AtomicBoolean(false);
     private final AtomicBoolean mOngoingVideoTranscoding = new AtomicBoolean(false);
     private final AtomicBoolean mForceDuringOnCreate = new AtomicBoolean(false);
     private final AtomicReference<OngoingCall> ongoingCall = new AtomicReference<>();
     private final MessageGenerator mMessageGenerator = new MessageGenerator(this);
-    public OnContactStatusChanged onContactStatusChanged = (contact, online) -> {
-        Conversation conversation = find(getConversations(), contact);
-        if (conversation != null) {
-            if (online) {
-                if (contact.getPresences().size() == 1) {
-                    sendUnsentMessages(conversation);
+    public OnContactStatusChanged onContactStatusChanged =
+            (contact, online) -> {
+                final var conversation = find(contact);
+                if (conversation == null) {
+                    return;
                 }
-            }
-        }
-    };
+                if (online) {
+                    if (contact.getPresences().size() == 1) {
+                        sendUnsentMessages(conversation);
+                    }
+                }
+            };
     private final PresenceGenerator mPresenceGenerator = new PresenceGenerator(this);
     private List<Account> accounts;
-    private final JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(this);
+    private final JingleConnectionManager mJingleConnectionManager =
+            new JingleConnectionManager(this);
     private final HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(this);
     private final AvatarService mAvatarService = new AvatarService(this);
     private final MessageArchiveService mMessageArchiveService = new MessageArchiveService(this);
     private final PushManagementService mPushManagementService = new PushManagementService(this);
-    private final QuickConversationsService mQuickConversationsService = new QuickConversationsService(this);
-    private final ConversationsFileObserver fileObserver = new ConversationsFileObserver(
-            Environment.getExternalStorageDirectory().getAbsolutePath()
-    ) {
-        @Override
-        public void onEvent(final int event, final File file) {
-            markFileDeleted(file);
-        }
-    };
-    private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() {
+    private final QuickConversationsService mQuickConversationsService =
+            new QuickConversationsService(this);
+    private final ConversationsFileObserver fileObserver =
+            new ConversationsFileObserver(
+                    Environment.getExternalStorageDirectory().getAbsolutePath()) {
+                @Override
+                public void onEvent(final int event, final File file) {
+                    markFileDeleted(file);
+                }
+            };
+    private final OnMessageAcknowledged mOnMessageAcknowledgedListener =
+            new OnMessageAcknowledged() {
 
-        @Override
-        public boolean onMessageAcknowledged(final Account account, final Jid to, final String id) {
-            if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
-                final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
-                mJingleConnectionManager.updateProposedSessionDiscovered(
-                        account,
-                        to,
-                        sessionId,
-                        JingleConnectionManager.DeviceDiscoveryState.SEARCHING_ACKNOWLEDGED
-                );
-            }
-
-
-            final Jid bare = to.asBareJid();
-
-            for (final Conversation conversation : getConversations()) {
-                if (conversation.getAccount() == account && conversation.getJid().asBareJid().equals(bare)) {
-                    final Message message = conversation.findUnsentMessageWithUuid(id);
-                    if (message != null) {
-                        message.setStatus(Message.STATUS_SEND);
-                        message.setErrorMessage(null);
-                        databaseBackend.updateMessage(message, false);
-                        return true;
+                @Override
+                public boolean onMessageAcknowledged(
+                        final Account account, final Jid to, final String id) {
+                    if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
+                        final String sessionId =
+                                id.substring(
+                                        JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX
+                                                .length());
+                        mJingleConnectionManager.updateProposedSessionDiscovered(
+                                account,
+                                to,
+                                sessionId,
+                                JingleConnectionManager.DeviceDiscoveryState
+                                        .SEARCHING_ACKNOWLEDGED);
+                    }
+
+                    final Jid bare = to.asBareJid();
+
+                    for (final Conversation conversation : getConversations()) {
+                        if (conversation.getAccount() == account
+                                && conversation.getJid().asBareJid().equals(bare)) {
+                            final Message message = conversation.findUnsentMessageWithUuid(id);
+                            if (message != null) {
+                                message.setStatus(Message.STATUS_SEND);
+                                message.setErrorMessage(null);
+                                databaseBackend.updateMessage(message, false);
+                                return true;
+                            }
+                        }
                     }
+                    return false;
                 }
-            }
-            return false;
-        }
-    };
+            };
 
     private final AtomicBoolean diallerIntegrationActive = new AtomicBoolean(false);
 
@@ -348,136 +400,176 @@ public class XmppConnectionService extends Service {
 
     private int unreadCount = -1;
 
-    //Ui callback listeners
-    private final Set<OnConversationUpdate> mOnConversationUpdates = Collections.newSetFromMap(new WeakHashMap<OnConversationUpdate, Boolean>());
-    private final Set<OnShowErrorToast> mOnShowErrorToasts = Collections.newSetFromMap(new WeakHashMap<OnShowErrorToast, Boolean>());
-    private final Set<OnAccountUpdate> mOnAccountUpdates = Collections.newSetFromMap(new WeakHashMap<OnAccountUpdate, Boolean>());
-    private final Set<OnCaptchaRequested> mOnCaptchaRequested = Collections.newSetFromMap(new WeakHashMap<OnCaptchaRequested, Boolean>());
-    private final Set<OnRosterUpdate> mOnRosterUpdates = Collections.newSetFromMap(new WeakHashMap<OnRosterUpdate, Boolean>());
-    private final Set<OnUpdateBlocklist> mOnUpdateBlocklist = Collections.newSetFromMap(new WeakHashMap<OnUpdateBlocklist, Boolean>());
-    private final Set<OnMucRosterUpdate> mOnMucRosterUpdate = Collections.newSetFromMap(new WeakHashMap<OnMucRosterUpdate, Boolean>());
-    private final Set<OnKeyStatusUpdated> mOnKeyStatusUpdated = Collections.newSetFromMap(new WeakHashMap<OnKeyStatusUpdated, Boolean>());
-    private final Set<OnJingleRtpConnectionUpdate> onJingleRtpConnectionUpdate = Collections.newSetFromMap(new WeakHashMap<OnJingleRtpConnectionUpdate, Boolean>());
+    // Ui callback listeners
+    private final Set<OnConversationUpdate> mOnConversationUpdates =
+            Collections.newSetFromMap(new WeakHashMap<OnConversationUpdate, Boolean>());
+    private final Set<OnShowErrorToast> mOnShowErrorToasts =
+            Collections.newSetFromMap(new WeakHashMap<OnShowErrorToast, Boolean>());
+    private final Set<OnAccountUpdate> mOnAccountUpdates =
+            Collections.newSetFromMap(new WeakHashMap<OnAccountUpdate, Boolean>());
+    private final Set<OnCaptchaRequested> mOnCaptchaRequested =
+            Collections.newSetFromMap(new WeakHashMap<OnCaptchaRequested, Boolean>());
+    private final Set<OnRosterUpdate> mOnRosterUpdates =
+            Collections.newSetFromMap(new WeakHashMap<OnRosterUpdate, Boolean>());
+    private final Set<OnUpdateBlocklist> mOnUpdateBlocklist =
+            Collections.newSetFromMap(new WeakHashMap<OnUpdateBlocklist, Boolean>());
+    private final Set<OnMucRosterUpdate> mOnMucRosterUpdate =
+            Collections.newSetFromMap(new WeakHashMap<OnMucRosterUpdate, Boolean>());
+    private final Set<OnKeyStatusUpdated> mOnKeyStatusUpdated =
+            Collections.newSetFromMap(new WeakHashMap<OnKeyStatusUpdated, Boolean>());
+    private final Set<OnJingleRtpConnectionUpdate> onJingleRtpConnectionUpdate =
+            Collections.newSetFromMap(new WeakHashMap<OnJingleRtpConnectionUpdate, Boolean>());
 
     private final Object LISTENER_LOCK = new Object();
 
-
     public final Set<String> FILENAMES_TO_IGNORE_DELETION = new HashSet<>();
 
-
-
     private final AtomicLong mLastExpiryRun = new AtomicLong(0);
-    private final LruCache<Pair<String, String>, ServiceDiscoveryResult> discoCache = new LruCache<>(20);
-    private final OnStatusChanged statusListener = new OnStatusChanged() {
-
-        @Override
-        public void onStatusChanged(final Account account) {
-            XmppConnection connection = account.getXmppConnection();
-            updateAccountUi();
+    private final LruCache<Pair<String, String>, ServiceDiscoveryResult> discoCache =
+            new LruCache<>(20);
+    private final OnStatusChanged statusListener =
+            new OnStatusChanged() {
 
-            if (account.getStatus() == Account.State.ONLINE || account.getStatus().isError()) {
-                mQuickConversationsService.signalAccountStateChange();
-            }
+                @Override
+                public void onStatusChanged(final Account account) {
+                    XmppConnection connection = account.getXmppConnection();
+                    updateAccountUi();
 
-            if (account.getStatus() == Account.State.ONLINE) {
-                synchronized (mLowPingTimeoutMode) {
-                    if (mLowPingTimeoutMode.remove(account.getJid().asBareJid())) {
-                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": leaving low ping timeout mode");
-                    }
-                }
-                if (account.setShowErrorNotification(true)) {
-                    databaseBackend.updateAccount(account);
-                }
-                mMessageArchiveService.executePendingQueries(account);
-                if (connection != null && connection.getFeatures().csi()) {
-                    if (checkListeners()) {
-                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + " sending csi//inactive");
-                        connection.sendInactive();
-                    } else {
-                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + " sending csi//active");
-                        connection.sendActive();
-                    }
-                }
-                List<Conversation> conversations = getConversations();
-                for (Conversation conversation : conversations) {
-                    final boolean inProgressJoin;
-                    synchronized (account.inProgressConferenceJoins) {
-                        inProgressJoin = account.inProgressConferenceJoins.contains(conversation);
+                    if (account.getStatus() == Account.State.ONLINE
+                            || account.getStatus().isError()) {
+                        mQuickConversationsService.signalAccountStateChange();
                     }
-                    final boolean pendingJoin;
-                    synchronized (account.pendingConferenceJoins) {
-                        pendingJoin = account.pendingConferenceJoins.contains(conversation);
-                    }
-                    if (conversation.getAccount() == account
-                            && !pendingJoin
-                            && !inProgressJoin) {
-                        sendUnsentMessages(conversation);
-                    }
-                }
-                final List<Conversation> pendingLeaves;
-                synchronized (account.pendingConferenceLeaves) {
-                    pendingLeaves = new ArrayList<>(account.pendingConferenceLeaves);
-                    account.pendingConferenceLeaves.clear();
 
-                }
-                for (Conversation conversation : pendingLeaves) {
-                    leaveMuc(conversation);
-                }
-                final List<Conversation> pendingJoins;
-                synchronized (account.pendingConferenceJoins) {
-                    pendingJoins = new ArrayList<>(account.pendingConferenceJoins);
-                    account.pendingConferenceJoins.clear();
-                }
-                for (Conversation conversation : pendingJoins) {
-                    joinMuc(conversation);
-                }
-                fetchMamPreferences(account, null);
-                scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode());
-            } else if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED || account.getStatus() == Account.State.LOGGED_OUT) {
-                resetSendingToWaiting(account);
-                if (account.isConnectionEnabled() && isInLowPingTimeoutMode(account)) {
-                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": went into offline state during low ping mode. reconnecting now");
-                    reconnectAccount(account, true, false);
-                } else {
-                    final int timeToReconnect = SECURE_RANDOM.nextInt(10) + 2;
-                    scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
-                }
-            } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
-                databaseBackend.updateAccount(account);
-                reconnectAccount(account, true, false);
-            } else if (account.getStatus() != Account.State.CONNECTING && account.getStatus() != Account.State.NO_INTERNET) {
-                resetSendingToWaiting(account);
-                if (connection != null && account.getStatus().isAttemptReconnect()) {
-                    final boolean aggressive = account.getStatus() == Account.State.SEE_OTHER_HOST
-                            || hasJingleRtpConnection(account);
-                    final int next = connection.getTimeToNextAttempt(aggressive);
-                    final boolean lowPingTimeoutMode = isInLowPingTimeoutMode(account);
-                    if (next <= 0) {
-                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": error connecting account. reconnecting now. lowPingTimeout=" + lowPingTimeoutMode);
+                    if (account.getStatus() == Account.State.ONLINE) {
+                        synchronized (mLowPingTimeoutMode) {
+                            if (mLowPingTimeoutMode.remove(account.getJid().asBareJid())) {
+                                Log.d(
+                                        Config.LOGTAG,
+                                        account.getJid().asBareJid()
+                                                + ": leaving low ping timeout mode");
+                            }
+                        }
+                        if (account.setShowErrorNotification(true)) {
+                            databaseBackend.updateAccount(account);
+                        }
+                        mMessageArchiveService.executePendingQueries(account);
+                        if (connection != null && connection.getFeatures().csi()) {
+                            if (checkListeners()) {
+                                Log.d(
+                                        Config.LOGTAG,
+                                        account.getJid().asBareJid() + " sending csi//inactive");
+                                connection.sendInactive();
+                            } else {
+                                Log.d(
+                                        Config.LOGTAG,
+                                        account.getJid().asBareJid() + " sending csi//active");
+                                connection.sendActive();
+                            }
+                        }
+                        List<Conversation> conversations = getConversations();
+                        for (Conversation conversation : conversations) {
+                            final boolean inProgressJoin;
+                            synchronized (account.inProgressConferenceJoins) {
+                                inProgressJoin =
+                                        account.inProgressConferenceJoins.contains(conversation);
+                            }
+                            final boolean pendingJoin;
+                            synchronized (account.pendingConferenceJoins) {
+                                pendingJoin = account.pendingConferenceJoins.contains(conversation);
+                            }
+                            if (conversation.getAccount() == account
+                                    && !pendingJoin
+                                    && !inProgressJoin) {
+                                sendUnsentMessages(conversation);
+                            }
+                        }
+                        final List<Conversation> pendingLeaves;
+                        synchronized (account.pendingConferenceLeaves) {
+                            pendingLeaves = new ArrayList<>(account.pendingConferenceLeaves);
+                            account.pendingConferenceLeaves.clear();
+                        }
+                        for (Conversation conversation : pendingLeaves) {
+                            leaveMuc(conversation);
+                        }
+                        final List<Conversation> pendingJoins;
+                        synchronized (account.pendingConferenceJoins) {
+                            pendingJoins = new ArrayList<>(account.pendingConferenceJoins);
+                            account.pendingConferenceJoins.clear();
+                        }
+                        for (Conversation conversation : pendingJoins) {
+                            joinMuc(conversation);
+                        }
+                        scheduleWakeUpCall(
+                                Config.PING_MAX_INTERVAL * 1000L, account.getUuid().hashCode());
+                    } else if (account.getStatus() == Account.State.OFFLINE
+                            || account.getStatus() == Account.State.DISABLED
+                            || account.getStatus() == Account.State.LOGGED_OUT) {
+                        resetSendingToWaiting(account);
+                        if (account.isConnectionEnabled() && isInLowPingTimeoutMode(account)) {
+                            Log.d(
+                                    Config.LOGTAG,
+                                    account.getJid().asBareJid()
+                                            + ": went into offline state during low ping mode."
+                                            + " reconnecting now");
+                            reconnectAccount(account, true, false);
+                        } else {
+                            final int timeToReconnect = SECURE_RANDOM.nextInt(10) + 2;
+                            scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
+                        }
+                    } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
+                        databaseBackend.updateAccount(account);
                         reconnectAccount(account, true, false);
-                    } else {
-                        final int attempt = connection.getAttempt() + 1;
-                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": error connecting account. try again in " + next + "s for the " + attempt + " time. lowPingTimeout=" + lowPingTimeoutMode+", aggressive="+aggressive);
-                        scheduleWakeUpCall(next, account.getUuid().hashCode());
-                        if (aggressive) {
-                            internalPingExecutor.schedule(
-                                    XmppConnectionService.this::manageAccountConnectionStatesInternal,
-                                    (next * 1000L) + 50,
-                                    TimeUnit.MILLISECONDS
-                            );
+                    } else if (account.getStatus() != Account.State.CONNECTING
+                            && account.getStatus() != Account.State.NO_INTERNET) {
+                        resetSendingToWaiting(account);
+                        if (connection != null && account.getStatus().isAttemptReconnect()) {
+                            final boolean aggressive =
+                                    account.getStatus() == Account.State.SEE_OTHER_HOST
+                                            || hasJingleRtpConnection(account);
+                            final int next = connection.getTimeToNextAttempt(aggressive);
+                            final boolean lowPingTimeoutMode = isInLowPingTimeoutMode(account);
+                            if (next <= 0) {
+                                Log.d(
+                                        Config.LOGTAG,
+                                        account.getJid().asBareJid()
+                                                + ": error connecting account. reconnecting now."
+                                                + " lowPingTimeout="
+                                                + lowPingTimeoutMode);
+                                reconnectAccount(account, true, false);
+                            } else {
+                                final int attempt = connection.getAttempt() + 1;
+                                Log.d(
+                                        Config.LOGTAG,
+                                        account.getJid().asBareJid()
+                                                + ": error connecting account. try again in "
+                                                + next
+                                                + "s for the "
+                                                + attempt
+                                                + " time. lowPingTimeout="
+                                                + lowPingTimeoutMode
+                                                + ", aggressive="
+                                                + aggressive);
+                                scheduleWakeUpCall(next, account.getUuid().hashCode());
+                                if (aggressive) {
+                                    internalPingExecutor.schedule(
+                                            XmppConnectionService.this
+                                                    ::manageAccountConnectionStatesInternal,
+                                            (next * 1000L) + 50,
+                                            TimeUnit.MILLISECONDS);
+                                }
+                            }
                         }
                     }
+                    getNotificationService().updateErrorNotification();
                 }
-            }
-            getNotificationService().updateErrorNotification();
-        }
-    };
+            };
     private OpenPgpServiceConnection pgpServiceConnection;
     private PgpEngine mPgpEngine = null;
     private WakeLock wakeLock;
     private LruCache<String, Drawable> mDrawableCache;
     private final BroadcastReceiver mInternalEventReceiver = new InternalEventReceiver();
-    private final BroadcastReceiver mInternalRestrictedEventReceiver = new RestrictedEventReceiver(Arrays.asList(TorServiceUtils.ACTION_STATUS));
+    private final BroadcastReceiver mInternalRestrictedEventReceiver =
+            new RestrictedEventReceiver(Arrays.asList(TorServiceUtils.ACTION_STATUS));
     private final BroadcastReceiver mInternalScreenEventReceiver = new InternalEventReceiver();
     private EmojiSearch emojiSearch = null;
 
@@ -510,15 +602,16 @@ public class XmppConnectionService extends Service {
             return null;
         } else if (pgpServiceConnection != null && pgpServiceConnection.isBound()) {
             if (this.mPgpEngine == null) {
-                this.mPgpEngine = new PgpEngine(new OpenPgpApi(
-                        getApplicationContext(),
-                        pgpServiceConnection.getService()), this);
+                this.mPgpEngine =
+                        new PgpEngine(
+                                new OpenPgpApi(
+                                        getApplicationContext(), pgpServiceConnection.getService()),
+                                this);
             }
             return mPgpEngine;
         } else {
             return null;
         }
-
     }
 
     public OpenPgpApi getOpenPgpApi() {
@@ -617,7 +710,8 @@ public class XmppConnectionService extends Service {
         return this.mAvatarService;
     }
 
-    public void attachLocationToConversation(final Conversation conversation, final Uri uri, final String subject, final UiCallback<Message> callback) {
+    public void attachLocationToConversation(
+            final Conversation conversation, final Uri uri, final String subject, final UiCallback<Message> callback) {
         int encryption = conversation.getNextEncryption();
         if (encryption == Message.ENCRYPTION_PGP) {
             encryption = Message.ENCRYPTION_DECRYPTED;
@@ -634,7 +728,12 @@ public class XmppConnectionService extends Service {
         }
     }
 
-    public void attachFileToConversation(final Conversation conversation, final Uri uri, final String type, final String subject, final UiCallback<Message> callback) {
+    public void attachFileToConversation(
+            final Conversation conversation,
+            final Uri uri,
+            final String type,
+            final String subject,
+            final UiCallback<Message> callback) {
         final Message message;
         if (conversation.getReplyTo() == null) {
             message = new Message(conversation, "", conversation.getNextEncryption());
@@ -653,7 +752,8 @@ public class XmppConnectionService extends Service {
         }
         Log.d(Config.LOGTAG, "attachFile: type=" + message.getType());
         Log.d(Config.LOGTAG, "counterpart=" + message.getCounterpart());
-        final AttachFileToConversationRunnable runnable = new AttachFileToConversationRunnable(this, uri, type, message, callback);
+        final AttachFileToConversationRunnable runnable =
+                new AttachFileToConversationRunnable(this, uri, type, message, callback);
         if (runnable.isVideoMessage()) {
             VIDEO_COMPRESSION_EXECUTOR.execute(runnable);
         } else {
@@ -661,7 +761,12 @@ public class XmppConnectionService extends Service {
         }
     }
 
-    public void attachImageToConversation(final Conversation conversation, final Uri uri, final String type, final String subject, final UiCallback<Message> callback) {
+    public void attachImageToConversation(
+            final Conversation conversation,
+            final Uri uri,
+            final String type,
+            final String subject,
+            final UiCallback<Message> callback) {
         final String mimeType = MimeUtils.guessMimeTypeFromUriAndMime(this, uri, type);
         final String compressPictures = getCompressPicturesPreference();
 
@@ -798,7 +903,10 @@ public class XmppConnectionService extends Service {
         return c != null && c.getMode() == Conversational.MODE_MULTI;
     }
 
-    public void search(final List<String> term, final String uuid, final OnSearchResultsAvailable onSearchResultsAvailable) {
+    public void search(
+            final List<String> term,
+            final String uuid,
+            final OnSearchResultsAvailable onSearchResultsAvailable) {
         MessageSearchTask.search(this, term, uuid, onSearchResultsAvailable);
     }
 
@@ -807,9 +915,16 @@ public class XmppConnectionService extends Service {
         final var nomedia = getBooleanPreference("nomedia", R.bool.default_nomedia);
         fileBackend.setupNomedia(nomedia);
         final String action = Strings.nullToEmpty(intent == null ? null : intent.getAction());
-        final boolean needsForegroundService = intent != null && intent.getBooleanExtra(SystemEventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE, false);
+        final boolean needsForegroundService =
+                intent != null
+                        && intent.getBooleanExtra(
+                                SystemEventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE, false);
         if (needsForegroundService) {
-            Log.d(Config.LOGTAG, "toggle forced foreground service after receiving event (action=" + action + ")");
+            Log.d(
+                    Config.LOGTAG,
+                    "toggle forced foreground service after receiving event (action="
+                            + action
+                            + ")");
             toggleForegroundService(true, action.equals(ACTION_STARTING_CALL));
         }
         final String uuid = intent == null ? null : intent.getStringExtra("uuid");
@@ -832,75 +947,93 @@ public class XmppConnectionService extends Service {
                 logoutAndSave(true);
                 return START_NOT_STICKY;
             case ACTION_CLEAR_MESSAGE_NOTIFICATION:
-                mNotificationExecutor.execute(() -> {
-                    try {
-                        final Conversation c = findConversationByUuid(uuid);
-                        if (c != null) {
-                            mNotificationService.clearMessages(c);
-                        } else {
-                            mNotificationService.clearMessages();
-                        }
-                        restoredFromDatabaseLatch.await();
+                mNotificationExecutor.execute(
+                        () -> {
+                            try {
+                                final Conversation c = findConversationByUuid(uuid);
+                                if (c != null) {
+                                    mNotificationService.clearMessages(c);
+                                } else {
+                                    mNotificationService.clearMessages();
+                                }
+                                restoredFromDatabaseLatch.await();
 
-                    } catch (InterruptedException e) {
-                        Log.d(Config.LOGTAG, "unable to process clear message notification");
-                    }
-                });
+                            } catch (InterruptedException e) {
+                                Log.d(
+                                        Config.LOGTAG,
+                                        "unable to process clear message notification");
+                            }
+                        });
                 break;
             case ACTION_CLEAR_MISSED_CALL_NOTIFICATION:
-                mNotificationExecutor.execute(() -> {
-                    try {
-                        final Conversation c = findConversationByUuid(uuid);
-                        if (c != null) {
-                            mNotificationService.clearMissedCalls(c);
-                        } else {
-                            mNotificationService.clearMissedCalls();
-                        }
-                        restoredFromDatabaseLatch.await();
+                mNotificationExecutor.execute(
+                        () -> {
+                            try {
+                                final Conversation c = findConversationByUuid(uuid);
+                                if (c != null) {
+                                    mNotificationService.clearMissedCalls(c);
+                                } else {
+                                    mNotificationService.clearMissedCalls();
+                                }
+                                restoredFromDatabaseLatch.await();
 
-                    } catch (InterruptedException e) {
-                        Log.d(Config.LOGTAG, "unable to process clear missed call notification");
-                    }
-                });
+                            } catch (InterruptedException e) {
+                                Log.d(
+                                        Config.LOGTAG,
+                                        "unable to process clear missed call notification");
+                            }
+                        });
                 break;
-            case ACTION_DISMISS_CALL: {
-                if (intent == null) {
+            case ACTION_DISMISS_CALL:
+                {
+                    if (intent == null) {
+                        break;
+                    }
+                    final String sessionId =
+                            intent.getStringExtra(RtpSessionActivity.EXTRA_SESSION_ID);
+                    Log.d(
+                            Config.LOGTAG,
+                            "received intent to dismiss call with session id " + sessionId);
+                    mJingleConnectionManager.rejectRtpSession(sessionId);
                     break;
                 }
-                final String sessionId = intent.getStringExtra(RtpSessionActivity.EXTRA_SESSION_ID);
-                Log.d(Config.LOGTAG, "received intent to dismiss call with session id " + sessionId);
-                mJingleConnectionManager.rejectRtpSession(sessionId);
-                break;
-            }
             case TorServiceUtils.ACTION_STATUS:
-                final String status = intent == null ? null : intent.getStringExtra(TorServiceUtils.EXTRA_STATUS);
-                //TODO port and host are in 'extras' - but this may not be a reliable source?
+                final String status =
+                        intent == null ? null : intent.getStringExtra(TorServiceUtils.EXTRA_STATUS);
+                // TODO port and host are in 'extras' - but this may not be a reliable source?
                 if ("ON".equals(status)) {
                     handleOrbotStartedEvent();
                     return START_STICKY;
                 }
                 break;
-            case ACTION_END_CALL: {
-                if (intent == null) {
-                    break;
-                }
-                final String sessionId = intent.getStringExtra(RtpSessionActivity.EXTRA_SESSION_ID);
-                Log.d(Config.LOGTAG, "received intent to end call with session id " + sessionId);
-                mJingleConnectionManager.endRtpSession(sessionId);
-            }
-            break;
-            case ACTION_PROVISION_ACCOUNT: {
-                if (intent == null) {
-                    break;
+            case ACTION_END_CALL:
+                {
+                    if (intent == null) {
+                        break;
+                    }
+                    final String sessionId =
+                            intent.getStringExtra(RtpSessionActivity.EXTRA_SESSION_ID);
+                    Log.d(
+                            Config.LOGTAG,
+                            "received intent to end call with session id " + sessionId);
+                    mJingleConnectionManager.endRtpSession(sessionId);
                 }
-                final String address = intent.getStringExtra("address");
-                final String password = intent.getStringExtra("password");
-                if (QuickConversationsService.isQuicksy() || Strings.isNullOrEmpty(address) || Strings.isNullOrEmpty(password)) {
+                break;
+            case ACTION_PROVISION_ACCOUNT:
+                {
+                    if (intent == null) {
+                        break;
+                    }
+                    final String address = intent.getStringExtra("address");
+                    final String password = intent.getStringExtra("password");
+                    if (QuickConversationsService.isQuicksy()
+                            || Strings.isNullOrEmpty(address)
+                            || Strings.isNullOrEmpty(password)) {
+                        break;
+                    }
+                    provisionAccount(address, password);
                     break;
                 }
-                provisionAccount(address, password);
-                break;
-            }
             case ACTION_DISMISS_ERROR_NOTIFICATIONS:
                 dismissErrorNotifications();
                 break;
@@ -908,55 +1041,75 @@ public class XmppConnectionService extends Service {
                 resetAllAttemptCounts(false, true);
                 break;
             case ACTION_REPLY_TO_CONVERSATION:
-                final Bundle remoteInput = intent == null ? null : RemoteInput.getResultsFromIntent(intent);
+                final Bundle remoteInput =
+                        intent == null ? null : RemoteInput.getResultsFromIntent(intent);
                 if (remoteInput == null) {
                     break;
                 }
                 final CharSequence body = remoteInput.getCharSequence("text_reply");
-                final boolean dismissNotification = intent.getBooleanExtra("dismiss_notification", false);
+                final boolean dismissNotification =
+                        intent.getBooleanExtra("dismiss_notification", false);
                 final String lastMessageUuid = intent.getStringExtra("last_message_uuid");
                 if (body == null || body.length() <= 0) {
                     break;
                 }
-                mNotificationExecutor.execute(() -> {
-                    try {
-                        restoredFromDatabaseLatch.await();
-                        final Conversation c = findConversationByUuid(uuid);
-                        if (c != null) {
-                            directReply(c, body.toString(), lastMessageUuid, dismissNotification);
-                        }
-                    } catch (InterruptedException e) {
-                        Log.d(Config.LOGTAG, "unable to process direct reply");
-                    }
-                });
+                mNotificationExecutor.execute(
+                        () -> {
+                            try {
+                                restoredFromDatabaseLatch.await();
+                                final Conversation c = findConversationByUuid(uuid);
+                                if (c != null) {
+                                    directReply(
+                                            c,
+                                            body.toString(),
+                                            lastMessageUuid,
+                                            dismissNotification);
+                                }
+                            } catch (InterruptedException e) {
+                                Log.d(Config.LOGTAG, "unable to process direct reply");
+                            }
+                        });
                 break;
             case ACTION_MARK_AS_READ:
-                mNotificationExecutor.execute(() -> {
-                    final Conversation c = findConversationByUuid(uuid);
-                    if (c == null) {
-                        Log.d(Config.LOGTAG, "received mark read intent for unknown conversation (" + uuid + ")");
-                        return;
-                    }
-                    try {
-                        restoredFromDatabaseLatch.await();
-                        sendReadMarker(c, null);
-                    } catch (InterruptedException e) {
-                        Log.d(Config.LOGTAG, "unable to process notification read marker for conversation " + c.getName());
-                    }
-
-                });
+                mNotificationExecutor.execute(
+                        () -> {
+                            final Conversation c = findConversationByUuid(uuid);
+                            if (c == null) {
+                                Log.d(
+                                        Config.LOGTAG,
+                                        "received mark read intent for unknown conversation ("
+                                                + uuid
+                                                + ")");
+                                return;
+                            }
+                            try {
+                                restoredFromDatabaseLatch.await();
+                                sendReadMarker(c, null);
+                            } catch (InterruptedException e) {
+                                Log.d(
+                                        Config.LOGTAG,
+                                        "unable to process notification read marker for"
+                                                + " conversation "
+                                                + c.getName());
+                            }
+                        });
                 break;
             case ACTION_SNOOZE:
-                mNotificationExecutor.execute(() -> {
-                    final Conversation c = findConversationByUuid(uuid);
-                    if (c == null) {
-                        Log.d(Config.LOGTAG, "received snooze intent for unknown conversation (" + uuid + ")");
-                        return;
-                    }
-                    c.setMutedTill(System.currentTimeMillis() + 30 * 60 * 1000);
-                    mNotificationService.clearMessages(c);
-                    updateConversation(c);
-                });
+                mNotificationExecutor.execute(
+                        () -> {
+                            final Conversation c = findConversationByUuid(uuid);
+                            if (c == null) {
+                                Log.d(
+                                        Config.LOGTAG,
+                                        "received snooze intent for unknown conversation ("
+                                                + uuid
+                                                + ")");
+                                return;
+                            }
+                            c.setMutedTill(System.currentTimeMillis() + 30 * 60 * 1000);
+                            mNotificationService.clearMessages(c);
+                            updateConversation(c);
+                        });
             case AudioManager.RINGER_MODE_CHANGED_ACTION:
             case NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED:
                 if (dndOnSilentMode()) {
@@ -983,12 +1136,16 @@ public class XmppConnectionService extends Service {
                 final Messenger messenger = intent.getParcelableExtra("messenger");
                 final UnifiedPushBroker.PushTargetMessenger pushTargetMessenger;
                 if (messenger != null && application != null && instance != null) {
-                    pushTargetMessenger = new UnifiedPushBroker.PushTargetMessenger(new UnifiedPushDatabase.PushTarget(application, instance),messenger);
-                    Log.d(Config.LOGTAG,"found push target messenger");
+                    pushTargetMessenger =
+                            new UnifiedPushBroker.PushTargetMessenger(
+                                    new UnifiedPushDatabase.PushTarget(application, instance),
+                                    messenger);
+                    Log.d(Config.LOGTAG, "found push target messenger");
                 } else {
                     pushTargetMessenger = null;
                 }
-                final Optional<UnifiedPushBroker.Transport> transport = renewUnifiedPushEndpoints(pushTargetMessenger);
+                final Optional<UnifiedPushBroker.Transport> transport =
+                        renewUnifiedPushEndpoints(pushTargetMessenger);
                 if (instance != null && transport.isPresent()) {
                     unifiedPushBroker.rebroadcastEndpoint(messenger, instance, transport.get());
                 }
@@ -1078,16 +1235,17 @@ public class XmppConnectionService extends Service {
         }
         if (pingNow) {
             for (final Account account : pingCandidates) {
+                final var connection = account.getXmppConnection();
                 final boolean lowTimeout = isInLowPingTimeoutMode(account);
-                account.getXmppConnection().sendPing();
+                final var delta =
+                        (SystemClock.elapsedRealtime() - connection.getLastPacketReceived())
+                                / 1000L;
+                connection.sendPing();
                 Log.d(
                         Config.LOGTAG,
-                        account.getJid().asBareJid()
-                                + " send ping (action="
-                                + action
-                                + ",lowTimeout="
-                                + lowTimeout
-                                + ")");
+                        String.format(
+                                "%s: send ping (action=%s,lowTimeout=%s,interval=%s)",
+                                account.getJid().asBareJid(), action, lowTimeout, delta));
                 scheduleWakeUpCall(
                         lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT,
                         account.getUuid().hashCode());
@@ -1138,10 +1296,16 @@ public class XmppConnectionService extends Service {
         }
     }
 
-    private boolean processAccountState(final Account account, final boolean interactive, final boolean isUiAction, final boolean isAccountPushed, final HashSet<Account> pingCandidates) {
+    private boolean processAccountState(
+            final Account account,
+            final boolean interactive,
+            final boolean isUiAction,
+            final boolean isAccountPushed,
+            final HashSet<Account> pingCandidates) {
         if (!account.getStatus().isAttemptReconnect()) {
             return false;
         }
+        final var requestCode = account.getUuid().hashCode();
         if (!hasInternetConnection()) {
             account.setStatus(Account.State.NO_INTERNET);
             statusListener.onStatusChanged(account);
@@ -1154,31 +1318,44 @@ public class XmppConnectionService extends Service {
                 synchronized (mLowPingTimeoutMode) {
                     long lastReceived = account.getXmppConnection().getLastPacketReceived();
                     long lastSent = account.getXmppConnection().getLastPingSent();
-                    long pingInterval = isUiAction ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000;
-                    long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime();
-                    int pingTimeout = mLowPingTimeoutMode.contains(account.getJid().asBareJid()) ? Config.LOW_PING_TIMEOUT * 1000 : Config.PING_TIMEOUT * 1000;
+                    long pingInterval =
+                            isUiAction
+                                    ? Config.PING_MIN_INTERVAL * 1000
+                                    : Config.PING_MAX_INTERVAL * 1000;
+                    long msToNextPing =
+                            (Math.max(lastReceived, lastSent) + pingInterval)
+                                    - SystemClock.elapsedRealtime();
+                    int pingTimeout =
+                            mLowPingTimeoutMode.contains(account.getJid().asBareJid())
+                                    ? Config.LOW_PING_TIMEOUT * 1000
+                                    : Config.PING_TIMEOUT * 1000;
                     long pingTimeoutIn = (lastSent + pingTimeout) - SystemClock.elapsedRealtime();
                     if (lastSent > lastReceived) {
                         if (pingTimeoutIn < 0) {
                             Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ping timeout");
                             this.reconnectAccount(account, true, interactive);
                         } else {
-                            int secs = (int) (pingTimeoutIn / 1000);
-                            this.scheduleWakeUpCall(secs, account.getUuid().hashCode());
+                            this.scheduleWakeUpCall(pingTimeoutIn, requestCode);
                         }
                     } else {
                         pingCandidates.add(account);
                         if (isAccountPushed) {
                             if (mLowPingTimeoutMode.add(account.getJid().asBareJid())) {
-                                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": entering low ping timeout mode");
+                                Log.d(
+                                        Config.LOGTAG,
+                                        account.getJid().asBareJid()
+                                                + ": entering low ping timeout mode");
                             }
                             return true;
                         } else if (msToNextPing <= 0) {
                             return true;
                         } else {
-                            this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode());
+                            this.scheduleWakeUpCall(msToNextPing, requestCode);
                             if (mLowPingTimeoutMode.remove(account.getJid().asBareJid())) {
-                                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": leaving low ping timeout mode");
+                                Log.d(
+                                        Config.LOGTAG,
+                                        account.getJid().asBareJid()
+                                                + ": leaving low ping timeout mode");
                             }
                         }
                     }
  
  
  
    
    @@ -0,0 +1,68 @@
+package eu.siacs.conversations.ui;
+
+import android.os.Bundle;
+import android.widget.Toast;
+
+import androidx.databinding.DataBindingUtil;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityAddReactionBinding;
+
+public class AddReactionActivity extends XmppActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final ActivityAddReactionBinding binding =
+                DataBindingUtil.setContentView(this, R.layout.activity_add_reaction);
+        Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
+
+        setSupportActionBar(binding.toolbar);
+        binding.toolbar.setNavigationIcon(R.drawable.ic_clear_24dp);
+        binding.toolbar.setNavigationOnClickListener(v -> finish());
+        setTitle(R.string.add_reaction_title);
+        binding.emojiPicker.setOnEmojiPickedListener(
+                emojiViewItem -> addReaction(emojiViewItem.getEmoji()));
+    }
+
+    private void addReaction(final String emoji) {
+        final var intent = getIntent();
+        final var conversation = intent == null ? null : intent.getStringExtra("conversation");
+        final var message = intent == null ? null : intent.getStringExtra("message");
+        if (Strings.isNullOrEmpty(conversation) || Strings.isNullOrEmpty(message)) {
+            Toast.makeText(this, R.string.could_not_add_reaction, Toast.LENGTH_LONG).show();
+            return;
+        }
+        final var c = xmppConnectionService.findConversationByUuid(conversation);
+        final var m = c == null ? null : c.findMessageWithUuid(message);
+        if (m == null) {
+            Toast.makeText(this, R.string.could_not_add_reaction, Toast.LENGTH_LONG).show();
+            return;
+        }
+        final var aggregated = m.getAggregatedReactions();
+        if (aggregated.ourReactions.contains(emoji)) {
+            if (!xmppConnectionService.sendReactions(m, aggregated.ourReactions)) {
+                Toast.makeText(this, R.string.could_not_add_reaction, Toast.LENGTH_LONG).show();
+                return;
+            }
+        } else {
+            final ImmutableSet.Builder<String> reactionBuilder = new ImmutableSet.Builder<>();
+            reactionBuilder.addAll(aggregated.ourReactions);
+            reactionBuilder.add(emoji);
+            if (!xmppConnectionService.sendReactions(m, reactionBuilder.build())) {
+                Toast.makeText(this, R.string.could_not_add_reaction, Toast.LENGTH_LONG).show();
+            }
+        }
+        finish();
+    }
+
+    @Override
+    protected void refreshUiReal() {}
+
+    @Override
+    protected void onBackendConnected() {}
+}
  
  
  
    
    @@ -9,12 +9,10 @@ import com.cheogram.android.EmojiSearch;
 import com.google.android.material.chip.Chip;
 import com.google.android.material.chip.ChipGroup;
 import com.google.android.material.color.MaterialColors;
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableSet;
 
 import eu.siacs.conversations.R;
-import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.Reaction;
 import eu.siacs.conversations.utils.UIHelper;
 
@@ -23,33 +21,35 @@ import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
+import java.util.function.Function;
 
 public class BindingAdapters {
 
     public static void setReactionsOnReceived(
             final ChipGroup chipGroup,
-            final Conversation conversation,
             final Reaction.Aggregated reactions,
             final Consumer<Collection<String>> onModifiedReactions,
+            final Function<Map.Entry<EmojiSearch.Emoji, Collection<Reaction>>, Boolean> onDetailsClicked,
             final Consumer<EmojiSearch.CustomEmoji> onCustomReaction,
             final Consumer<Reaction> onCustomReactionRemove,
             final Runnable addReaction) {
-        setReactions(chipGroup, conversation, reactions, true, onModifiedReactions, onCustomReaction, onCustomReactionRemove, addReaction);
+        setReactions(chipGroup, reactions, true, onModifiedReactions, onDetailsClicked, onCustomReaction, onCustomReactionRemove, addReaction);
     }
 
     public static void setReactionsOnSent(
             final ChipGroup chipGroup,
             final Reaction.Aggregated reactions,
-            final Consumer<Collection<String>> onModifiedReactions) {
-        setReactions(chipGroup, null, reactions, false, onModifiedReactions, null, null, null);
+            final Consumer<Collection<String>> onModifiedReactions,
+            final Function<Map.Entry<EmojiSearch.Emoji, Collection<Reaction>>, Boolean> onDetailsClicked) {
+        setReactions(chipGroup, reactions, false, onModifiedReactions, onDetailsClicked, null, null, null);
     }
 
     private static void setReactions(
             final ChipGroup chipGroup,
-            final Conversation conversation,
             final Reaction.Aggregated aggregated,
             final boolean onReceived,
             final Consumer<Collection<String>> onModifiedReactions,
+            final Function<Map.Entry<EmojiSearch.Emoji, Collection<Reaction>>, Boolean> onDetailsClicked,
             final Consumer<EmojiSearch.CustomEmoji> onCustomReaction,
             final Consumer<Reaction> onCustomReactionRemove,
             final Runnable addReaction) {
@@ -113,14 +113,7 @@ public class BindingAdapters {
                                 }
                             }
                         });
-                chip.setOnLongClickListener(v -> {
-                    final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
-                    builder.setTitle(emoji.toString());
-                    builder.setMessage(reaction.getValue().stream().map(r -> UIHelper.getDisplayName(conversation, r)).collect(Collectors.joining("\n")));
-                    builder.setPositiveButton(context.getResources().getString(R.string.ok), null);
-                    builder.create().show();
-                    return true;
-                });
+                chip.setOnLongClickListener(v -> onDetailsClicked.apply(reaction));
                 chipGroup.addView(chip);
             }
             if (addReaction != null) {
  
  
  
    
    @@ -1,6 +1,5 @@
 package eu.siacs.conversations.ui;
 
-import android.app.AlertDialog;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -17,11 +16,9 @@ import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 import android.widget.TextView;
 import android.widget.Toast;
-
 import androidx.annotation.NonNull;
 import androidx.core.content.ContextCompat;
 import androidx.databinding.DataBindingUtil;
-
 import com.google.android.material.color.MaterialColors;
 import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 import com.google.common.base.Strings;
@@ -35,7 +32,6 @@ import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.databinding.ActivityChannelDiscoveryBinding;
 import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.entities.Bookmark;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.Room;
 import eu.siacs.conversations.services.ChannelDiscoveryService;
@@ -46,7 +42,11 @@ import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
 import eu.siacs.conversations.utils.AccountUtils;
 import eu.siacs.conversations.xmpp.Jid;
 
-public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.OnActionExpandListener, TextView.OnEditorActionListener, ChannelDiscoveryService.OnChannelSearchResultsFound, ChannelSearchResultAdapter.OnChannelSearchResultSelected {
+public class ChannelDiscoveryActivity extends XmppActivity
+        implements MenuItem.OnActionExpandListener,
+                TextView.OnEditorActionListener,
+                ChannelDiscoveryService.OnChannelSearchResultsFound,
+                ChannelSearchResultAdapter.OnChannelSearchResultSelected {
 
     private static final String CHANNEL_DISCOVERY_OPT_IN = "channel_discovery_opt_in";
 
@@ -63,9 +63,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
     private boolean optedIn = false;
 
     @Override
-    protected void refreshUiReal() {
-
-    }
+    protected void refreshUiReal() {}
 
     @Override
     protected void onBackendConnected() {
@@ -101,7 +99,8 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
         this.adapter.setOnChannelSearchResultSelectedListener(this);
         this.optedIn = getPreferences().getBoolean(CHANNEL_DISCOVERY_OPT_IN, false);
 
-        final String search = savedInstanceState == null ? null : savedInstanceState.getString("search");
+        final String search =
+                savedInstanceState == null ? null : savedInstanceState.getString("search");
         if (search != null) {
             mInitialSearchValue.push(search);
         }
@@ -111,14 +110,17 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
 
     private ChannelDiscoveryService.Method getMethod(final Context c) {
         if (this.mucServices != null) return ChannelDiscoveryService.Method.LOCAL_SERVER;
-        if ( Strings.isNullOrEmpty(Config.CHANNEL_DISCOVERY)) {
+        if (Strings.isNullOrEmpty(Config.CHANNEL_DISCOVERY)) {
             return ChannelDiscoveryService.Method.LOCAL_SERVER;
         }
         if (QuickConversationsService.isQuicksy()) {
             return ChannelDiscoveryService.Method.JABBER_NETWORK;
         }
         final SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(c);
-        final String m = p.getString("channel_discovery_method", c.getString(R.string.default_channel_discovery));
+        final String m =
+                p.getString(
+                        "channel_discovery_method",
+                        c.getString(R.string.default_channel_discovery));
         try {
             return ChannelDiscoveryService.Method.valueOf(m);
         } catch (IllegalArgumentException e) {
@@ -139,7 +141,8 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
             mMenuSearchView.expandActionView();
             mSearchEditText.append(initialSearchValue);
             mSearchEditText.requestFocus();
-            if ((optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) && xmppConnectionService != null) {
+            if ((optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER)
+                    && xmppConnectionService != null) {
                 xmppConnectionService.discoverChannels(initialSearchValue, this.method, this.mucServices, this);
             }
         }
@@ -150,18 +153,22 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
 
     @Override
     public boolean onMenuItemActionExpand(@NonNull MenuItem item) {
-        mSearchEditText.post(() -> {
-            mSearchEditText.requestFocus();
-            final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
-            imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
-        });
+        mSearchEditText.post(
+                () -> {
+                    mSearchEditText.requestFocus();
+                    final InputMethodManager imm =
+                            (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+                    imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
+                });
         return true;
     }
 
     @Override
     public boolean onMenuItemActionCollapse(@NonNull MenuItem item) {
-        final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
-        imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
+        final InputMethodManager imm =
+                (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+        imm.hideSoftInputFromWindow(
+                mSearchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
         mSearchEditText.setText("");
         toggleLoadingScreen();
         if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) {
@@ -173,7 +180,9 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
     private void toggleLoadingScreen() {
         adapter.submitList(Collections.emptyList());
         binding.progressBar.setVisibility(View.VISIBLE);
-        binding.list.setBackgroundColor(MaterialColors.getColor(binding.list, com.google.android.material.R.attr.colorSurface));
+        binding.list.setBackgroundColor(
+                MaterialColors.getColor(
+                        binding.list, com.google.android.material.R.attr.colorSurface));
     }
 
     @Override
@@ -188,13 +197,14 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
             builder.setPositiveButton(R.string.confirm, (dialog, which) -> optIn());
             builder.setOnCancelListener(dialog -> finish());
             final androidx.appcompat.app.AlertDialog dialog = builder.create();
-            dialog.setOnShowListener(d -> {
-                final TextView textView = dialog.findViewById(android.R.id.message);
-                if (textView == null) {
-                    return;
-                }
-                textView.setMovementMethod(LinkMovementMethod.getInstance());
-            });
+            dialog.setOnShowListener(
+                    d -> {
+                        final TextView textView = dialog.findViewById(android.R.id.message);
+                        if (textView == null) {
+                            return;
+                        }
+                        textView.setMovementMethod(LinkMovementMethod.getInstance());
+                    });
             dialog.setCanceledOnTouchOutside(false);
             dialog.show();
             holdLoading();
@@ -204,13 +214,17 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
     private void holdLoading() {
         adapter.submitList(Collections.emptyList());
         binding.progressBar.setVisibility(View.GONE);
-        binding.list.setBackgroundColor(MaterialColors.getColor(binding.list, com.google.android.material.R.attr.colorSurface));
+        binding.list.setBackgroundColor(
+                MaterialColors.getColor(
+                        binding.list, com.google.android.material.R.attr.colorSurface));
     }
 
     @Override
     public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
         if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) {
-            savedInstanceState.putString("search", mSearchEditText != null ? mSearchEditText.getText().toString() : null);
+            savedInstanceState.putString(
+                    "search",
+                    mSearchEditText != null ? mSearchEditText.getText().toString() : null);
         }
         super.onSaveInstanceState(savedInstanceState);
     }
@@ -235,16 +249,20 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
 
     @Override
     public void onChannelSearchResultsFound(final List<Room> results) {
-        runOnUiThread(() -> {
-            adapter.submitList(results);
-            binding.progressBar.setVisibility(View.GONE);
-            if (results.isEmpty()) {
-                binding.list.setBackground(ContextCompat.getDrawable(this,R.drawable.background_no_results));
-            } else {
-                binding.list.setBackgroundColor(MaterialColors.getColor(binding.list, com.google.android.material.R.attr.colorSurface));
-            }
-        });
-
+        runOnUiThread(
+                () -> {
+                    adapter.submitList(results);
+                    binding.progressBar.setVisibility(View.GONE);
+                    if (results.isEmpty()) {
+                        binding.list.setBackground(
+                                ContextCompat.getDrawable(this, R.drawable.background_no_results));
+                    } else {
+                        binding.list.setBackgroundColor(
+                                MaterialColors.getColor(
+                                        binding.list,
+                                        com.google.android.material.R.attr.colorSurface));
+                    }
+                });
     }
 
     @Override
@@ -258,12 +276,16 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
             final AtomicReference<String> account = new AtomicReference<>(accounts.get(0));
             final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
             builder.setTitle(R.string.choose_account);
-            builder.setSingleChoiceItems(accounts.toArray(new CharSequence[0]), 0, (dialog, which) -> account.set(accounts.get(which)));
-            builder.setPositiveButton(R.string.join, (dialog, which) -> joinChannelSearchResult(account.get(), result));
+            builder.setSingleChoiceItems(
+                    accounts.toArray(new CharSequence[0]),
+                    0,
+                    (dialog, which) -> account.set(accounts.get(which)));
+            builder.setPositiveButton(
+                    R.string.join,
+                    (dialog, which) -> joinChannelSearchResult(account.get(), result));
             builder.setNegativeButton(R.string.cancel, null);
             builder.create().show();
         }
-
     }
 
     @Override
@@ -294,17 +316,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
         final Conversation conversation =
                 xmppConnectionService.findOrCreateConversation(
                         account, result.getRoom(), true, true, true);
-        final var existingBookmark = conversation.getBookmark();
-        if (existingBookmark == null) {
-            final var bookmark = new Bookmark(account, conversation.getJid().asBareJid());
-            bookmark.setAutojoin(true);
-            xmppConnectionService.createBookmark(account, bookmark);
-        } else {
-            if (!existingBookmark.autojoin()) {
-                existingBookmark.setAutojoin(true);
-                xmppConnectionService.createBookmark(account, existingBookmark);
-            }
-        }
+        xmppConnectionService.ensureBookmarkIsAutoJoin(conversation);
         switchToConversation(conversation);
     }
 }
  
  
  
    
    @@ -1,5 +1,8 @@
 package eu.siacs.conversations.ui;
 
+import static eu.siacs.conversations.entities.Bookmark.printableValue;
+import static eu.siacs.conversations.utils.StringUtils.changed;
+
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -7,6 +10,7 @@ import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.ColorStateList;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.preference.PreferenceManager;
 import android.text.Editable;
@@ -31,6 +35,7 @@ import androidx.databinding.DataBindingUtil;
 
 import com.cheogram.android.Util;
 
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 import com.google.android.material.color.MaterialColors;
 import com.google.common.collect.ImmutableList;
 import com.google.common.primitives.Ints;
@@ -77,14 +82,17 @@ import eu.siacs.conversations.utils.XmppUri;
 import eu.siacs.conversations.utils.XEP0392Helper;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.XmppConnection;
-import me.drakeet.support.toast.ToastCompat;
-
-import static eu.siacs.conversations.entities.Bookmark.printableValue;
-import static eu.siacs.conversations.utils.StringUtils.changed;
 
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import me.drakeet.support.toast.ToastCompat;
 
-public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnConfigurationPushed, XmppConnectionService.OnRoomDestroy, TextWatcher, OnMediaLoaded {
+public class ConferenceDetailsActivity extends XmppActivity
+        implements OnConversationUpdate,
+                OnMucRosterUpdate,
+                XmppConnectionService.OnAffiliationChanged,
+                XmppConnectionService.OnConfigurationPushed,
+                XmppConnectionService.OnRoomDestroy,
+                TextWatcher,
+                OnMediaLoaded {
     public static final String ACTION_VIEW_MUC = "view_muc";
 
     private Conversation mConversation;
@@ -96,26 +104,25 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
     private boolean mAdvancedMode = false;
     private boolean showDynamicTags = true;
 
-    private final UiCallback<Conversation> renameCallback = new UiCallback<Conversation>() {
-        @Override
-        public void success(Conversation object) {
-            displayToast(getString(R.string.your_nick_has_been_changed));
-            runOnUiThread(() -> {
-                updateView();
-            });
-
-        }
-
-        @Override
-        public void error(final int errorCode, Conversation object) {
-            displayToast(getString(errorCode));
-        }
+    private final UiCallback<Conversation> renameCallback =
+            new UiCallback<Conversation>() {
+                @Override
+                public void success(Conversation object) {
+                    displayToast(getString(R.string.your_nick_has_been_changed));
+                    runOnUiThread(
+                            () -> {
+                                updateView();
+                            });
+                }
 
-        @Override
-        public void userInputRequired(PendingIntent pi, Conversation object) {
+                @Override
+                public void error(final int errorCode, Conversation object) {
+                    displayToast(getString(errorCode));
+                }
 
-        }
-    };
+                @Override
+                public void userInputRequired(PendingIntent pi, Conversation object) {}
+            };
 
     public static void open(final Activity activity, final Conversation conversation) {
         Intent intent = new Intent(activity, ConferenceDetailsActivity.class);
@@ -124,39 +131,49 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
         activity.startActivity(intent);
     }
 
-    private final OnClickListener mNotifyStatusClickListener = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ConferenceDetailsActivity.this);
-            builder.setTitle(R.string.pref_notification_settings);
-            String[] choices = {
-                    getString(R.string.notify_on_all_messages),
-                    getString(R.string.notify_only_when_highlighted),
+    private final OnClickListener mNotifyStatusClickListener =
+            new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    final MaterialAlertDialogBuilder builder =
+                            new MaterialAlertDialogBuilder(ConferenceDetailsActivity.this);
+                    builder.setTitle(R.string.pref_notification_settings);
+                    String[] choices = {
+                        getString(R.string.notify_on_all_messages),
+                        getString(R.string.notify_only_when_highlighted),
                     getString(R.string.notify_only_when_highlighted_or_replied),
-                    getString(R.string.notify_never)
-            };
-            final AtomicInteger choice;
-            if (mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0) == Long.MAX_VALUE) {
-                choice = new AtomicInteger(3);
-            } else {
-                choice = new AtomicInteger(mConversation.alwaysNotify() ? 0 : (mConversation.notifyReplies() ? 2 : 1));
-            }
-            builder.setSingleChoiceItems(choices, choice.get(), (dialog, which) -> choice.set(which));
-            builder.setNegativeButton(R.string.cancel, null);
-            builder.setPositiveButton(R.string.ok, (dialog, which) -> {
-                if (choice.get() == 3) {
-                    mConversation.setMutedTill(Long.MAX_VALUE);
-                } else {
-                    mConversation.setMutedTill(0);
-                    mConversation.setAttribute(Conversation.ATTRIBUTE_ALWAYS_NOTIFY, String.valueOf(choice.get() == 0));
-                    mConversation.setAttribute(Conversation.ATTRIBUTE_NOTIFY_REPLIES, String.valueOf(choice.get() == 2));
+                        getString(R.string.notify_never)
+                    };
+                    final AtomicInteger choice;
+                    if (mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0)
+                            == Long.MAX_VALUE) {
+                        choice = new AtomicInteger(3);
+                    } else {
+                        choice = new AtomicInteger(mConversation.alwaysNotify() ? 0 : (mConversation.notifyReplies() ? 2 : 1));
+                    }
+                    builder.setSingleChoiceItems(
+                            choices, choice.get(), (dialog, which) -> choice.set(which));
+                    builder.setNegativeButton(R.string.cancel, null);
+                    builder.setPositiveButton(
+                            R.string.ok,
+                            (dialog, which) -> {
+                                if (choice.get() == 3) {
+                                    mConversation.setMutedTill(Long.MAX_VALUE);
+                                } else {
+                                    mConversation.setMutedTill(0);
+                                    mConversation.setAttribute(
+                                            Conversation.ATTRIBUTE_ALWAYS_NOTIFY,
+                                            String.valueOf(choice.get() == 0));
+                                    mConversation.setAttribute(
+                                            Conversation.ATTRIBUTE_NOTIFY_REPLIES,
+                                            String.valueOf(choice.get() == 2));
+                                }
+                                xmppConnectionService.updateConversation(mConversation);
+                                updateView();
+                            });
+                    builder.create().show();
                 }
-                xmppConnectionService.updateConversation(mConversation);
-                updateView();
-            });
-            builder.create().show();
-        }
-    };
+            };
 
     private final OnClickListener mChangeConferenceSettings =
             new OnClickListener() {
@@ -217,32 +234,49 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
         this.binding.changeConferenceButton.setOnClickListener(this.mChangeConferenceSettings);
         setSupportActionBar(binding.toolbar);
         configureActionBar(getSupportActionBar());
-        this.binding.editNickButton.setOnClickListener(v -> quickEdit(mConversation.getMucOptions().getActualNick(),
-                R.string.nickname,
-                value -> {
-                    if (xmppConnectionService.renameInMuc(mConversation, value, renameCallback)) {
-                        return null;
-                    } else {
-                        return getString(R.string.invalid_muc_nick);
-                    }
-                }));
+        this.binding.editNickButton.setOnClickListener(
+                v ->
+                        quickEdit(
+                                mConversation.getMucOptions().getActualNick(),
+                                R.string.nickname,
+                                value -> {
+                                    if (xmppConnectionService.renameInMuc(
+                                            mConversation, value, renameCallback)) {
+                                        return null;
+                                    } else {
+                                        return getString(R.string.invalid_muc_nick);
+                                    }
+                                }));
         this.mAdvancedMode = getPreferences().getBoolean("advanced_muc_mode", false);
         this.binding.mucInfoMore.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
         this.binding.notificationStatusButton.setOnClickListener(this.mNotifyStatusClickListener);
-        this.binding.yourPhoto.setOnClickListener(v -> {
-            final MucOptions mucOptions = mConversation.getMucOptions();
-            if (!mucOptions.hasVCards()) {
-                Toast.makeText(this, R.string.host_does_not_support_group_chat_avatars, Toast.LENGTH_SHORT).show();
-                return;
-            }
-            if (!mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
-                Toast.makeText(this, R.string.only_the_owner_can_change_group_chat_avatar, Toast.LENGTH_SHORT).show();
-                return;
-            }
-            final Intent intent = new Intent(this, PublishGroupChatProfilePictureActivity.class);
-            intent.putExtra("uuid", mConversation.getUuid());
-            startActivity(intent);
-        });
+        this.binding.yourPhoto.setOnClickListener(
+                v -> {
+                    final MucOptions mucOptions = mConversation.getMucOptions();
+                    if (!mucOptions.hasVCards()) {
+                        Toast.makeText(
+                                        this,
+                                        R.string.host_does_not_support_group_chat_avatars,
+                                        Toast.LENGTH_SHORT)
+                                .show();
+                        return;
+                    }
+                    if (!mucOptions
+                            .getSelf()
+                            .getAffiliation()
+                            .ranks(MucOptions.Affiliation.OWNER)) {
+                        Toast.makeText(
+                                        this,
+                                        R.string.only_the_owner_can_change_group_chat_avatar,
+                                        Toast.LENGTH_SHORT)
+                                .show();
+                        return;
+                    }
+                    final Intent intent =
+                            new Intent(this, PublishGroupChatProfilePictureActivity.class);
+                    intent.putExtra("uuid", mConversation.getUuid());
+                    startActivity(intent);
+                });
         this.binding.yourPhoto.setOnLongClickListener(v -> {
             PopupMenu popupMenu = new PopupMenu(this, v);
             popupMenu.inflate(R.menu.conference_photo);
@@ -267,11 +301,13 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
             popupMenu.show();
             return true;
         });
-        this.binding.editMucNameButton.setContentDescription(getString(R.string.edit_name_and_topic));
+        this.binding.editMucNameButton.setContentDescription(
+                getString(R.string.edit_name_and_topic));
         this.binding.editMucNameButton.setOnClickListener(this::onMucEditButtonClicked);
         this.binding.mucEditTitle.addTextChangedListener(this);
         this.binding.mucEditSubject.addTextChangedListener(this);
-        //this.binding.mucEditSubject.addTextChangedListener(new StylingHelper.MessageEditorStyler(this.binding.mucEditSubject));
+        //this.binding.mucEditSubject.addTextChangedListener(
+        //        new StylingHelper.MessageEditorStyler(this.binding.mucEditSubject));
         this.binding.editTags.addTextChangedListener(this);
         this.mMediaAdapter = new MediaAdapter(this, R.dimen.media_size);
         this.mUserPreviewAdapter = new UserPreviewAdapter();
@@ -284,11 +320,12 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
             switchToConversation(mConversation, null, false, null, false, true, null, thread.getThreadId());
         });
         this.binding.invite.setOnClickListener(v -> inviteToConversation(mConversation));
-        this.binding.showUsers.setOnClickListener(v -> {
-            Intent intent = new Intent(this, MucUsersActivity.class);
-            intent.putExtra("uuid", mConversation.getUuid());
-            startActivity(intent);
-        });
+        this.binding.showUsers.setOnClickListener(
+                v -> {
+                    Intent intent = new Intent(this, MucUsersActivity.class);
+                    intent.putExtra("uuid", mConversation.getUuid());
+                    startActivity(intent);
+                });
         this.binding.relatedMucs.setOnClickListener(v -> {
             final Intent intent = new Intent(this, ChannelDiscoveryActivity.class);
             intent.putExtra("services", new String[]{ mConversation.getJid().getDomain().toEscapedString(), mConversation.getAccount().getJid().toEscapedString() });
@@ -299,7 +336,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
     @Override
     public void onStart() {
         super.onStart();
-        binding.mediaWrapper.setVisibility(Compatibility.hasStoragePermission(this) ? View.VISIBLE : View.GONE);
+        binding.mediaWrapper.setVisibility(
+                Compatibility.hasStoragePermission(this) ? View.VISIBLE : View.GONE);
     }
 
     @Override
@@ -327,23 +365,43 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
                 this.mAdvancedMode = !menuItem.isChecked();
                 menuItem.setChecked(this.mAdvancedMode);
                 getPreferences().edit().putBoolean("advanced_muc_mode", mAdvancedMode).apply();
-                final boolean online = mConversation != null && mConversation.getMucOptions().online();
-                this.binding.mucInfoMore.setVisibility(this.mAdvancedMode && online ? View.VISIBLE : View.GONE);
+                final boolean online =
+                        mConversation != null && mConversation.getMucOptions().online();
+                this.binding.mucInfoMore.setVisibility(
+                        this.mAdvancedMode && online ? View.VISIBLE : View.GONE);
                 invalidateOptionsMenu();
                 updateView();
                 break;
+            case R.id.action_custom_notifications:
+                if (mConversation != null) {
+                    configureCustomNotifications(mConversation);
+                }
+                break;
         }
         return super.onOptionsItemSelected(menuItem);
     }
 
+    private void configureCustomNotifications(final Conversation conversation) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R
+                || conversation.getMode() != Conversation.MODE_MULTI) {
+            return;
+        }
+        final var shortcut =
+                xmppConnectionService
+                        .getShortcutService()
+                        .getShortcutInfo(conversation.getMucOptions());
+        configureCustomNotification(shortcut);
+    }
+
     @Override
-    public boolean onContextItemSelected(MenuItem item) {
+    public boolean onContextItemSelected(@NonNull final MenuItem item) {
         final User user = mUserPreviewAdapter.getSelectedUser();
         if (user == null) {
             Toast.makeText(this, R.string.unable_to_perform_this_action, Toast.LENGTH_SHORT).show();
             return true;
         }
-        if (!MucDetailsContextMenuHelper.onContextItemSelected(item, mUserPreviewAdapter.getSelectedUser(), this)) {
+        if (!MucDetailsContextMenuHelper.onContextItemSelected(
+                item, mUserPreviewAdapter.getSelectedUser(), this)) {
             return super.onContextItemSelected(item);
         }
         return true;
@@ -358,7 +416,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
             this.binding.editMucNameButton.setContentDescription(getString(R.string.cancel));
             final String name = mucOptions.getName();
             this.binding.mucEditTitle.setText("");
-            final boolean owner = mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER);
+            final boolean owner =
+                    mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER);
             if (owner || printableValue(name)) {
                 this.binding.mucEditTitle.setVisibility(View.VISIBLE);
                 if (name != null) {
@@ -410,8 +469,14 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
                 this.binding.editTags.setVisibility(View.GONE);
             }
         } else {
-            String subject = this.binding.mucEditSubject.isEnabled() ? this.binding.mucEditSubject.getEditableText().toString().trim() : null;
-            String name = this.binding.mucEditTitle.isEnabled() ? this.binding.mucEditTitle.getEditableText().toString().trim() : null;
+            String subject =
+                    this.binding.mucEditSubject.isEnabled()
+                            ? this.binding.mucEditSubject.getEditableText().toString().trim()
+                            : null;
+            String name =
+                    this.binding.mucEditTitle.isEnabled()
+                            ? this.binding.mucEditTitle.getEditableText().toString().trim()
+                            : null;
             onMucInfoUpdated(subject, name);
 
             final Bookmark bookmark = mConversation.getBookmark();
@@ -430,7 +495,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
         this.binding.mucEditor.setVisibility(View.GONE);
         this.binding.mucDisplay.setVisibility(View.VISIBLE);
         this.binding.editMucNameButton.setImageResource(R.drawable.ic_edit_24dp);
-        this.binding.editMucNameButton.setContentDescription(getString(R.string.edit_name_and_topic));
+        this.binding.editMucNameButton.setContentDescription(
+                getString(R.string.edit_name_and_topic));
     }
 
     private void onMucInfoUpdated(String subject, String name) {
@@ -438,7 +504,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
         if (mucOptions.canChangeSubject() && changed(mucOptions.getSubject(), subject)) {
             xmppConnectionService.pushSubjectToConference(mConversation, subject);
         }
-        if (mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER) && changed(mucOptions.getName(), name)) {
+        if (mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER)
+                && changed(mucOptions.getName(), name)) {
             Bundle options = new Bundle();
             options.putString("muc#roomconfig_persistentroom", "1");
             options.putString("muc#roomconfig_roomname", StringUtils.nullOnEmpty(name));
@@ -446,12 +513,13 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
         }
     }
 
-
     @Override
     protected String getShareableUri(boolean http) {
         if (mConversation != null) {
             if (http) {
-                return "https://conversations.im/j/" + XmppUri.lameUrlEncode(mConversation.getJid().asBareJid().toEscapedString());
+                return "https://conversations.im/j/"
+                        + XmppUri.lameUrlEncode(
+                                mConversation.getJid().asBareJid().toEscapedString());
             } else {
                 return "xmpp:" + Uri.encode(mConversation.getJid().asBareJid().toEscapedString(), "@/+") + "?join";
             }
@@ -470,7 +538,12 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
             return true;
         }
         menuItemSaveBookmark.setVisible(mConversation.getBookmark() == null);
-        menuItemDestroyRoom.setVisible(mConversation.getMucOptions().getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER));
+        menuItemDestroyRoom.setVisible(
+                mConversation
+                        .getMucOptions()
+                        .getSelf()
+                        .getAffiliation()
+                        .ranks(MucOptions.Affiliation.OWNER));
         return true;
     }
 
@@ -483,32 +556,40 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
         final MenuItem destroy = menu.findItem(R.id.action_destroy_room);
         destroy.setTitle(groupChat ? R.string.destroy_room : R.string.destroy_channel);
         AccountUtils.showHideMenuItems(menu);
+        final MenuItem customNotifications = menu.findItem(R.id.action_custom_notifications);
+        customNotifications.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
         return super.onCreateOptionsMenu(menu);
     }
 
     @Override
-    public void onMediaLoaded(List<Attachment> attachments) {
-        runOnUiThread(() -> {
-            int limit = GridManager.getCurrentColumnCount(binding.media);
-            mMediaAdapter.setAttachments(attachments.subList(0, Math.min(limit, attachments.size())));
-            binding.mediaWrapper.setVisibility(attachments.size() > 0 ? View.VISIBLE : View.GONE);
-        });
-
+    public void onMediaLoaded(final List<Attachment> attachments) {
+        runOnUiThread(
+                () -> {
+                    final int limit = GridManager.getCurrentColumnCount(binding.media);
+                    mMediaAdapter.setAttachments(
+                            attachments.subList(0, Math.min(limit, attachments.size())));
+                    binding.mediaWrapper.setVisibility(
+                            attachments.isEmpty() ? View.GONE : View.VISIBLE);
+                });
     }
 
-
     protected void saveAsBookmark() {
-        xmppConnectionService.saveConversationAsBookmark(mConversation, mConversation.getMucOptions().getName());
+        xmppConnectionService.saveConversationAsBookmark(
+                mConversation, mConversation.getMucOptions().getName());
     }
 
     protected void destroyRoom() {
         final boolean groupChat = mConversation != null && mConversation.isPrivateAndNonAnonymous();
         final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
         builder.setTitle(groupChat ? R.string.destroy_room : R.string.destroy_channel);
-        builder.setMessage(groupChat ? R.string.destroy_room_dialog : R.string.destroy_channel_dialog);
-        builder.setPositiveButton(R.string.ok, (dialog, which) -> {
-            xmppConnectionService.destroyRoom(mConversation, ConferenceDetailsActivity.this);
-        });
+        builder.setMessage(
+                groupChat ? R.string.destroy_room_dialog : R.string.destroy_channel_dialog);
+        builder.setPositiveButton(
+                R.string.ok,
+                (dialog, which) -> {
+                    xmppConnectionService.destroyRoom(
+                            mConversation, ConferenceDetailsActivity.this);
+                });
         builder.setNegativeButton(R.string.cancel, null);
         final AlertDialog dialog = builder.create();
         dialog.setCanceledOnTouchOutside(false);
@@ -530,7 +611,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
                 if (Compatibility.hasStoragePermission(this)) {
                     final int limit = GridManager.getCurrentColumnCount(this.binding.media);
                     xmppConnectionService.getAttachments(this.mConversation, limit, this);
-                    this.binding.showMedia.setOnClickListener((v) -> MediaBrowserActivity.launch(this, mConversation));
+                    this.binding.showMedia.setOnClickListener(
+                            (v) -> MediaBrowserActivity.launch(this, mConversation));
                 }
 
                 binding.storeInCache.setChecked(mConversation.storeInCache());
@@ -561,20 +643,25 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
         final MucOptions mucOptions = mConversation.getMucOptions();
         final User self = mucOptions.getSelf();
         final String account = mConversation.getAccount().getJid().asBareJid().toEscapedString();
-        setTitle(mucOptions.isPrivateAndNonAnonymous() ? R.string.action_muc_details : R.string.channel_details);
+        setTitle(
+                mucOptions.isPrivateAndNonAnonymous()
+                        ? R.string.action_muc_details
+                        : R.string.channel_details);
         final Bookmark bookmark = mConversation.getBookmark();
         final XmppConnection connection = mConversation.getAccount().getXmppConnection();
         this.binding.editMucNameButton.setVisibility((self.getAffiliation().ranks(MucOptions.Affiliation.OWNER) || mucOptions.canChangeSubject() || (bookmark != null && connection != null && connection.getFeatures().bookmarks2())) ? View.VISIBLE : View.GONE);
         this.binding.detailsAccount.setText(getString(R.string.using_account, account));
         this.binding.truejid.setVisibility(View.GONE);
         if (mConversation.isPrivateAndNonAnonymous()) {
-            this.binding.jid.setText(getString(R.string.hosted_on, mConversation.getJid().getDomain()));
+            this.binding.jid.setText(
+                    getString(R.string.hosted_on, mConversation.getJid().getDomain()));
             this.binding.truejid.setText(mConversation.getJid().asBareJid().toEscapedString());
             if (mAdvancedMode) this.binding.truejid.setVisibility(View.VISIBLE);
         } else {
             this.binding.jid.setText(mConversation.getJid().asBareJid().toEscapedString());
         }
-        AvatarWorkerTask.loadAvatar(mConversation, binding.yourPhoto, R.dimen.avatar_on_details_screen_size);
+        AvatarWorkerTask.loadAvatar(
+                mConversation, binding.yourPhoto, R.dimen.avatar_on_details_screen_size);
         String roomName = mucOptions.getName();
         String subject = mucOptions.getSubject();
         final boolean hasTitle;
@@ -595,7 +682,12 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
             StylingHelper.format(spannable, this.binding.mucSubject.getCurrentTextColor());
             MyLinkify.addLinks(spannable, false);
             this.binding.mucSubject.setText(spannable);
-            this.binding.mucSubject.setTextAppearance( subject.length() > (hasTitle ? 128 : 196) ? com.google.android.material.R.style.TextAppearance_Material3_BodyMedium : com.google.android.material.R.style.TextAppearance_Material3_BodyLarge);
+            this.binding.mucSubject.setTextAppearance(
+                    subject.length() > (hasTitle ? 128 : 196)
+                            ? com.google.android.material.R.style
+                                    .TextAppearance_Material3_BodyMedium
+                            : com.google.android.material.R.style
+                                    .TextAppearance_Material3_BodyLarge);
             this.binding.mucSubject.setAutoLinkMask(0);
             this.binding.mucSubject.setVisibility(View.VISIBLE);
             this.binding.mucSubject.setMovementMethod(LinkMovementMethod.getInstance());
@@ -613,7 +705,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
                 this.binding.mucConferenceType.setText(MucConfiguration.describe(this, mucOptions));
             } else if (!mucOptions.isPrivateAndNonAnonymous() && mucOptions.nonanonymous()) {
                 this.binding.mucSettings.setVisibility(View.VISIBLE);
-                this.binding.mucConferenceType.setText(R.string.group_chat_will_make_your_jabber_id_public);
+                this.binding.mucConferenceType.setText(
+                        R.string.group_chat_will_make_your_jabber_id_public);
             } else {
                 this.binding.mucSettings.setVisibility(View.GONE);
             }
@@ -636,43 +729,55 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
         final long mutedTill = mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
         if (mutedTill == Long.MAX_VALUE) {
             this.binding.notificationStatusText.setText(R.string.notify_never);
-            this.binding.notificationStatusButton.setImageResource(R.drawable.ic_notifications_off_24dp);
+            this.binding.notificationStatusButton.setImageResource(
+                    R.drawable.ic_notifications_off_24dp);
         } else if (System.currentTimeMillis() < mutedTill) {
             this.binding.notificationStatusText.setText(R.string.notify_paused);
-            this.binding.notificationStatusButton.setImageResource(R.drawable.ic_notifications_paused_24dp);
+            this.binding.notificationStatusButton.setImageResource(
+                    R.drawable.ic_notifications_paused_24dp);
         } else if (mConversation.alwaysNotify()) {
             this.binding.notificationStatusText.setText(R.string.notify_on_all_messages);
-            this.binding.notificationStatusButton.setImageResource(R.drawable.ic_notifications_24dp);
+            this.binding.notificationStatusButton.setImageResource(
+                    R.drawable.ic_notifications_24dp);
         } else if (mConversation.notifyReplies()) {
             this.binding.notificationStatusText.setText(R.string.notify_only_when_highlighted_or_replied);
             this.binding.notificationStatusButton.setImageResource(R.drawable.ic_notifications_none_24dp);
         } else {
             this.binding.notificationStatusText.setText(R.string.notify_only_when_highlighted);
-            this.binding.notificationStatusButton.setImageResource(R.drawable.ic_notifications_none_24dp);
+            this.binding.notificationStatusButton.setImageResource(
+                    R.drawable.ic_notifications_none_24dp);
         }
         final List<User> users = mucOptions.getUsers();
-        Collections.sort(users, (a, b) -> {
-            if (b.getAffiliation().outranks(a.getAffiliation())) {
-                return 1;
-            } else if (a.getAffiliation().outranks(b.getAffiliation())) {
-                return -1;
-            } else {
-                if (a.getAvatar() != null && b.getAvatar() == null) {
-                    return -1;
-                } else if (a.getAvatar() == null && b.getAvatar() != null) {
-                    return 1;
-                } else {
-                    return a.getComparableName().compareToIgnoreCase(b.getComparableName());
-                }
-            }
-        });
-        this.mUserPreviewAdapter.submitList(MucOptions.sub(users, GridManager.getCurrentColumnCount(binding.users)));
+        Collections.sort(
+                users,
+                (a, b) -> {
+                    if (b.getAffiliation().outranks(a.getAffiliation())) {
+                        return 1;
+                    } else if (a.getAffiliation().outranks(b.getAffiliation())) {
+                        return -1;
+                    } else {
+                        if (a.getAvatar() != null && b.getAvatar() == null) {
+                            return -1;
+                        } else if (a.getAvatar() == null && b.getAvatar() != null) {
+                            return 1;
+                        } else {
+                            return a.getComparableName().compareToIgnoreCase(b.getComparableName());
+                        }
+                    }
+                });
+        this.mUserPreviewAdapter.submitList(
+                MucOptions.sub(users, GridManager.getCurrentColumnCount(binding.users)));
         this.binding.invite.setVisibility(mucOptions.canInvite() ? View.VISIBLE : View.GONE);
         this.binding.showUsers.setVisibility(mucOptions.getUsers(true, mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.ADMIN)).size() > 0 ? View.VISIBLE : View.GONE);
-        this.binding.showUsers.setText(getResources().getQuantityString(R.plurals.view_users, users.size(), users.size()));
-        this.binding.usersWrapper.setVisibility(users.size() > 0 || mucOptions.canInvite() ? View.VISIBLE : View.GONE);
+        this.binding.showUsers.setText(
+                getResources().getQuantityString(R.plurals.view_users, users.size(), users.size()));
+        this.binding.usersWrapper.setVisibility(
+                users.size() > 0 || mucOptions.canInvite() ? View.VISIBLE : View.GONE);
         if (users.size() == 0) {
-            this.binding.noUsersHints.setText(mucOptions.isPrivateAndNonAnonymous() ? R.string.no_users_hint_group_chat : R.string.no_users_hint_channel);
+            this.binding.noUsersHints.setText(
+                    mucOptions.isPrivateAndNonAnonymous()
+                            ? R.string.no_users_hint_group_chat
+                            : R.string.no_users_hint_channel);
             this.binding.noUsersHints.setVisibility(View.VISIBLE);
         } else {
             this.binding.noUsersHints.setVisibility(View.GONE);
@@ -718,7 +823,10 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
 
     public static String getStatus(Context context, User user, final boolean advanced) {
         if (advanced) {
-            return String.format("%s (%s)", context.getString(user.getAffiliation().getResId()), context.getString(user.getRole().getResId()));
+            return String.format(
+                    "%s (%s)",
+                    context.getString(user.getAffiliation().getResId()),
+                    context.getString(user.getRole().getResId()));
         } else {
             return context.getString(user.getAffiliation().getResId());
         }
@@ -746,7 +854,11 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
     @Override
     public void onRoomDestroyFailed() {
         final boolean groupChat = mConversation != null && mConversation.isPrivateAndNonAnonymous();
-        displayToast(getString(groupChat ? R.string.could_not_destroy_room : R.string.could_not_destroy_channel));
+        displayToast(
+                getString(
+                        groupChat
+                                ? R.string.could_not_destroy_room
+                                : R.string.could_not_destroy_channel));
     }
 
     @Override
@@ -760,23 +872,20 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
     }
 
     private void displayToast(final String msg) {
-        runOnUiThread(() -> {
-            if (isFinishing()) {
-                return;
-            }
-            ToastCompat.makeText(this, msg, Toast.LENGTH_SHORT).show();
-        });
+        runOnUiThread(
+                () -> {
+                    if (isFinishing()) {
+                        return;
+                    }
+                    ToastCompat.makeText(this, msg, Toast.LENGTH_SHORT).show();
+                });
     }
 
     @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-
-    }
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
 
     @Override
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-
-    }
+    public void onTextChanged(CharSequence s, int start, int before, int count) {}
 
     @Override
     public void afterTextChanged(Editable s) {
@@ -785,8 +894,14 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
         }
         final MucOptions mucOptions = mConversation.getMucOptions();
         if (this.binding.mucEditor.getVisibility() == View.VISIBLE) {
-            boolean subjectChanged = changed(binding.mucEditSubject.getEditableText().toString(), mucOptions.getSubject());
-            boolean nameChanged = changed(binding.mucEditTitle.getEditableText().toString(), mucOptions.getName());
+            boolean subjectChanged =
+                    changed(
+                            binding.mucEditSubject.getEditableText().toString(),
+                            mucOptions.getSubject());
+            boolean nameChanged =
+                    changed(
+                            binding.mucEditTitle.getEditableText().toString(),
+                            mucOptions.getName());
             final Bookmark bookmark = mConversation.getBookmark();
             if (subjectChanged || nameChanged || (bookmark != null && mConversation.getAccount().getXmppConnection().getFeatures().bookmarks2())) {
                 this.binding.editMucNameButton.setImageResource(R.drawable.ic_save_24dp);
  
  
  
    
    @@ -33,7 +33,6 @@ import android.widget.CompoundButton.OnCheckedChangeListener;
 import android.widget.EditText;
 import android.widget.TextView;
 import android.widget.Toast;
-
 import androidx.annotation.NonNull;
 import androidx.core.content.ContextCompat;
 import androidx.core.view.ViewCompat;
@@ -98,8 +97,17 @@ import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
 import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 import eu.siacs.conversations.xmpp.XmppConnection;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.openintents.openpgp.util.OpenPgpUtils;
 
-public class ContactDetailsActivity extends OmemoActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated, OnMediaLoaded {
+public class ContactDetailsActivity extends OmemoActivity
+        implements OnAccountUpdate,
+                OnRosterUpdate,
+                OnUpdateBlocklist,
+                OnKeyStatusUpdated,
+                OnMediaLoaded {
     public static final String ACTION_VIEW_CONTACT = "view_contact";
     private final int REQUEST_SYNC_CONTACTS = 0x28cf;
     ActivityContactDetailsBinding binding;
@@ -108,40 +116,55 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
     protected MenuItem save = null;
 
     private Contact contact;
-    private final DialogInterface.OnClickListener removeFromRoster = new DialogInterface.OnClickListener() {
-
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-            xmppConnectionService.deleteContactOnServer(contact);
-        }
-    };
-    private final OnCheckedChangeListener mOnSendCheckedChange = new OnCheckedChangeListener() {
+    private final DialogInterface.OnClickListener removeFromRoster =
+            new DialogInterface.OnClickListener() {
 
-        @Override
-        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-            if (isChecked) {
-                if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
-                    xmppConnectionService.stopPresenceUpdatesTo(contact);
-                } else {
-                    contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
+                @Override
+                public void onClick(DialogInterface dialog, int which) {
+                    xmppConnectionService.deleteContactOnServer(contact);
                 }
-            } else {
-                contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
-                xmppConnectionService.sendPresencePacket(contact.getAccount(), xmppConnectionService.getPresenceGenerator().stopPresenceUpdatesTo(contact));
-            }
-        }
-    };
-    private final OnCheckedChangeListener mOnReceiveCheckedChange = new OnCheckedChangeListener() {
-
-        @Override
-        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-            if (isChecked) {
-                xmppConnectionService.sendPresencePacket(contact.getAccount(), xmppConnectionService.getPresenceGenerator().requestPresenceUpdatesFrom(contact));
-            } else {
-                xmppConnectionService.sendPresencePacket(contact.getAccount(), xmppConnectionService.getPresenceGenerator().stopPresenceUpdatesFrom(contact));
-            }
-        }
-    };
+            };
+    private final OnCheckedChangeListener mOnSendCheckedChange =
+            new OnCheckedChangeListener() {
+
+                @Override
+                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                    if (isChecked) {
+                        if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
+                            xmppConnectionService.stopPresenceUpdatesTo(contact);
+                        } else {
+                            contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
+                        }
+                    } else {
+                        contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
+                        xmppConnectionService.sendPresencePacket(
+                                contact.getAccount(),
+                                xmppConnectionService
+                                        .getPresenceGenerator()
+                                        .stopPresenceUpdatesTo(contact));
+                    }
+                }
+            };
+    private final OnCheckedChangeListener mOnReceiveCheckedChange =
+            new OnCheckedChangeListener() {
+
+                @Override
+                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                    if (isChecked) {
+                        xmppConnectionService.sendPresencePacket(
+                                contact.getAccount(),
+                                xmppConnectionService
+                                        .getPresenceGenerator()
+                                        .requestPresenceUpdatesFrom(contact));
+                    } else {
+                        xmppConnectionService.sendPresencePacket(
+                                contact.getAccount(),
+                                xmppConnectionService
+                                        .getPresenceGenerator()
+                                        .stopPresenceUpdatesFrom(contact));
+                    }
+                }
+            };
     private Jid accountJid;
     private Jid contactJid;
     private boolean showDynamicTags = false;
@@ -152,14 +175,18 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
     private void checkContactPermissionAndShowAddDialog() {
         if (hasContactsPermission()) {
             showAddToPhoneBookDialog();
-        } else if (QuickConversationsService.isContactListIntegration(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-            requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
+        } else if (QuickConversationsService.isContactListIntegration(this)
+                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            requestPermissions(
+                    new String[] {Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
         }
     }
 
     private boolean hasContactsPermission() {
-        if (QuickConversationsService.isContactListIntegration(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-            return checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED;
+        if (QuickConversationsService.isContactListIntegration(this)
+                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            return checkSelfPermission(Manifest.permission.READ_CONTACTS)
+                    == PackageManager.PERMISSION_GRANTED;
         } else {
             return true;
         }
@@ -167,9 +194,10 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
 
     private void showAddToPhoneBookDialog() {
         final Jid jid = contact.getJid();
-        final boolean quicksyContact = AbstractQuickConversationsService.isQuicksy()
-                && Config.QUICKSY_DOMAIN.equals(jid.getDomain())
-                && jid.getLocal() != null;
+        final boolean quicksyContact =
+                AbstractQuickConversationsService.isQuicksy()
+                        && Config.QUICKSY_DOMAIN.equals(jid.getDomain())
+                        && jid.getLocal() != null;
         final String value;
         if (quicksyContact) {
             value = PhoneNumberUtilWrapper.toFormattedPhoneNumber(this, jid);
@@ -180,24 +208,33 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
         builder.setTitle(getString(R.string.action_add_phone_book));
         builder.setMessage(getString(R.string.add_phone_book_text, value));
         builder.setNegativeButton(getString(R.string.cancel), null);
-        builder.setPositiveButton(getString(R.string.add), (dialog, which) -> {
-            final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
-            intent.setType(Contacts.CONTENT_ITEM_TYPE);
-            if (quicksyContact) {
-                intent.putExtra(Intents.Insert.PHONE, value);
-            } else {
-                intent.putExtra(Intents.Insert.IM_HANDLE, value);
-                intent.putExtra(Intents.Insert.IM_PROTOCOL, CommonDataKinds.Im.PROTOCOL_JABBER);
-                //TODO for modern use we want PROTOCOL_CUSTOM and an extra field with a value of 'XMPP'
-                // however we don’t have such a field and thus have to use the legacy PROTOCOL_JABBER
-            }
-            intent.putExtra("finishActivityOnSaveCompleted", true);
-            try {
-                startActivityForResult(intent, 0);
-            } catch (ActivityNotFoundException e) {
-                Toast.makeText(ContactDetailsActivity.this, R.string.no_application_found_to_view_contact, Toast.LENGTH_SHORT).show();
-            }
-        });
+        builder.setPositiveButton(
+                getString(R.string.add),
+                (dialog, which) -> {
+                    final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+                    intent.setType(Contacts.CONTENT_ITEM_TYPE);
+                    if (quicksyContact) {
+                        intent.putExtra(Intents.Insert.PHONE, value);
+                    } else {
+                        intent.putExtra(Intents.Insert.IM_HANDLE, value);
+                        intent.putExtra(
+                                Intents.Insert.IM_PROTOCOL, CommonDataKinds.Im.PROTOCOL_JABBER);
+                        // TODO for modern use we want PROTOCOL_CUSTOM and an extra field with a
+                        // value of 'XMPP'
+                        // however we don’t have such a field and thus have to use the legacy
+                        // PROTOCOL_JABBER
+                    }
+                    intent.putExtra("finishActivityOnSaveCompleted", true);
+                    try {
+                        startActivityForResult(intent, 0);
+                    } catch (ActivityNotFoundException e) {
+                        Toast.makeText(
+                                        ContactDetailsActivity.this,
+                                        R.string.no_application_found_to_view_contact,
+                                        Toast.LENGTH_SHORT)
+                                .show();
+                    }
+                });
         builder.create().show();
     }
 
@@ -224,7 +261,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
     @Override
     protected String getShareableUri(boolean http) {
         if (http) {
-            return "https://conversations.im/i/" + XmppUri.lameUrlEncode(contact.getJid().asBareJid().toEscapedString());
+            return "https://conversations.im/i/"
+                    + XmppUri.lameUrlEncode(contact.getJid().asBareJid().toEscapedString());
         } else {
             return "xmpp:" + Uri.encode(contact.getJid().asBareJid().toEscapedString(), "@/+");
         }
@@ -233,7 +271,9 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
     @Override
     protected void onCreate(final Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        showInactiveOmemo = savedInstanceState != null && savedInstanceState.getBoolean("show_inactive_omemo", false);
+        showInactiveOmemo =
+                savedInstanceState != null
+                        && savedInstanceState.getBoolean("show_inactive_omemo", false);
         if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) {
             try {
                 this.accountJid = Jid.ofEscaped(getIntent().getExtras().getString(EXTRA_ACCOUNT));
@@ -250,10 +290,11 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
 
         setSupportActionBar(binding.toolbar);
         configureActionBar(getSupportActionBar());
-        binding.showInactiveDevices.setOnClickListener(v -> {
-            showInactiveOmemo = !showInactiveOmemo;
-            populateView();
-        });
+        binding.showInactiveDevices.setOnClickListener(
+                v -> {
+                    showInactiveOmemo = !showInactiveOmemo;
+                    populateView();
+                });
         binding.addContactButton.setOnClickListener(v -> showAddToRosterDialog(contact));
 
         mMediaAdapter = new MediaAdapter(this, R.dimen.media_size);
@@ -273,12 +314,15 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
         final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
         this.showDynamicTags = preferences.getBoolean(AppSettings.SHOW_DYNAMIC_TAGS, false);
         this.showLastSeen = preferences.getBoolean("last_activity", false);
-        binding.mediaWrapper.setVisibility(Compatibility.hasStoragePermission(this) ? View.VISIBLE : View.GONE);
+        binding.mediaWrapper.setVisibility(
+                Compatibility.hasStoragePermission(this) ? View.VISIBLE : View.GONE);
         mMediaAdapter.setAttachments(Collections.emptyList());
     }
 
     @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+    public void onRequestPermissionsResult(
+            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        // TODO check for Camera / Scan permission
         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
         if (grantResults.length > 0)
             if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
@@ -322,15 +366,20 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
                 final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
                 builder.setNegativeButton(getString(R.string.cancel), null);
                 builder.setTitle(getString(R.string.action_delete_contact))
-                        .setMessage(JidDialog.style(this, R.string.remove_contact_text, contact.getJid().toEscapedString()))
-                        .setPositiveButton(getString(R.string.delete),
-                                removeFromRoster).create().show();
+                        .setMessage(
+                                JidDialog.style(
+                                        this,
+                                        R.string.remove_contact_text,
+                                        contact.getJid().toEscapedString()))
+                        .setPositiveButton(getString(R.string.delete), removeFromRoster)
+                        .create()
+                        .show();
                 break;
             case R.id.action_save:
                 saveEdits();
                 break;
             case R.id.action_edit_contact:
-                Uri systemAccount = contact.getSystemAccount();
+                final Uri systemAccount = contact.getSystemAccount();
                 if (systemAccount == null) {
                     menuItem.expandActionView();
                     EditText text = menuItem.getActionView().findViewById(R.id.search_field);
@@ -382,31 +431,43 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
                     try {
                         startActivity(intent);
                     } catch (ActivityNotFoundException e) {
-                        Toast.makeText(ContactDetailsActivity.this, R.string.no_application_found_to_view_contact, Toast.LENGTH_SHORT).show();
+                        Toast.makeText(
+                                        ContactDetailsActivity.this,
+                                        R.string.no_application_found_to_view_contact,
+                                        Toast.LENGTH_SHORT)
+                                .show();
                     }
-
                 }
                 break;
-            case R.id.action_block:
+            case R.id.action_block, R.id.action_unblock:
                 BlockContactDialog.show(this, contact);
                 break;
-            case R.id.action_unblock:
-                BlockContactDialog.show(this, contact);
+            case R.id.action_custom_notifications:
+                configureCustomNotifications(contact);
                 break;
         }
         return super.onOptionsItemSelected(menuItem);
     }
 
+    private void configureCustomNotifications(final Contact contact) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        final var shortcut = xmppConnectionService.getShortcutService().getShortcutInfo(contact);
+        configureCustomNotification(shortcut);
+    }
+
     @Override
     public boolean onCreateOptionsMenu(final Menu menu) {
         getMenuInflater().inflate(R.menu.contact_details, menu);
         AccountUtils.showHideMenuItems(menu);
-        edit = menu.findItem(R.id.action_edit_contact);
-        save = menu.findItem(R.id.action_save);
-        MenuItem block = menu.findItem(R.id.action_block);
-        MenuItem unblock = menu.findItem(R.id.action_unblock);
-        MenuItem edit = menu.findItem(R.id.action_edit_contact);
-        MenuItem delete = menu.findItem(R.id.action_delete_contact);
+        final MenuItem block = menu.findItem(R.id.action_block);
+        final MenuItem unblock = menu.findItem(R.id.action_unblock);
+        final MenuItem edit = menu.findItem(R.id.action_edit_contact);
+        final MenuItem save = menu.findItem(R.id.action_save);
+        final MenuItem delete = menu.findItem(R.id.action_delete_contact);
+        final MenuItem customNotifications = menu.findItem(R.id.action_custom_notifications);
+        customNotifications.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
         if (contact == null) {
             return true;
         }
@@ -463,7 +524,11 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
                 binding.statusMessage.setVisibility(View.VISIBLE);
                 final Spannable span = new SpannableString(message);
                 if (Emoticons.isOnlyEmoji(message)) {
-                    span.setSpan(new RelativeSizeSpan(2.0f), 0, message.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    span.setSpan(
+                            new RelativeSizeSpan(2.0f),
+                            0,
+                            message.length(),
+                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                 }
                 binding.statusMessage.setText(span);
             } else {
@@ -487,14 +552,16 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
                 binding.detailsSendPresence.setText(R.string.send_presence_updates);
             } else {
                 binding.detailsSendPresence.setText(R.string.preemptively_grant);
-                binding.detailsSendPresence.setChecked(contact.getOption(Contact.Options.PREEMPTIVE_GRANT));
+                binding.detailsSendPresence.setChecked(
+                        contact.getOption(Contact.Options.PREEMPTIVE_GRANT));
             }
             if (contact.getOption(Contact.Options.TO)) {
                 binding.detailsReceivePresence.setText(R.string.receive_presence_updates);
                 binding.detailsReceivePresence.setChecked(true);
             } else {
                 binding.detailsReceivePresence.setText(R.string.ask_for_presence_updates);
-                binding.detailsReceivePresence.setChecked(contact.getOption(Contact.Options.ASKING));
+                binding.detailsReceivePresence.setChecked(
+                        contact.getOption(Contact.Options.ASKING));
             }
             if (contact.getAccount().isOnlineAndConnected()) {
                 binding.detailsReceivePresence.setEnabled(true);
@@ -520,7 +587,11 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
                     && contact.getLastseen() > 0
                     && contact.getPresences().allOrNonSupport(Namespace.IDLE)) {
                 binding.detailsLastseen.setVisibility(View.VISIBLE);
-                binding.detailsLastseen.setText(UIHelper.lastseen(getApplicationContext(), contact.isActive(), contact.getLastseen()));
+                binding.detailsLastseen.setText(
+                        UIHelper.lastseen(
+                                getApplicationContext(),
+                                contact.isActive(),
+                                contact.getLastseen()));
             } else {
                 binding.detailsLastseen.setVisibility(View.GONE);
             }
@@ -529,7 +600,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
         binding.detailsContactjid.setText(IrregularUnicodeDetector.style(this, contact.getJid()));
         final String account = contact.getAccount().getJid().asBareJid().toEscapedString();
         binding.detailsAccount.setText(getString(R.string.using_account, account));
-        AvatarWorkerTask.loadAvatar(contact, binding.detailsContactBadge, R.dimen.avatar_on_details_screen_size);
+        AvatarWorkerTask.loadAvatar(
+                contact, binding.detailsContactBadge, R.dimen.avatar_on_details_screen_size);
         binding.detailsContactBadge.setOnClickListener(this::onBadgeClick);
 
         binding.detailsContactKeys.removeAllViews();
@@ -537,7 +609,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
         final LayoutInflater inflater = getLayoutInflater();
         final AxolotlService axolotlService = contact.getAccount().getAxolotlService();
         if (Config.supportOmemo() && axolotlService != null) {
-            final Collection<XmppAxolotlSession> sessions = axolotlService.findSessionsForContact(contact);
+            final Collection<XmppAxolotlSession> sessions =
+                    axolotlService.findSessionsForContact(contact);
             boolean anyActive = false;
             for (XmppAxolotlSession session : sessions) {
                 anyActive = session.getTrust().isActive();
@@ -567,9 +640,13 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
                     showUnverifiedWarning = true;
                 }
             }
-            binding.unverifiedWarning.setVisibility(showUnverifiedWarning ? View.VISIBLE : View.GONE);
+            binding.unverifiedWarning.setVisibility(
+                    showUnverifiedWarning ? View.VISIBLE : View.GONE);
             if (showsInactive || skippedInactive) {
-                binding.showInactiveDevices.setText(showsInactive ? R.string.hide_inactive_devices : R.string.show_inactive_devices);
+                binding.showInactiveDevices.setText(
+                        showsInactive
+                                ? R.string.hide_inactive_devices
+                                : R.string.show_inactive_devices);
                 binding.showInactiveDevices.setVisibility(View.VISIBLE);
             } else {
                 binding.showInactiveDevices.setVisibility(View.GONE);
@@ -578,7 +655,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
             binding.showInactiveDevices.setVisibility(View.GONE);
         }
         final boolean isCameraFeatureAvailable = isCameraFeatureAvailable();
-        binding.scanButton.setVisibility(hasKeys && isCameraFeatureAvailable ? View.VISIBLE : View.GONE);
+        binding.scanButton.setVisibility(
+                hasKeys && isCameraFeatureAvailable ? View.VISIBLE : View.GONE);
         if (hasKeys) {
             binding.scanButton.setOnClickListener((v) -> ScanActivity.scan(this));
         }
@@ -589,7 +667,9 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
             TextView keyType = view.findViewById(R.id.key_type);
             keyType.setText(R.string.openpgp_key_id);
             if ("pgp".equals(messageFingerprint)) {
-                keyType.setTextColor(MaterialColors.getColor(keyType, com.google.android.material.R.attr.colorPrimaryVariant));
+                keyType.setTextColor(
+                        MaterialColors.getColor(
+                                keyType, com.google.android.material.R.attr.colorPrimaryVariant));
             }
             key.setText(OpenPgpUtils.convertKeyIdToHex(contact.getPgpKeyId()));
             final OnClickListener openKey = v -> launchOpenKeyChain(contact.getPgpKeyId());
@@ -601,7 +681,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
         binding.keysWrapper.setVisibility(hasKeys ? View.VISIBLE : View.GONE);
 
         final List<ListItem.Tag> tagList = contact.getTags(this);
-        final boolean hasMetaTags = contact.isBlocked() || contact.getShownStatus() != Presence.Status.OFFLINE;
+        final boolean hasMetaTags =
+                contact.isBlocked() || contact.getShownStatus() != Presence.Status.OFFLINE;
         if ((tagList.isEmpty() && !hasMetaTags) || !this.showDynamicTags) {
             binding.tags.setVisibility(View.GONE);
         } else {
@@ -610,9 +691,13 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
             final ImmutableList.Builder<Integer> viewIdBuilder = new ImmutableList.Builder<>();
             for (final ListItem.Tag tag : tagList) {
                 final String name = tag.getName();
-                final TextView tv = (TextView) inflater.inflate(R.layout.item_tag, binding.tags, false);
+                final TextView tv =
+                        (TextView) inflater.inflate(R.layout.item_tag, binding.tags, false);
                 tv.setText(name);
-                tv.setBackgroundTintList(ColorStateList.valueOf(MaterialColors.harmonizeWithPrimary(this,XEP0392Helper.rgbFromNick(name))));
+                tv.setBackgroundTintList(
+                        ColorStateList.valueOf(
+                                MaterialColors.harmonizeWithPrimary(
+                                        this, XEP0392Helper.rgbFromNick(name))));
                 final int id = ViewCompat.generateViewId();
                 tv.setId(id);
                 viewIdBuilder.add(id);
@@ -620,11 +705,14 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
             }
             if (contact.isBlocked()) {
                 final TextView tv =
-                        (TextView)
-                                inflater.inflate(
-                                        R.layout.item_tag, binding.tags, false);
+                        (TextView) inflater.inflate(R.layout.item_tag, binding.tags, false);
                 tv.setText(R.string.blocked);
-                tv.setBackgroundTintList(ColorStateList.valueOf(MaterialColors.harmonizeWithPrimary(tv.getContext(), ContextCompat.getColor(tv.getContext(),R.color.gray_800))));
+                tv.setBackgroundTintList(
+                        ColorStateList.valueOf(
+                                MaterialColors.harmonizeWithPrimary(
+                                        tv.getContext(),
+                                        ContextCompat.getColor(
+                                                tv.getContext(), R.color.gray_800))));
                 final int id = ViewCompat.generateViewId();
                 tv.setId(id);
                 viewIdBuilder.add(id);
@@ -633,9 +721,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
                 final Presence.Status status = contact.getShownStatus();
                 if (status != Presence.Status.OFFLINE) {
                     final TextView tv =
-                            (TextView)
-                                    inflater.inflate(
-                                            R.layout.item_tag, binding.tags, false);
+                            (TextView) inflater.inflate(R.layout.item_tag, binding.tags, false);
                     UIHelper.setStatus(tv, status);
                     final int id = ViewCompat.generateViewId();
                     tv.setId(id);
@@ -707,8 +793,10 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
 
             if (Compatibility.hasStoragePermission(this)) {
                 final int limit = GridManager.getCurrentColumnCount(this.binding.media);
-                xmppConnectionService.getAttachments(account, contact.getJid().asBareJid(), limit, this);
-                this.binding.showMedia.setOnClickListener((v) -> MediaBrowserActivity.launch(this, contact));
+                xmppConnectionService.getAttachments(
+                        account, contact.getJid().asBareJid(), limit, this);
+                this.binding.showMedia.setOnClickListener(
+                        (v) -> MediaBrowserActivity.launch(this, contact));
             }
 
             final VcardAdapter items = new VcardAdapter();
@@ -763,7 +851,9 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
 
     @Override
     protected void processFingerprintVerification(XmppUri uri) {
-        if (contact != null && contact.getJid().asBareJid().equals(uri.getJid()) && uri.hasFingerprints()) {
+        if (contact != null
+                && contact.getJid().asBareJid().equals(uri.getJid())
+                && uri.hasFingerprints()) {
             if (xmppConnectionService.verifyFingerprints(contact, uri.getFingerprints())) {
                 Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
             }
@@ -774,12 +864,14 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
 
     @Override
     public void onMediaLoaded(List<Attachment> attachments) {
-        runOnUiThread(() -> {
-            int limit = GridManager.getCurrentColumnCount(binding.media);
-            mMediaAdapter.setAttachments(attachments.subList(0, Math.min(limit, attachments.size())));
-            binding.mediaWrapper.setVisibility(attachments.size() > 0 ? View.VISIBLE : View.GONE);
-        });
-
+        runOnUiThread(
+                () -> {
+                    int limit = GridManager.getCurrentColumnCount(binding.media);
+                    mMediaAdapter.setAttachments(
+                            attachments.subList(0, Math.min(limit, attachments.size())));
+                    binding.mediaWrapper.setVisibility(
+                            attachments.size() > 0 ? View.VISIBLE : View.GONE);
+                });
     }
 
     class VcardAdapter extends ArrayAdapter<Element> {
  
  
  
    
    @@ -200,23 +200,12 @@ import eu.siacs.conversations.xmpp.jingle.JingleFileTransferConnection;
 import eu.siacs.conversations.xmpp.jingle.Media;
 import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
 import eu.siacs.conversations.xmpp.jingle.RtpCapability;
+import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
 
 import im.conversations.android.xmpp.model.stanza.Iq;
 
 import org.jetbrains.annotations.NotNull;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicBoolean;
-import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
-
 public class ConversationFragment extends XmppFragment
         implements EditMessage.KeyboardListener,
                 MessageAdapter.OnContactPictureLongClicked,
@@ -407,7 +396,10 @@ public class ConversationFragment extends XmppFragment
                                                             } catch (IllegalStateException e) {
                                                                 Log.d(
                                                                         Config.LOGTAG,
-                                                                        "caught illegal state exception while updating status messages");
+                                                                        "caught illegal state"
+                                                                                + " exception while"
+                                                                                + " updating status"
+                                                                                + " messages");
                                                             }
                                                             messageListAdapter
                                                                     .notifyDataSetChanged();
@@ -784,14 +776,6 @@ public class ConversationFragment extends XmppFragment
         for (int i = 0; i < messages.size(); ++i) {
             if (uuid.equals(messages.get(i).getUuid())) {
                 return i;
-            } else {
-                Message next = messages.get(i);
-                while (next != null && next.wasMergedIntoPrevious(activity == null ? null : activity.xmppConnectionService)) {
-                    if (uuid.equals(next.getUuid())) {
-                        return i;
-                    }
-                    next = next.next();
-                }
             }
         }
         return -1;
@@ -1425,13 +1409,12 @@ public class ConversationFragment extends XmppFragment
                     menuCall.setVisible(false);
                 } else {
                     menuOngoingCall.setVisible(false);
-                    final RtpCapability.Capability rtpCapability =
-                            RtpCapability.check(conversation.getContact());
+                    // use RtpCapability.check(conversation.getContact()); to check if contact
+                    // actually has support
                     final boolean cameraAvailable =
                             activity != null && activity.isCameraFeatureAvailable();
-                    menuCall.setVisible(rtpCapability != RtpCapability.Capability.NONE);
-                    menuVideoCall.setVisible(
-                            rtpCapability == RtpCapability.Capability.VIDEO && cameraAvailable);
+                    menuCall.setVisible(true);
+                    menuVideoCall.setVisible(cameraAvailable);
                 }
                 menuContactDetails.setVisible(!this.conversation.withSelf());
                 menuMucDetails.setVisible(false);
@@ -1846,13 +1829,9 @@ public class ConversationFragment extends XmppFragment
         }
     }
 
-    private void populateContextMenu(ContextMenu menu) {
+    private void populateContextMenu(final ContextMenu menu) {
         final Message m = this.selectedMessage;
         final Transferable t = m.getTransferable();
-        Message relevantForCorrection = m;
-        while (relevantForCorrection.mergeable(relevantForCorrection.next())) {
-            relevantForCorrection = relevantForCorrection.next();
-        }
         if (m.getType() != Message.TYPE_STATUS && m.getType() != Message.TYPE_RTP_SESSION) {
 
             if (m.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE
@@ -1889,6 +1868,7 @@ public class ConversationFragment extends XmppFragment
             MenuItem shareWith = menu.findItem(R.id.share_with);
             MenuItem sendAgain = menu.findItem(R.id.send_again);
             MenuItem copyUrl = menu.findItem(R.id.copy_url);
+            MenuItem copyLink = menu.findItem(R.id.copy_link);
             MenuItem saveAsSticker = menu.findItem(R.id.save_as_sticker);
             MenuItem downloadFile = menu.findItem(R.id.download_file);
             MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission);
@@ -1902,7 +1882,8 @@ public class ConversationFragment extends XmppFragment
                             && m.getErrorMessage() != null
                             && !Message.ERROR_MESSAGE_CANCELLED.equals(m.getErrorMessage());
             final Conversational conversational = m.getConversation();
-            if (m.getStatus() == Message.STATUS_RECEIVED && conversational instanceof Conversation c) {
+            if (m.getStatus() == Message.STATUS_RECEIVED
+                    && conversational instanceof Conversation c) {
                 final XmppConnection connection = c.getAccount().getXmppConnection();
                 if (c.isWithStranger()
                         && m.getServerMsgId() != null
@@ -1912,8 +1893,16 @@ public class ConversationFragment extends XmppFragment
                     reportAndBlock.setVisible(true);
                 }
             }
-            if (!encrypted && !m.isPrivateMessage()) {
-                addReaction.setVisible(!showError && !m.isDeleted());
+            if (conversational instanceof Conversation c) {
+                addReaction.setVisible(
+                        !showError
+                                && !m.isDeleted()
+                                && !m.isPrivateMessage()
+                                && (c.getMode() == Conversational.MODE_SINGLE
+                                        || (c.getMucOptions().occupantId()
+                                                && c.getMucOptions().participating())));
+            } else {
+                addReaction.setVisible(false);
             }
             if (!m.isFileOrImage()
                     && !encrypted
@@ -1922,20 +1911,29 @@ public class ConversationFragment extends XmppFragment
                     && !unInitiatedButKnownSize
                     && t == null) {
                 copyMessage.setVisible(true);
+                quoteMessage.setVisible(!showError && !MessageUtils.prepareQuote(m).isEmpty());
+                final String scheme =
+                        ShareUtil.getLinkScheme(new SpannableStringBuilder(m.getBody()));
+                if ("xmpp".equals(scheme)) {
+                    copyLink.setTitle(R.string.copy_jabber_id);
+                    copyLink.setVisible(true);
+                } else if (scheme != null) {
+                    copyLink.setVisible(true);
+                }
             }
             quoteMessage.setVisible(!encrypted && !showError);
             if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED && !deleted) {
                 retryDecryption.setVisible(true);
             }
             if (!showError
-                    && relevantForCorrection.getType() == Message.TYPE_TEXT
-                    && relevantForCorrection.isEditable()
+                    && m.getType() == Message.TYPE_TEXT
+                    && m.isEditable()
                     && !m.isGeoUri()
                     && m.getConversation() instanceof Conversation) {
                 correctMessage.setVisible(true);
-                if (!relevantForCorrection.getBody().equals("") && !relevantForCorrection.getBody().equals(" ")) retractMessage.setVisible(true);
+                if (!m.getBody().equals("") && !m.getBody().equals(" ")) retractMessage.setVisible(true);
             }
-            if (relevantForCorrection.getStatus() == Message.STATUS_WAITING) {
+            if (m.getStatus() == Message.STATUS_WAITING) {
                 correctMessage.setVisible(true);
                 retractMessage.setVisible(true);
             }
@@ -2017,10 +2015,7 @@ public class ConversationFragment extends XmppFragment
                     .setTitle(R.string.retract_message)
                     .setMessage("Do you really want to retract this message?")
                     .setPositiveButton(R.string.yes, (dialog, whichButton) -> {
-                        Message message = selectedMessage;
-                        while (message.mergeable(message.next())) {
-                            message = message.next();
-                        }
+                        final var message = selectedMessage;
                         if (message.getStatus() == Message.STATUS_WAITING || message.getStatus() == Message.STATUS_OFFERED) {
                             activity.xmppConnectionService.deleteMessage(message);
                             return;
@@ -2054,11 +2049,7 @@ public class ConversationFragment extends XmppFragment
                 return true;
             case R.id.moderate_message:
                 activity.quickEdit("Spam", (reason) -> {
-                    Message message = selectedMessage;
-                    do {
-                        activity.xmppConnectionService.moderateMessage(conversation.getAccount(), message, reason);
-                        message = message.mergeable(message.next()) ? message.next() : null;
-                    } while (message != null);
+                    activity.xmppConnectionService.moderateMessage(conversation.getAccount(), selectedMessage, reason);
                     return null;
                 }, R.string.moderate_reason, false, false, true, true);
                 return true;
@@ -2350,9 +2341,9 @@ public class ConversationFragment extends XmppFragment
     private void addShortcut() {
         ShortcutInfoCompat info;
         if (conversation.getMode() == Conversation.MODE_MULTI) {
-            info = activity.xmppConnectionService.getShortcutService().getShortcutInfoCompat(conversation.getMucOptions());
+            info = activity.xmppConnectionService.getShortcutService().getShortcutInfo(conversation.getMucOptions());
         } else {
-            info = activity.xmppConnectionService.getShortcutService().getShortcutInfoCompat(conversation.getContact());
+            info = activity.xmppConnectionService.getShortcutService().getShortcutInfo(conversation.getContact());
         }
         ShortcutManagerCompat.requestPinShortcut(activity, info, null);
     }
@@ -2436,7 +2427,11 @@ public class ConversationFragment extends XmppFragment
     }
 
     private void triggerRtpSession(final Account account, final Jid with, final String action) {
-        CallIntegrationConnectionService.placeCall(activity.xmppConnectionService, account,with,RtpSessionActivity.actionToMedia(action));
+        CallIntegrationConnectionService.placeCall(
+                activity.xmppConnectionService,
+                account,
+                with,
+                RtpSessionActivity.actionToMedia(action));
     }
 
     private void handleAttachmentSelection(MenuItem item) {
@@ -2512,10 +2507,14 @@ public class ConversationFragment extends XmppFragment
     }
 
     public void attachFile(final int attachmentChoice) {
-        attachFile(attachmentChoice, true);
+        attachFile(attachmentChoice, true, false);
     }
 
     public void attachFile(final int attachmentChoice, final boolean updateRecentlyUsed) {
+        attachFile(attachmentChoice, updateRecentlyUsed, false);
+    }
+
+    public void attachFile(final int attachmentChoice, final boolean updateRecentlyUsed, final boolean fromPermissions) {
         if (attachmentChoice == ATTACHMENT_CHOICE_RECORD_VOICE) {
             if (!hasPermissions(
                     attachmentChoice,
@@ -2524,7 +2523,8 @@ public class ConversationFragment extends XmppFragment
                 return;
             }
         } else if (attachmentChoice == ATTACHMENT_CHOICE_TAKE_PHOTO
-                || attachmentChoice == ATTACHMENT_CHOICE_RECORD_VIDEO) {
+                || attachmentChoice == ATTACHMENT_CHOICE_RECORD_VIDEO
+                || (attachmentChoice == ATTACHMENT_CHOICE_CHOOSE_IMAGE && !fromPermissions)) {
             if (!hasPermissions(
                     attachmentChoice,
                     Manifest.permission.WRITE_EXTERNAL_STORAGE,
@@ -2615,7 +2615,7 @@ public class ConversationFragment extends XmppFragment
         final PermissionUtils.PermissionResult permissionResult =
                 PermissionUtils.removeBluetoothConnect(permissions, grantResults);
         if (grantResults.length > 0) {
-            if (allGranted(permissionResult.grantResults)) {
+            if (allGranted(permissionResult.grantResults) || requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
                 switch (requestCode) {
                     case REQUEST_START_DOWNLOAD:
                         if (this.mPendingDownloadableMessage != null) {
@@ -2637,7 +2637,7 @@ public class ConversationFragment extends XmppFragment
                         triggerRtpSession(RtpSessionActivity.ACTION_MAKE_VIDEO_CALL);
                         break;
                     default:
-                        attachFile(requestCode);
+                        attachFile(requestCode, true, true);
                         break;
                 }
             } else {
@@ -2720,7 +2720,8 @@ public class ConversationFragment extends XmppFragment
 
     @SuppressLint("InflateParams")
     protected void clearHistoryDialog(final Conversation conversation) {
-        final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
+        final MaterialAlertDialogBuilder builder =
+                new MaterialAlertDialogBuilder(requireActivity());
         builder.setTitle(R.string.clear_conversation_history);
         final View dialogView =
                 requireActivity().getLayoutInflater().inflate(R.layout.dialog_clear_history, null);
@@ -2744,7 +2745,8 @@ public class ConversationFragment extends XmppFragment
     }
 
     protected void muteConversationDialog(final Conversation conversation) {
-        final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
+        final MaterialAlertDialogBuilder builder =
+                new MaterialAlertDialogBuilder(requireActivity());
         builder.setTitle(R.string.disable_notifications);
         final int[] durations = activity.getResources().getIntArray(R.array.mute_options_durations);
         final CharSequence[] labels = new CharSequence[durations.length];
@@ -2776,7 +2778,9 @@ public class ConversationFragment extends XmppFragment
     private boolean hasPermissions(int requestCode, List<String> permissions) {
         final List<String> missingPermissions = new ArrayList<>();
         for (String permission : permissions) {
-            if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU || Config.ONLY_INTERNAL_STORAGE) && permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+            if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+                            || Config.ONLY_INTERNAL_STORAGE)
+                    && permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                 continue;
             }
             if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
@@ -2786,9 +2790,7 @@ public class ConversationFragment extends XmppFragment
         if (missingPermissions.size() == 0) {
             return true;
         } else {
-            requestPermissions(
-                    missingPermissions.toArray(new String[0]),
-                    requestCode);
+            requestPermissions(missingPermissions.toArray(new String[0]), requestCode);
             return false;
         }
     }
@@ -2826,7 +2828,9 @@ public class ConversationFragment extends XmppFragment
                 intent.setType("*/*");
                 intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"image/*", "video/*"});
                 intent = Intent.createChooser(intent, getString(R.string.perform_action_with));
-                intent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { takePhotoIntent, takeVideoIntent });
+                if (activity.checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
+                    intent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { takePhotoIntent, takeVideoIntent });
+                }
                 break;
             case ATTACHMENT_CHOICE_RECORD_VIDEO:
                 intent = takeVideoIntent;
@@ -2932,9 +2936,6 @@ public class ConversationFragment extends XmppFragment
                     }
                 }
                 if (message != null) {
-                    while (message.next() != null && message.next().wasMergedIntoPrevious(activity == null ? null : activity.xmppConnectionService)) {
-                        message = message.next();
-                    }
                     return message;
                 }
             }
@@ -2968,7 +2969,15 @@ public class ConversationFragment extends XmppFragment
     }
 
     private void addReaction(final Message message) {
-        activity.addReaction(message, reactions -> activity.xmppConnectionService.sendReactions(message, reactions));
+        activity.addReaction(
+                message,
+                reactions -> {
+                    if (activity.xmppConnectionService.sendReactions(message, reactions)) {
+                        return;
+                    }
+                    Toast.makeText(activity, R.string.could_not_add_reaction, Toast.LENGTH_LONG)
+                            .show();
+                });
     }
 
     private void reportMessage(final Message message) {
@@ -2976,7 +2985,8 @@ public class ConversationFragment extends XmppFragment
     }
 
     private void showErrorMessage(final Message message) {
-        final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
+        final MaterialAlertDialogBuilder builder =
+                new MaterialAlertDialogBuilder(requireActivity());
         builder.setTitle(R.string.error_message);
         final String errorMessage = message.getErrorMessage();
         final String[] errorMessageParts =
@@ -3047,7 +3057,8 @@ public class ConversationFragment extends XmppFragment
     }
 
     private void deleteFile(final Message message) {
-        final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
+        final MaterialAlertDialogBuilder builder =
+                new MaterialAlertDialogBuilder(requireActivity());
         builder.setNegativeButton(R.string.cancel, null);
         builder.setTitle(R.string.delete_file_dialog);
         builder.setMessage(R.string.delete_file_dialog_msg);
@@ -3157,10 +3168,7 @@ public class ConversationFragment extends XmppFragment
         updateEditablity();
     }
 
-    private void correctMessage(Message message) {
-        while (message.mergeable(message.next())) {
-            message = message.next();
-        }
+    private void correctMessage(final Message message) {
         setThread(message.getThread());
         conversation.setUserSelectedThread(true);
         this.conversation.setCorrectingMessage(message);
@@ -3281,7 +3289,8 @@ public class ConversationFragment extends XmppFragment
             final String uuid = pendingConversationsUuid.pop();
             Log.d(
                     Config.LOGTAG,
-                    "ConversationFragment.onStart() - activity was bound but no conversation loaded. uuid="
+                    "ConversationFragment.onStart() - activity was bound but no conversation"
+                            + " loaded. uuid="
                             + uuid);
             if (uuid != null) {
                 findAndReInitByUuidOrArchive(uuid);
@@ -3824,7 +3833,10 @@ public class ConversationFragment extends XmppFragment
                     R.string.enable,
                     this.mEnableAccountListener);
         } else if (account.getStatus() == Account.State.LOGGED_OUT) {
-            showSnackbar(R.string.this_account_is_logged_out,R.string.log_in,this.mEnableAccountListener);
+            showSnackbar(
+                    R.string.this_account_is_logged_out,
+                    R.string.log_in,
+                    this.mEnableAccountListener);
         } else if (conversation.isBlocked()) {
             showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener);
         } else if (account.getStatus() == Account.State.CONNECTING) {
@@ -3890,7 +3902,8 @@ public class ConversationFragment extends XmppFragment
                     showSnackbar(R.string.conference_kicked, R.string.join, joinMuc);
                     break;
                 case TECHNICAL_PROBLEMS:
-                    showSnackbar(R.string.conference_technical_problems, R.string.try_again, joinMuc);
+                    showSnackbar(
+                            R.string.conference_technical_problems, R.string.try_again, joinMuc);
                     break;
                 case UNKNOWN:
                     showSnackbar(R.string.conference_unknown_error, R.string.try_again, joinMuc);
@@ -4168,7 +4181,7 @@ public class ConversationFragment extends XmppFragment
                         if (!ReadByMarker.contains(marker, addedMarkers)) {
                             addedMarkers.add(
                                     marker); // may be put outside this condition. set should do
-                                             // dedup anyway
+                            // dedup anyway
                             MucOptions.User user = mucOptions.findUser(marker);
                             if (user != null && !users.contains(user)) {
                                 shownMarkers.add(user);
@@ -4427,8 +4440,10 @@ public class ConversationFragment extends XmppFragment
                         });
     }
 
-    public void showNoPGPKeyDialog(final boolean plural, final DialogInterface.OnClickListener listener) {
-        final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
+    public void showNoPGPKeyDialog(
+            final boolean plural, final DialogInterface.OnClickListener listener) {
+        final MaterialAlertDialogBuilder builder =
+                new MaterialAlertDialogBuilder(requireActivity());
         if (plural) {
             builder.setTitle(getString(R.string.no_pgp_keys));
             builder.setMessage(getText(R.string.contacts_have_no_pgp_keys));
@@ -4597,7 +4612,13 @@ public class ConversationFragment extends XmppFragment
         try {
             getActivity()
                     .startIntentSenderForResult(
-                            pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0, Compatibility.pgpStartIntentSenderOptions());
+                            pendingIntent.getIntentSender(),
+                            requestCode,
+                            null,
+                            0,
+                            0,
+                            0,
+                            Compatibility.pgpStartIntentSenderOptions());
         } catch (final SendIntentException ignored) {
         }
     }
  
  
  
    
    @@ -53,7 +53,6 @@ import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.widget.Toast;
-
 import androidx.annotation.IdRes;
 import androidx.annotation.NonNull;
 import androidx.appcompat.app.ActionBar;
@@ -117,8 +116,22 @@ import eu.siacs.conversations.utils.ThemeHelper;
 import eu.siacs.conversations.utils.XmppUri;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.openintents.openpgp.util.OpenPgpApi;
 
-public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged {
+public class ConversationsActivity extends XmppActivity
+        implements OnConversationSelected,
+                OnConversationArchived,
+                OnConversationsListItemUpdated,
+                OnConversationRead,
+                XmppConnectionService.OnAccountUpdate,
+                XmppConnectionService.OnConversationUpdate,
+                XmppConnectionService.OnRosterUpdate,
+                OnUpdateBlocklist,
+                XmppConnectionService.OnShowErrorToast,
+                XmppConnectionService.OnAffiliationChanged {
 
     public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.action.VIEW";
     public static final String EXTRA_CONVERSATION = "conversationUuid";
@@ -134,11 +147,9 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
     public static final String EXTRA_NODE = "node";
     public static final String EXTRA_JID = "jid";
 
-    private static final List<String> VIEW_AND_SHARE_ACTIONS = Arrays.asList(
-            ACTION_VIEW_CONVERSATION,
-            Intent.ACTION_SEND,
-            Intent.ACTION_SEND_MULTIPLE
-    );
+    private static final List<String> VIEW_AND_SHARE_ACTIONS =
+            Arrays.asList(
+                    ACTION_VIEW_CONVERSATION, Intent.ACTION_SEND, Intent.ACTION_SEND_MULTIPLE);
 
     public static final int REQUEST_OPEN_MESSAGE = 0x9876;
     public static final int REQUEST_PLAY_PAUSE = 0x5432;
@@ -162,9 +173,11 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
     public static final long DRAWER_START_CHAT_PUBLIC = 14;
     public static final long DRAWER_START_CHAT_DISCOVER = 15;
 
-    //secondary fragment (when holding the conversation, must be initialized before refreshing the overview fragment
-    private static final @IdRes
-    int[] FRAGMENT_ID_NOTIFICATION_ORDER = {R.id.secondary_fragment, R.id.main_fragment};
+    // secondary fragment (when holding the conversation, must be initialized before refreshing the
+    // overview fragment
+    private static final @IdRes int[] FRAGMENT_ID_NOTIFICATION_ORDER = {
+        R.id.secondary_fragment, R.id.main_fragment
+    };
     private final PendingItem<Intent> pendingViewIntent = new PendingItem<>();
     private final PendingItem<ActivityResult> postponedActivityResult = new PendingItem<>();
     private ActivityConversationsBinding binding;
@@ -181,7 +194,9 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
 
     private static boolean isViewOrShareIntent(Intent i) {
         Log.d(Config.LOGTAG, "action: " + (i == null ? null : i.getAction()));
-        return i != null && VIEW_AND_SHARE_ACTIONS.contains(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION);
+        return i != null
+                && VIEW_AND_SHARE_ACTIONS.contains(i.getAction())
+                && i.hasExtra(EXTRA_CONVERSATION);
     }
 
     private static Intent createLauncherIntent(Context context) {
@@ -428,7 +443,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
         }
 
         invalidateActionBarTitle();
-        if (binding.secondaryFragment != null && ConversationFragment.getConversation(this) == null) {
+        if (binding.secondaryFragment != null
+                && ConversationFragment.getConversation(this) == null) {
             Conversation conversation = ConversationsOverviewFragment.getSuggestion(this);
             if (conversation != null) {
                 openConversation(conversation, null);
@@ -696,7 +712,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
         return performRedirectIfNecessary(null, noAnimation);
     }
 
-    private boolean performRedirectIfNecessary(final Conversation ignore, final boolean noAnimation) {
+    private boolean performRedirectIfNecessary(
+            final Conversation ignore, final boolean noAnimation) {
         if (xmppConnectionService == null) {
             return false;
         }
@@ -707,12 +724,13 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
             if (noAnimation) {
                 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
             }
-            runOnUiThread(() -> {
-                startActivity(intent);
-                if (noAnimation) {
-                    overridePendingTransition(0, 0);
-                }
-            });
+            runOnUiThread(
+                    () -> {
+                        startActivity(intent);
+                        if (noAnimation) {
+                            overridePendingTransition(0, 0);
+                        }
+                    });
         }
         return mRedirectInProcess.get();
     }
@@ -738,7 +756,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
     }
 
     private String getBatteryOptimizationPreferenceKey() {
-        @SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
+        @SuppressLint("HardwareIds")
+        String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
         return "show_battery_optimization" + (device == null ? "" : device);
     }
 
@@ -747,20 +766,31 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
     }
 
     private boolean openBatteryOptimizationDialogIfNeeded() {
-        if (isOptimizingBattery() && getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true)) {
+        if (isOptimizingBattery()
+                && getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true)) {
             final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
             builder.setTitle(R.string.battery_optimizations_enabled);
-            builder.setMessage(getString(R.string.battery_optimizations_enabled_dialog, getString(R.string.app_name)));
-            builder.setPositiveButton(R.string.next, (dialog, which) -> {
-                final Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
-                final Uri uri = Uri.parse("package:" + getPackageName());
-                intent.setData(uri);
-                try {
-                    startActivityForResult(intent, REQUEST_BATTERY_OP);
-                } catch (final ActivityNotFoundException e) {
-                    Toast.makeText(this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
-                }
-            });
+            builder.setMessage(
+                    getString(
+                            R.string.battery_optimizations_enabled_dialog,
+                            getString(R.string.app_name)));
+            builder.setPositiveButton(
+                    R.string.next,
+                    (dialog, which) -> {
+                        final Intent intent =
+                                new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
+                        final Uri uri = Uri.parse("package:" + getPackageName());
+                        intent.setData(uri);
+                        try {
+                            startActivityForResult(intent, REQUEST_BATTERY_OP);
+                        } catch (final ActivityNotFoundException e) {
+                            Toast.makeText(
+                                            this,
+                                            R.string.device_does_not_support_battery_op,
+                                            Toast.LENGTH_SHORT)
+                                    .show();
+                        }
+                    });
             builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain());
             final AlertDialog dialog = builder.create();
             dialog.setCanceledOnTouchOutside(false);
@@ -771,8 +801,12 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
     }
 
     private boolean requestNotificationPermissionIfNeeded() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
-            requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, REQUEST_POST_NOTIFICATION);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+                && ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
+                        != PackageManager.PERMISSION_GRANTED) {
+            requestPermissions(
+                    new String[] {Manifest.permission.POST_NOTIFICATIONS},
+                    REQUEST_POST_NOTIFICATION);
             return true;
         }
         return false;
@@ -878,9 +912,10 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
         }
     }
 
-    private boolean processViewIntent(Intent intent) {
+    private boolean processViewIntent(final Intent intent) {
         final String uuid = intent.getStringExtra(EXTRA_CONVERSATION);
-        final Conversation conversation = uuid != null ? xmppConnectionService.findConversationByUuid(uuid) : null;
+        final Conversation conversation =
+                uuid != null ? xmppConnectionService.findConversationByUuidReliable(uuid) : null;
         if (conversation == null) {
             Log.d(Config.LOGTAG, "unable to view conversation with uuid:" + uuid);
             return false;
@@ -890,7 +925,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
     }
 
     @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+    public void onRequestPermissionsResult(
+            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
         UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
         if (grantResults.length > 0) {
@@ -1057,7 +1093,9 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
     public void onConversationSelected(Conversation conversation) {
         clearPendingViewIntent();
         if (ConversationFragment.getConversation(this) == conversation) {
-            Log.d(Config.LOGTAG, "ignore onConversationSelected() because conversation is already open");
+            Log.d(
+                    Config.LOGTAG,
+                    "ignore onConversationSelected() because conversation is already open");
             return;
         }
         openConversation(conversation, null);
@@ -1070,13 +1108,12 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
     }
 
     private void displayToast(final String msg) {
-        runOnUiThread(() -> Toast.makeText(ConversationsActivity.this, msg, Toast.LENGTH_SHORT).show());
+        runOnUiThread(
+                () -> Toast.makeText(ConversationsActivity.this, msg, Toast.LENGTH_SHORT).show());
     }
 
     @Override
-    public void onAffiliationChangedSuccessful(Jid jid) {
-
-    }
+    public void onAffiliationChangedSuccessful(Jid jid) {}
 
     @Override
     public void onAffiliationChangeFailed(Jid jid, int resId) {
@@ -1086,7 +1123,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
     private void openConversation(Conversation conversation, Bundle extras) {
         final FragmentManager fragmentManager = getFragmentManager();
         executePendingTransactions(fragmentManager);
-        ConversationFragment conversationFragment = (ConversationFragment) fragmentManager.findFragmentById(R.id.secondary_fragment);
+        ConversationFragment conversationFragment =
+                (ConversationFragment) fragmentManager.findFragmentById(R.id.secondary_fragment);
         final boolean mainNeedsRefresh;
         if (conversationFragment == null) {
             mainNeedsRefresh = false;
@@ -1102,7 +1140,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
                     fragmentTransaction.commit();
                 } catch (IllegalStateException e) {
                     Log.w(Config.LOGTAG, "sate loss while opening conversation", e);
-                    //allowing state loss is probably fine since view intents et all are already stored and a click can probably be 'ignored'
+                    // allowing state loss is probably fine since view intents et all are already
+                    // stored and a click can probably be 'ignored'
                     return;
                 }
             }
@@ -1120,14 +1159,15 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
         try {
             fragmentManager.executePendingTransactions();
         } catch (final Exception e) {
-            Log.e(Config.LOGTAG,"unable to execute pending fragment transactions");
+            Log.e(Config.LOGTAG, "unable to execute pending fragment transactions");
         }
     }
 
     public boolean onXmppUriClicked(Uri uri) {
         XmppUri xmppUri = new XmppUri(uri);
         if (xmppUri.isValidJid() && !xmppUri.hasFingerprints()) {
-            final Conversation conversation = xmppConnectionService.findUniqueConversationByJid(xmppUri);
+            final Conversation conversation =
+                    xmppConnectionService.findUniqueConversationByJid(xmppUri);
             if (conversation != null) {
                 if (xmppUri.getParameter("password") != null) {
                     xmppConnectionService.providePasswordForMuc(conversation, xmppUri.getParameter("password"));
@@ -1293,7 +1333,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
         final FragmentManager fragmentManager = getFragmentManager();
         FragmentTransaction transaction = fragmentManager.beginTransaction();
         final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment);
-        final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment);
+        final Fragment secondaryFragment =
+                fragmentManager.findFragmentById(R.id.secondary_fragment);
         if (mainFragment != null) {
             if (binding.secondaryFragment != null) {
                 if (mainFragment instanceof ConversationFragment) {
@@ -1348,7 +1389,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
                 return;
             }
         }
-        final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment);
+        final Fragment secondaryFragment =
+                fragmentManager.findFragmentById(R.id.secondary_fragment);
         if (secondaryFragment instanceof ConversationFragment conversationFragment) {
             final Conversation conversation = conversationFragment.getConversation();
             if (conversation != null) {
@@ -1392,15 +1434,21 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
             try {
                 fragmentManager.popBackStack();
             } catch (final IllegalStateException e) {
-                Log.w(Config.LOGTAG, "state loss while popping back state after archiving conversation", e);
-                //this usually means activity is no longer active; meaning on the next open we will run through this again
+                Log.w(
+                        Config.LOGTAG,
+                        "state loss while popping back state after archiving conversation",
+                        e);
+                // this usually means activity is no longer active; meaning on the next open we will
+                // run through this again
             }
             return;
         }
-        final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment);
+        final Fragment secondaryFragment =
+                fragmentManager.findFragmentById(R.id.secondary_fragment);
         if (secondaryFragment instanceof ConversationFragment) {
             if (((ConversationFragment) secondaryFragment).getConversation() == conversation) {
-                Conversation suggestion = ConversationsOverviewFragment.getSuggestion(this, conversation);
+                Conversation suggestion =
+                        ConversationsOverviewFragment.getSuggestion(this, conversation);
                 if (suggestion != null) {
                     openConversation(suggestion, null);
                 }
  
  
  
    
    @@ -586,6 +586,7 @@ public class EditAccountActivity extends OmemoActivity
 
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        // TODO check for Camera / Scan permission
         super.onActivityResult(requestCode, resultCode, data);
         if (requestCode == REQUEST_BATTERY_OP || requestCode == REQUEST_DATA_SAVER) {
             updateAccountInformation(mAccount == null);
@@ -764,7 +765,7 @@ public class EditAccountActivity extends OmemoActivity
         this.binding.hostname.addTextChangedListener(mTextWatcher);
         this.binding.hostname.setOnFocusChangeListener(mEditTextFocusListener);
         this.binding.clearDevices.setOnClickListener(v -> showWipePepDialog());
-        this.binding.port.setText(String.valueOf(Resolver.DEFAULT_PORT_XMPP));
+        this.binding.port.setText(String.valueOf(Resolver.XMPP_PORT_STARTTLS));
         this.binding.port.addTextChangedListener(mTextWatcher);
         this.binding.saveButton.setOnClickListener(this.mSaveButtonClickListener);
         this.binding.cancelButton.setOnClickListener(this.mCancelButtonClickListener);
@@ -1110,11 +1111,7 @@ public class EditAccountActivity extends OmemoActivity
     }
 
     private void deleteAccount() {
-        this.deleteAccount(
-                mAccount,
-                () -> {
-                    finish();
-                });
+        this.deleteAccount(mAccount, () -> finish());
     }
 
     private boolean inNeedOfSaslAccept() {
@@ -1519,7 +1516,7 @@ public class EditAccountActivity extends OmemoActivity
             if (hasKeys
                     && Config.supportOmemo()) { // TODO: either the button should be visible if we
                 // print an active device or the device list should
-                // be fed with reactived devices
+                // be fed with reactivated devices
                 this.binding.otherDeviceKeysCard.setVisibility(View.VISIBLE);
                 Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
                 if (otherDevices == null || otherDevices.isEmpty()) {
@@ -1548,12 +1545,17 @@ public class EditAccountActivity extends OmemoActivity
             }
         } else {
             final TextInputLayout errorLayout;
-            if (this.mAccount.errorStatus()) {
-                if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED
-                        || this.mAccount.getStatus() == Account.State.DOWNGRADE_ATTACK) {
+            final var status = this.mAccount.getStatus();
+            if (status.isError()
+                    || Arrays.asList(
+                                    Account.State.NO_INTERNET,
+                                    Account.State.MISSING_INTERNET_PERMISSION)
+                            .contains(status)) {
+                if (status == Account.State.UNAUTHORIZED
+                        || status == Account.State.DOWNGRADE_ATTACK) {
                     errorLayout = this.binding.accountPasswordLayout;
                 } else if (mShowOptions
-                        && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND
+                        && status == Account.State.SERVER_NOT_FOUND
                         && this.binding.hostname.getText().length() > 0) {
                     errorLayout = this.binding.hostnameLayout;
                 } else {
  
  
  
    
    @@ -29,6 +29,8 @@
 
 package eu.siacs.conversations.ui;
 
+import static eu.siacs.conversations.ui.PublishProfilePictureActivity.REQUEST_CHOOSE_PICTURE;
+
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.drawable.AnimatedImageDrawable;
@@ -40,10 +42,9 @@ import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
 import android.widget.Toast;
-
 import androidx.annotation.StringRes;
 import androidx.databinding.DataBindingUtil;
-
+import com.canhub.cropper.CropImage;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.databinding.ActivityPublishProfilePictureBinding;
@@ -52,20 +53,15 @@ import eu.siacs.conversations.persistance.FileBackend;
 import eu.siacs.conversations.ui.interfaces.OnAvatarPublication;
 import eu.siacs.conversations.ui.util.PendingItem;
 
-import static eu.siacs.conversations.ui.PublishProfilePictureActivity.REQUEST_CHOOSE_PICTURE;
-
-import com.canhub.cropper.CropImage;
-
-public class PublishGroupChatProfilePictureActivity extends XmppActivity implements OnAvatarPublication {
+public class PublishGroupChatProfilePictureActivity extends XmppActivity
+        implements OnAvatarPublication {
     private final PendingItem<String> pendingConversationUuid = new PendingItem<>();
     private ActivityPublishProfilePictureBinding binding;
     private Conversation conversation;
     private Uri uri;
 
     @Override
-    protected void refreshUiReal() {
-
-    }
+    protected void refreshUiReal() {}
 
     @Override
     protected void onBackendConnected() {
@@ -97,17 +93,20 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity impleme
     }
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
+    public void onCreate(final Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        this.binding = DataBindingUtil.setContentView(this, R.layout.activity_publish_profile_picture);
+        this.binding =
+                DataBindingUtil.setContentView(this, R.layout.activity_publish_profile_picture);
+        this.binding.contactOnly.setVisibility(View.GONE);
         Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
         setSupportActionBar(this.binding.toolbar);
         configureActionBar(getSupportActionBar());
         this.binding.cancelButton.setOnClickListener((v) -> this.finish());
         this.binding.secondaryHint.setVisibility(View.GONE);
-        this.binding.accountImage.setOnClickListener((v) -> PublishProfilePictureActivity.chooseAvatar(this));
-        Intent intent = getIntent();
-        String uuid = intent == null ? null : intent.getStringExtra("uuid");
+        this.binding.accountImage.setOnClickListener(
+                (v) -> PublishProfilePictureActivity.chooseAvatar(this));
+        final var intent = getIntent();
+        final var uuid = intent == null ? null : intent.getStringExtra("uuid");
         if (uuid != null) {
             pendingConversationUuid.push(uuid);
         }
@@ -115,25 +114,24 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity impleme
         this.binding.publishButton.setOnClickListener(this::publish);
     }
 
-
-    private void publish(View view) {
+    private void publish(final View view) {
         binding.publishButton.setText(R.string.publishing);
         binding.publishButton.setEnabled(false);
         xmppConnectionService.publishMucAvatar(conversation, uri, this);
     }
 
     @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+    public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
         if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
             final CropImage.ActivityResult result = CropImage.getActivityResult(data);
             if (resultCode == RESULT_OK) {
-                this.uri = result.getUri();
+                this.uri = result == null ? null : result.getUri();
                 if (xmppConnectionServiceBound) {
                     reloadAvatar();
                 }
             } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {
-                Exception error = result.getError();
+                final var error = result == null ? null : result.getError();
                 if (error != null) {
                     Toast.makeText(this, error.getMessage(), Toast.LENGTH_SHORT).show();
                 }
@@ -162,18 +160,21 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity impleme
 
     @Override
     public void onAvatarPublicationSucceeded() {
-        runOnUiThread(() -> {
-            Toast.makeText(this, R.string.avatar_has_been_published, Toast.LENGTH_SHORT).show();
-            finish();
-        });
+        runOnUiThread(
+                () -> {
+                    Toast.makeText(this, R.string.avatar_has_been_published, Toast.LENGTH_SHORT)
+                            .show();
+                    finish();
+                });
     }
 
     @Override
     public void onAvatarPublicationFailed(@StringRes int res) {
-        runOnUiThread(() -> {
-            Toast.makeText(this, res, Toast.LENGTH_SHORT).show();
-            this.binding.publishButton.setText(R.string.publish);
-            this.binding.publishButton.setEnabled(true);
-        });
+        runOnUiThread(
+                () -> {
+                    Toast.makeText(this, res, Toast.LENGTH_SHORT).show();
+                    this.binding.publishButton.setText(R.string.publish);
+                    this.binding.publishButton.setEnabled(true);
+                });
     }
 }
  
  
  
    
    @@ -14,19 +14,12 @@ import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnLongClickListener;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
 import android.widget.Toast;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.StringRes;
 import androidx.databinding.DataBindingUtil;
-
 import com.canhub.cropper.CropImage;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.databinding.ActivityPublishProfilePictureBinding;
@@ -35,83 +28,89 @@ import eu.siacs.conversations.persistance.FileBackend;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.ui.interfaces.OnAvatarPublication;
 import eu.siacs.conversations.utils.PhoneHelper;
+import java.util.concurrent.atomic.AtomicBoolean;
 
-public class PublishProfilePictureActivity extends XmppActivity implements XmppConnectionService.OnAccountUpdate, OnAvatarPublication {
+public class PublishProfilePictureActivity extends XmppActivity
+        implements XmppConnectionService.OnAccountUpdate, OnAvatarPublication {
 
     public static final int REQUEST_CHOOSE_PICTURE = 0x1337;
 
-    private ImageView avatar;
-    private TextView hintOrWarning;
-    private TextView secondaryHint;
-    private Button cancelButton;
-    private Button publishButton;
+    private ActivityPublishProfilePictureBinding binding;
     private Uri avatarUri;
     private Uri defaultUri;
     private Account account;
     private boolean support = false;
     private boolean publishing = false;
     private final AtomicBoolean handledExternalUri = new AtomicBoolean(false);
-    private final OnLongClickListener backToDefaultListener = new OnLongClickListener() {
-
-        @Override
-        public boolean onLongClick(View v) {
-            avatarUri = defaultUri;
-            loadImageIntoPreview(defaultUri);
-            return true;
-        }
-    };
+    private final OnLongClickListener backToDefaultListener =
+            new OnLongClickListener() {
+
+                @Override
+                public boolean onLongClick(View v) {
+                    avatarUri = defaultUri;
+                    loadImageIntoPreview(defaultUri);
+                    return true;
+                }
+            };
     private boolean mInitialAccountSetup;
 
     @Override
     public void onAvatarPublicationSucceeded() {
-        runOnUiThread(() -> {
-            if (mInitialAccountSetup) {
-                Intent intent = new Intent(getApplicationContext(), StartConversationActivity.class);
-                StartConversationActivity.addInviteUri(intent, getIntent());
-                intent.putExtra("init", true);
-                intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
-                startActivity(intent);
-            }
-            Toast.makeText(PublishProfilePictureActivity.this,
-                    R.string.avatar_has_been_published,
-                    Toast.LENGTH_SHORT).show();
-            finish();
-        });
+        runOnUiThread(
+                () -> {
+                    if (mInitialAccountSetup) {
+                        Intent intent =
+                                new Intent(
+                                        getApplicationContext(), StartConversationActivity.class);
+                        StartConversationActivity.addInviteUri(intent, getIntent());
+                        intent.putExtra("init", true);
+                        intent.putExtra(
+                                EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
+                        startActivity(intent);
+                    }
+                    Toast.makeText(
+                                    PublishProfilePictureActivity.this,
+                                    R.string.avatar_has_been_published,
+                                    Toast.LENGTH_SHORT)
+                            .show();
+                    finish();
+                });
     }
 
     @Override
-    public void onAvatarPublicationFailed(int res) {
-        runOnUiThread(() -> {
-            hintOrWarning.setText(res);
-            hintOrWarning.setVisibility(View.VISIBLE);
-            publishing = false;
-            togglePublishButton(true, R.string.publish);
-        });
+    public void onAvatarPublicationFailed(final int res) {
+        runOnUiThread(
+                () -> {
+                    this.binding.hintOrWarning.setText(res);
+                    this.binding.hintOrWarning.setVisibility(View.VISIBLE);
+                    this.publishing = false;
+                    togglePublishButton(true, R.string.publish);
+                });
     }
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
+    public void onCreate(final Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        ActivityPublishProfilePictureBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_publish_profile_picture);
+        this.binding =
+                DataBindingUtil.setContentView(this, R.layout.activity_publish_profile_picture);
 
         setSupportActionBar(binding.toolbar);
 
         Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
 
-        this.avatar = findViewById(R.id.account_image);
-        this.cancelButton = findViewById(R.id.cancel_button);
-        this.publishButton = findViewById(R.id.publish_button);
-        this.hintOrWarning = findViewById(R.id.hint_or_warning);
-        this.secondaryHint = findViewById(R.id.secondary_hint);
-        this.publishButton.setOnClickListener(v -> {
-            if (avatarUri != null) {
-                publishing = true;
-                togglePublishButton(false, R.string.publishing);
-                xmppConnectionService.publishAvatar(account, avatarUri, this);
-            }
-        });
-        this.cancelButton.setOnClickListener(
+        this.binding.publishButton.setOnClickListener(
+                v -> {
+                    final boolean open = !this.binding.contactOnly.isChecked();
+                    final var uri = this.avatarUri;
+                    if (uri == null) {
+                        return;
+                    }
+                    publishing = true;
+                    togglePublishButton(false, R.string.publishing);
+                    xmppConnectionService.publishAvatarAsync(account, uri, open, this);
+                });
+        this.binding.cancelButton.setOnClickListener(
                 v -> {
                     if (mInitialAccountSetup) {
                         final Intent intent =
@@ -127,11 +126,12 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
                     }
                     finish();
                 });
-        this.avatar.setOnClickListener(v -> chooseAvatar(this));
+        this.binding.accountImage.setOnClickListener(v -> chooseAvatar(this));
         this.defaultUri = PhoneHelper.getProfilePictureUri(getApplicationContext());
         if (savedInstanceState != null) {
             this.avatarUri = savedInstanceState.getParcelable("uri");
-            this.handledExternalUri.set(savedInstanceState.getBoolean("handle_external_uri",false));
+            this.handledExternalUri.set(
+                    savedInstanceState.getBoolean("handle_external_uri", false));
         }
     }
 
@@ -144,8 +144,8 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
     @Override
     public boolean onOptionsItemSelected(final MenuItem item) {
         if (item.getItemId() == R.id.action_delete_avatar) {
-            if (xmppConnectionService != null && account != null) {
-                xmppConnectionService.deleteAvatar(account);
+            if (account != null) {
+                deleteAvatar(account);
             }
             return true;
         } else {
@@ -153,6 +153,22 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
         }
     }
 
+    private void deleteAvatar(final Account account) {
+        new MaterialAlertDialogBuilder(this)
+                .setTitle(R.string.delete_avatar)
+                .setMessage(R.string.delete_avatar_message)
+                .setNegativeButton(R.string.cancel, null)
+                .setPositiveButton(
+                        R.string.confirm,
+                        (d, v) -> {
+                            if (xmppConnectionService != null) {
+                                xmppConnectionService.deleteAvatar(account);
+                            }
+                        })
+                .create()
+                .show();
+    }
+
     @Override
     public void onSaveInstanceState(@NonNull Bundle outState) {
         if (this.avatarUri != null) {
@@ -162,7 +178,6 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
         super.onSaveInstanceState(outState);
     }
 
-
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
@@ -181,7 +196,7 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
             }
         } else if (requestCode == REQUEST_CHOOSE_PICTURE) {
             if (resultCode == RESULT_OK) {
-                cropUri(data.getData());
+                cropUri(this, data.getData());
             }
         }
     }
@@ -191,9 +206,9 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
             final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
             intent.setType("image/*");
             activity.startActivityForResult(
-                    Intent.createChooser(intent, activity.getString(R.string.attach_choose_picture)),
-                    REQUEST_CHOOSE_PICTURE
-            );
+                    Intent.createChooser(
+                            intent, activity.getString(R.string.attach_choose_picture)),
+                    REQUEST_CHOOSE_PICTURE);
         } else {
             CropImage.activity()
                     .setOutputCompressFormat(Bitmap.CompressFormat.PNG)
@@ -212,7 +227,9 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
     }
 
     private void reloadAvatar() {
-        this.support = this.account.getXmppConnection() != null && this.account.getXmppConnection().getFeatures().pep();
+        this.support =
+                this.account.getXmppConnection() != null
+                        && this.account.getXmppConnection().getFeatures().pep();
         if (this.avatarUri == null) {
             if (this.account.getAvatar() != null || this.defaultUri == null) {
                 loadImageIntoPreview(null);
@@ -233,80 +250,93 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
 
         final Uri uri = intent != null ? intent.getData() : null;
 
-        if (uri != null && handledExternalUri.compareAndSet(false,true)) {
-            cropUri(uri);
+        if (uri != null && handledExternalUri.compareAndSet(false, true)) {
+            cropUri(this, uri);
             return;
         }
 
         if (this.mInitialAccountSetup) {
-            this.cancelButton.setText(R.string.skip);
+            this.binding.cancelButton.setText(R.string.skip);
         }
-        configureActionBar(getSupportActionBar(), !this.mInitialAccountSetup && !handledExternalUri.get());
+        configureActionBar(
+                getSupportActionBar(), !this.mInitialAccountSetup && !handledExternalUri.get());
     }
 
-    public void cropUri(final Uri uri) {
+    public void cropUri(final Activity activity, final Uri uri) {
         if (Build.VERSION.SDK_INT >= 28) {
             loadImageIntoPreview(uri);
-            if (this.avatar.getDrawable() instanceof AnimatedImageDrawable || this.avatar.getDrawable() instanceof FileBackend.SVGDrawable) {
+            if (binding.accountImage.getDrawable() instanceof AnimatedImageDrawable || binding.accountImage.getDrawable() instanceof FileBackend.SVGDrawable) {
                 this.avatarUri = uri;
                 return;
             }
         }
 
-        CropImage.activity(uri).setOutputCompressFormat(Bitmap.CompressFormat.PNG)
+        CropImage.activity(uri)
+                .setOutputCompressFormat(Bitmap.CompressFormat.PNG)
                 .setAspectRatio(1, 1)
                 .setMinCropResultSize(Config.AVATAR_SIZE, Config.AVATAR_SIZE)
                 .start(this);
     }
 
-    protected void loadImageIntoPreview(Uri uri) {
+    protected void loadImageIntoPreview(final Uri uri) {
 
         Drawable bm = null;
         if (uri == null) {
-            bm = avatarService().get(account, (int) getResources().getDimension(R.dimen.publish_avatar_size));
+            bm =
+                    avatarService()
+                            .get(
+                                    account,
+                                    (int) getResources().getDimension(R.dimen.publish_avatar_size));
         } else {
             try {
-                bm = xmppConnectionService.getFileBackend().cropCenterSquareDrawable(uri, (int) getResources().getDimension(R.dimen.publish_avatar_size));
-            } catch (Exception e) {
+                bm =
+                        xmppConnectionService
+                                .getFileBackend()
+                                .cropCenterSquareDrawable(
+                                        uri,
+                                        (int)
+                                                getResources()
+                                                        .getDimension(R.dimen.publish_avatar_size));
+            } catch (final Exception e) {
                 Log.d(Config.LOGTAG, "unable to load bitmap into image view", e);
             }
         }
 
         if (bm == null) {
             togglePublishButton(false, R.string.publish);
-            this.hintOrWarning.setVisibility(View.VISIBLE);
-            this.hintOrWarning.setText(R.string.error_publish_avatar_converting);
+            this.binding.hintOrWarning.setVisibility(View.VISIBLE);
+            this.binding.hintOrWarning.setText(R.string.error_publish_avatar_converting);
             return;
         }
-        this.avatar.setImageDrawable(bm);
+        this.binding.accountImage.setImageDrawable(bm);
         if (Build.VERSION.SDK_INT >= 28 && bm instanceof AnimatedImageDrawable) {
             ((AnimatedImageDrawable) bm).start();
         }
         if (support) {
             togglePublishButton(uri != null, R.string.publish);
-            this.hintOrWarning.setVisibility(View.INVISIBLE);
+            this.binding.hintOrWarning.setVisibility(View.INVISIBLE);
         } else {
             togglePublishButton(false, R.string.publish);
-            this.hintOrWarning.setVisibility(View.VISIBLE);
+            this.binding.hintOrWarning.setVisibility(View.VISIBLE);
             if (account.getStatus() == Account.State.ONLINE) {
-                this.hintOrWarning.setText(R.string.error_publish_avatar_no_server_support);
+                this.binding.hintOrWarning.setText(R.string.error_publish_avatar_no_server_support);
             } else {
-                this.hintOrWarning.setText(R.string.error_publish_avatar_offline);
+                this.binding.hintOrWarning.setText(R.string.error_publish_avatar_offline);
             }
         }
         if (this.defaultUri == null || this.defaultUri.equals(uri)) {
-            this.secondaryHint.setVisibility(View.INVISIBLE);
-            this.avatar.setOnLongClickListener(null);
+            this.binding.secondaryHint.setVisibility(View.INVISIBLE);
+            this.binding.accountImage.setOnLongClickListener(null);
         } else if (this.defaultUri != null) {
-            this.secondaryHint.setVisibility(View.VISIBLE);
-            this.avatar.setOnLongClickListener(this.backToDefaultListener);
+            this.binding.secondaryHint.setVisibility(View.VISIBLE);
+            this.binding.accountImage.setOnLongClickListener(this.backToDefaultListener);
         }
     }
 
     protected void togglePublishButton(boolean enabled, @StringRes int res) {
         final boolean status = enabled && !publishing;
-        this.publishButton.setText(publishing ? R.string.publishing : res);
-        this.publishButton.setEnabled(status);
+        this.binding.publishButton.setText(publishing ? R.string.publishing : res);
+        this.binding.publishButton.setEnabled(status);
     }
 
     public void refreshUiReal() {
@@ -319,5 +349,4 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
     public void onAccountUpdate() {
         refreshUi();
     }
-
 }
  
  
  
    
    @@ -32,6 +32,7 @@ import androidx.annotation.RequiresApi;
 import androidx.annotation.StringRes;
 import androidx.databinding.DataBindingUtil;
 
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
@@ -100,7 +101,8 @@ public class RtpSessionActivity extends XmppActivity
     public static final String ACTION_MAKE_VOICE_CALL = "action_make_voice_call";
     public static final String ACTION_MAKE_VIDEO_CALL = "action_make_video_call";
 
-    private static final int CALL_DURATION_UPDATE_INTERVAL = 333;
+    private static final int CALL_DURATION_UPDATE_INTERVAL = 250;
+    private static final int BUTTON_VISIBILITY_TIMEOUT = 10_000;
 
     public static final List<RtpEndUserState> END_CARD =
             Arrays.asList(
@@ -155,6 +157,8 @@ public class RtpSessionActivity extends XmppActivity
                     mHandler.postDelayed(mTickExecutor, CALL_DURATION_UPDATE_INTERVAL);
                 }
             };
+    private boolean buttonsHiddenAfterTimeout = false;
+    private final Runnable mVisibilityToggleExecutor = this::updateButtonInVideoCallVisibility;
 
     public static Set<Media> actionToMedia(final String action) {
         if (ACTION_MAKE_VIDEO_CALL.equals(action)) {
@@ -191,6 +195,8 @@ public class RtpSessionActivity extends XmppActivity
                                 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                                 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
         this.binding = DataBindingUtil.setContentView(this, R.layout.activity_rtp_session);
+        this.binding.remoteVideo.setOnClickListener(this::onVideoScreenClick);
+        this.binding.localVideo.setOnClickListener(this::onVideoScreenClick);
         setSupportActionBar(binding.toolbar);
 
         binding.dialpad.setClickConsumer(tag -> {
@@ -207,6 +213,10 @@ public class RtpSessionActivity extends XmppActivity
         Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
     }
 
+    private void onVideoScreenClick(final View view) {
+        resetVisibilityExecutorShowButtons();
+    }
+
     @Override
     public boolean onCreateOptionsMenu(final Menu menu) {
         getMenuInflater().inflate(R.menu.activity_rtp_session, menu);
@@ -573,15 +583,17 @@ public class RtpSessionActivity extends XmppActivity
             final String extraLastState = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
             final RtpEndUserState state =
                     extraLastState == null ? null : RtpEndUserState.valueOf(extraLastState);
+            final var contact = account.getRoster().getContact(with);
             if (state != null) {
                 Log.d(Config.LOGTAG, "restored last state from intent extra");
                 updateButtonConfiguration(state);
                 updateVerifiedShield(false);
                 updateStateDisplay(state);
                 updateIncomingCallScreen(state);
+                updateSupportWarning(state, contact);
                 invalidateOptionsMenu();
             }
-            setWith(account.getRoster().getContact(with), state);
+            setWith(state, contact);
             if (xmppConnectionService
                     .getJingleConnectionManager()
                     .fireJingleRtpConnectionStateUpdates()) {
@@ -604,10 +616,10 @@ public class RtpSessionActivity extends XmppActivity
     }
 
     private void setWith(final RtpEndUserState state) {
-        setWith(getWith(), state);
+        setWith(state, getWith());
     }
 
-    private void setWith(final Contact contact, final RtpEndUserState state) {
+    private void setWith(final RtpEndUserState state, final Contact contact) {
         binding.with.setText(contact.getDisplayName());
         if (Arrays.asList(RtpEndUserState.INCOMING_CALL, RtpEndUserState.ACCEPTING_CALL)
                 .contains(state)) {
@@ -655,12 +667,20 @@ public class RtpSessionActivity extends XmppActivity
     public void onStart() {
         super.onStart();
         mHandler.postDelayed(mTickExecutor, CALL_DURATION_UPDATE_INTERVAL);
+        mHandler.postDelayed(mVisibilityToggleExecutor, BUTTON_VISIBILITY_TIMEOUT);
         this.binding.remoteVideo.setOnAspectRatioChanged(this);
     }
 
+    @Override
+    public void onResume() {
+        super.onResume();
+        resetVisibilityExecutorShowButtons();
+    }
+
     @Override
     public void onStop() {
         mHandler.removeCallbacks(mTickExecutor);
+        mHandler.removeCallbacks(mVisibilityToggleExecutor);
         binding.remoteVideo.release();
         binding.remoteVideo.setOnAspectRatioChanged(null);
         binding.localVideo.release();
@@ -782,6 +802,17 @@ public class RtpSessionActivity extends XmppActivity
         }
     }
 
+    private boolean isInConnectedVideoCall() {
+        final JingleRtpConnection rtpConnection;
+        try {
+            rtpConnection = requireRtpConnection();
+        } catch (final IllegalStateException e) {
+            return false;
+        }
+        return rtpConnection.getMedia().contains(Media.VIDEO)
+                && rtpConnection.getEndUserState() == RtpEndUserState.CONNECTED;
+    }
+
     private boolean initializeActivityWithRunningRtpSession(
             final Account account, Jid with, String sessionId) {
         final WeakReference<JingleRtpConnection> reference =
@@ -844,7 +875,9 @@ public class RtpSessionActivity extends XmppActivity
         updateCallDuration();
         updateVerifiedShield(false);
         invalidateOptionsMenu();
-        setWith(account.getRoster().getContact(with), state);
+        final var contact = account.getRoster().getContact(with);
+        setWith(state, contact);
+        updateSupportWarning(state, contact);
     }
 
     private void reInitializeActivityWithRunningRtpSession(
@@ -885,7 +918,7 @@ public class RtpSessionActivity extends XmppActivity
             final ContentAddition contentAddition) {
         switch (state) {
             case INCOMING_CALL -> {
-                Preconditions.checkArgument(media.size() > 0, "Media must not be empty");
+                Preconditions.checkArgument(!media.isEmpty(), "Media must not be empty");
                 if (media.contains(Media.VIDEO)) {
                     setTitle(R.string.rtp_state_incoming_video_call);
                 } else {
@@ -934,7 +967,7 @@ public class RtpSessionActivity extends XmppActivity
 
     private void updateIncomingCallScreen(final RtpEndUserState state, final Contact contact) {
         if (state == RtpEndUserState.INCOMING_CALL || state == RtpEndUserState.ACCEPTING_CALL) {
-            final boolean show = getResources().getBoolean(R.bool.show_avatar_incoming_call);
+            final boolean show = getResources().getBoolean(R.bool.is_portrait_mode);
             if (show) {
                 binding.contactPhoto.setVisibility(View.VISIBLE);
                 if (contact == null) {
@@ -959,6 +992,18 @@ public class RtpSessionActivity extends XmppActivity
         }
     }
 
+    private void updateSupportWarning(final RtpEndUserState state, final Contact contact) {
+        if (state == RtpEndUserState.CONNECTIVITY_ERROR
+                && getResources().getBoolean(R.bool.is_portrait_mode)) {
+            binding.supportWarning.setVisibility(
+                    RtpCapability.check(contact) == RtpCapability.Capability.NONE
+                            ? View.VISIBLE
+                            : View.GONE);
+        } else {
+            binding.supportWarning.setVisibility(View.GONE);
+        }
+    }
+
     private Set<Media> getMedia() {
         return requireRtpConnection().getMedia();
     }
@@ -976,7 +1021,9 @@ public class RtpSessionActivity extends XmppActivity
             final RtpEndUserState state,
             final Set<Media> media,
             final ContentAddition contentAddition) {
-        if (state == RtpEndUserState.ENDING_CALL || isPictureInPicture()) {
+        if (state == RtpEndUserState.ENDING_CALL
+                || isPictureInPicture()
+                || this.buttonsHiddenAfterTimeout) {
             this.binding.rejectCall.setVisibility(View.INVISIBLE);
             this.binding.endCall.setVisibility(View.INVISIBLE);
             this.binding.acceptCall.setVisibility(View.INVISIBLE);
@@ -1033,12 +1080,17 @@ public class RtpSessionActivity extends XmppActivity
             this.binding.endCall.setContentDescription(getString(R.string.hang_up));
             this.binding.endCall.setOnClickListener(this::endCall);
             this.binding.endCall.setImageResource(R.drawable.ic_call_end_24dp);
-            this.binding.endCall.setVisibility(View.VISIBLE);
+            setVisibleAndShow(this.binding.endCall);
             this.binding.acceptCall.setVisibility(View.INVISIBLE);
         }
         updateInCallButtonConfiguration(state, media);
     }
 
+    private static void setVisibleAndShow(final FloatingActionButton button) {
+        button.show();
+        button.setVisibility(View.VISIBLE);
+    }
+
     private boolean isPictureInPicture() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             return isInPictureInPictureMode();
@@ -1055,7 +1107,8 @@ public class RtpSessionActivity extends XmppActivity
     @SuppressLint("RestrictedApi")
     private void updateInCallButtonConfiguration(
             final RtpEndUserState state, final Set<Media> media) {
-        if (STATES_CONSIDERED_CONNECTED.contains(state) && !isPictureInPicture()) {
+        final var showButtons = !isPictureInPicture() && !buttonsHiddenAfterTimeout;
+        if (STATES_CONSIDERED_CONNECTED.contains(state) && showButtons) {
             Preconditions.checkArgument(!media.isEmpty(), "Media must not be empty");
             if (media.contains(Media.VIDEO)) {
                 final JingleRtpConnection rtpConnection = requireRtpConnection();
@@ -1075,7 +1128,7 @@ public class RtpSessionActivity extends XmppActivity
                 this.binding.inCallActionLeft.setVisibility(View.GONE);
             }
         } else if (STATES_SHOWING_SPEAKER_CONFIGURATION.contains(state)
-                && !isPictureInPicture()
+                && showButtons
                 && Media.audioOnly(media)) {
             final CallIntegration callIntegration;
             try {
@@ -1140,17 +1193,17 @@ public class RtpSessionActivity extends XmppActivity
                 this.binding.inCallActionRight.setClickable(false);
             }
         }
-        this.binding.inCallActionRight.setVisibility(View.VISIBLE);
+        setVisibleAndShow(this.binding.inCallActionRight);
     }
 
     @SuppressLint("RestrictedApi")
     private void updateInCallButtonConfigurationVideo(
             final boolean videoEnabled, final boolean isCameraSwitchable) {
-        this.binding.inCallActionRight.setVisibility(View.VISIBLE);
+        setVisibleAndShow(this.binding.inCallActionRight);
         if (isCameraSwitchable) {
             this.binding.inCallActionFarRight.setImageResource(
                     R.drawable.ic_flip_camera_android_24dp);
-            this.binding.inCallActionFarRight.setVisibility(View.VISIBLE);
+            setVisibleAndShow(this.binding.inCallActionFarRight);
             this.binding.inCallActionFarRight.setOnClickListener(this::switchCamera);
             this.binding.inCallActionFarRight.setContentDescription(
                     getString(R.string.flip_camera));
@@ -1171,6 +1224,7 @@ public class RtpSessionActivity extends XmppActivity
     }
 
     private void switchCamera(final View view) {
+        resetVisibilityToggleExecutor();
         Futures.addCallback(
                 requireRtpConnection().switchCamera(),
                 new FutureCallback<>() {
@@ -1196,6 +1250,7 @@ public class RtpSessionActivity extends XmppActivity
     }
 
     private void enableVideo(final View view) {
+        resetVisibilityToggleExecutor();
         try {
             requireRtpConnection().setVideoEnabled(true);
         } catch (final IllegalStateException e) {
@@ -1206,6 +1261,7 @@ public class RtpSessionActivity extends XmppActivity
     }
 
     private void disableVideo(final View view) {
+        resetVisibilityToggleExecutor();
         final JingleRtpConnection rtpConnection = requireRtpConnection();
         final ContentAddition pending = rtpConnection.getPendingContentAddition();
         if (pending != null && pending.direction == ContentAddition.Direction.OUTGOING) {
@@ -1230,7 +1286,7 @@ public class RtpSessionActivity extends XmppActivity
             this.binding.inCallActionLeft.setImageResource(R.drawable.ic_mic_off_24dp);
             this.binding.inCallActionLeft.setOnClickListener(this::enableMicrophone);
         }
-        this.binding.inCallActionLeft.setVisibility(View.VISIBLE);
+        setVisibleAndShow(this.binding.inCallActionLeft);
     }
 
     private void updateCallDuration() {
@@ -1249,6 +1305,47 @@ public class RtpSessionActivity extends XmppActivity
         }
     }
 
+    private void resetVisibilityToggleExecutor() {
+        mHandler.removeCallbacks(this.mVisibilityToggleExecutor);
+        mHandler.postDelayed(this.mVisibilityToggleExecutor, BUTTON_VISIBILITY_TIMEOUT);
+    }
+
+    private void updateButtonInVideoCallVisibility() {
+        if (isInConnectedVideoCall()) {
+            if (isPictureInPicture()) {
+                return;
+            }
+            Log.d(Config.LOGTAG, "hiding in-call buttons after timeout was reached");
+            hideInCallButtons();
+        }
+    }
+
+    private void hideInCallButtons() {
+        binding.inCallActionLeft.hide();
+        binding.endCall.hide();
+        binding.inCallActionRight.hide();
+        binding.inCallActionFarRight.hide();
+    }
+
+    private void showInCallButtons() {
+        this.buttonsHiddenAfterTimeout = false;
+        final JingleRtpConnection rtpConnection;
+        try {
+            rtpConnection = requireRtpConnection();
+        } catch (final IllegalStateException e) {
+            return;
+        }
+        updateButtonConfiguration(
+                rtpConnection.getEndUserState(),
+                rtpConnection.getMedia(),
+                rtpConnection.getPendingContentAddition());
+    }
+
+    private void resetVisibilityExecutorShowButtons() {
+        resetVisibilityToggleExecutor();
+        showInCallButtons();
+    }
+
     private void updateVideoViews(final RtpEndUserState state) {
         if (END_CARD.contains(state) || state == RtpEndUserState.ENDING_CALL) {
             binding.localVideo.setVisibility(View.GONE);
@@ -1343,17 +1440,23 @@ public class RtpSessionActivity extends XmppActivity
         return connection.getRemoteVideoTrack();
     }
 
-    private void disableMicrophone(View view) {
-        final JingleRtpConnection rtpConnection = requireRtpConnection();
-        if (rtpConnection.setMicrophoneEnabled(false)) {
-            updateInCallButtonConfiguration();
-        }
+    private void disableMicrophone(final View view) {
+        setMicrophoneEnabled(false);
     }
 
-    private void enableMicrophone(View view) {
-        final JingleRtpConnection rtpConnection = requireRtpConnection();
-        if (rtpConnection.setMicrophoneEnabled(true)) {
-            updateInCallButtonConfiguration();
+    private void enableMicrophone(final View view) {
+        setMicrophoneEnabled(true);
+    }
+
+    private void setMicrophoneEnabled(final boolean enabled) {
+        resetVisibilityExecutorShowButtons();
+        try {
+            final JingleRtpConnection rtpConnection = requireRtpConnection();
+            if (rtpConnection.setMicrophoneEnabled(enabled)) {
+                updateInCallButtonConfiguration();
+            }
+        } catch (final IllegalStateException e) {
+            Toast.makeText(this, R.string.could_not_modify_call, Toast.LENGTH_SHORT).show();
         }
     }
 
@@ -1362,7 +1465,7 @@ public class RtpSessionActivity extends XmppActivity
             requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.EARPIECE);
             acquireProximityWakeLock();
         } catch (final IllegalStateException e) {
-            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+            Toast.makeText(this, R.string.could_not_modify_call, Toast.LENGTH_SHORT).show();
         }
     }
 
@@ -1371,7 +1474,7 @@ public class RtpSessionActivity extends XmppActivity
             requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.SPEAKER_PHONE);
             releaseProximityWakeLock();
         } catch (final IllegalStateException e) {
-            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+            Toast.makeText(this, R.string.could_not_modify_call, Toast.LENGTH_SHORT).show();
         }
     }
 
@@ -1461,6 +1564,7 @@ public class RtpSessionActivity extends XmppActivity
                     () -> getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
         }
         if (with.isBareJid()) {
+            // TODO check for ENDED
             updateRtpSessionProposalState(account, with, state);
             return;
         }
@@ -1484,6 +1588,7 @@ public class RtpSessionActivity extends XmppActivity
                 finish();
                 return;
             }
+            resetVisibilityToggleExecutor();
             runOnUiThread(
                     () -> {
                         updateStateDisplay(state, media, contentAddition);
@@ -1492,6 +1597,7 @@ public class RtpSessionActivity extends XmppActivity
                         updateButtonConfiguration(state, media, contentAddition);
                         updateVideoViews(state);
                         updateIncomingCallScreen(state, contact);
+                        updateSupportWarning(state, contact);
                         invalidateOptionsMenu();
                     });
             if (END_CARD.contains(state)) {
@@ -1569,6 +1675,7 @@ public class RtpSessionActivity extends XmppActivity
                         updateStateDisplay(state);
                         updateButtonConfiguration(state, media, null);
                         updateIncomingCallScreen(state);
+                        updateSupportWarning(state, account.getRoster().getContact(with));
                         invalidateOptionsMenu();
                     });
             resetIntent(account, with, state, media);
  
  
  
    
    @@ -8,11 +8,11 @@ import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.widget.Toast;
-
 import androidx.annotation.NonNull;
+import androidx.core.content.pm.ShortcutManagerCompat;
 import androidx.databinding.DataBindingUtil;
 import androidx.recyclerview.widget.LinearLayoutManager;
-
+import com.google.common.collect.Iterables;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.databinding.ActivityShareWithBinding;
@@ -21,7 +21,6 @@ import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.ui.adapter.ConversationAdapter;
 import eu.siacs.conversations.xmpp.Jid;
-
 import java.util.ArrayList;
 import java.util.List;
 
@@ -112,7 +111,34 @@ public class ShareWithActivity extends XmppActivity
                 new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
         binding.chooseConversationList.setAdapter(mAdapter);
         mAdapter.setConversationClickListener((view, conversation) -> share(conversation));
+        final var intent = getIntent();
+        final var shortcutId = intent.getStringExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID);
         this.share = new Share();
+        if (shortcutId != null) {
+            final var conversation = shortcutIdToConversation(shortcutId);
+            if (conversation != null) {
+                // we have everything we need. Jump into chat
+                populateShare(intent);
+                share(conversation);
+            }
+        }
+    }
+
+    private String shortcutIdToConversation(final String shortcutId) {
+        final var shortcut =
+                Iterables.tryFind(
+                        ShortcutManagerCompat.getDynamicShortcuts(this),
+                        si -> si.getId().equals(shortcutId));
+        if (shortcut.isPresent()) {
+            final var extras = shortcut.get().getExtras();
+            if (extras == null) {
+                return null;
+            } else {
+                return extras.getString(ConversationsActivity.EXTRA_CONVERSATION);
+            }
+        } else {
+            return null;
+        }
     }
 
     @Override
@@ -137,10 +163,18 @@ public class ShareWithActivity extends XmppActivity
     @Override
     public void onStart() {
         super.onStart();
-        Intent intent = getIntent();
+        final Intent intent = getIntent();
         if (intent == null) {
             return;
         }
+        populateShare(intent);
+        if (xmppConnectionServiceBound) {
+            xmppConnectionService.populateWithOrderedConversations(
+                    mConversations, this.share.uris.isEmpty(), false);
+        }
+    }
+
+    private void populateShare(final Intent intent) {
         final String type = intent.getType();
         final String action = intent.getAction();
         final Uri data = intent.getData();
@@ -217,8 +251,12 @@ public class ShareWithActivity extends XmppActivity
             mPendingConversation = conversation;
             return;
         }
+        share(conversation.getUuid());
+    }
+
+    private void share(final String conversation) {
         final Intent intent = new Intent(this, ConversationsActivity.class);
-        intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid());
+        intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation);
         if (!share.uris.isEmpty()) {
             intent.setAction(Intent.ACTION_SEND_MULTIPLE);
             intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, share.uris);
@@ -233,7 +271,7 @@ public class ShareWithActivity extends XmppActivity
         }
         try {
             startActivity(intent);
-        } catch (SecurityException e) {
+        } catch (final SecurityException e) {
             Toast.makeText(
                             this,
                             R.string.sharing_application_not_grant_permission,
  
  
  
    
    @@ -1084,7 +1084,7 @@ public class StartConversationActivity extends XmppActivity
         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
         if (grantResults.length > 0)
             if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-                ScanActivity.onRequestPermissionResult(this, requestCode, grantResults);
+                UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
                 if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
                     if (QuickConversationsService.isQuicksy()) {
                         setRefreshing(true);
  
  
  
    
    @@ -4,6 +4,7 @@ import android.telephony.TelephonyManager;
 
 import android.Manifest;
 import android.annotation.SuppressLint;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
 import android.content.ClipData;
@@ -35,6 +36,7 @@ import android.os.IBinder;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.preference.PreferenceManager;
+import android.provider.Settings;
 import android.text.Html;
 import android.text.InputType;
 import android.util.DisplayMetrics;
@@ -48,14 +50,15 @@ import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.ImageView;
 import android.widget.Toast;
-
 import androidx.annotation.BoolRes;
 import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
 import androidx.annotation.StringRes;
 import androidx.appcompat.app.AlertDialog;
 import androidx.appcompat.app.AppCompatDelegate;
+import androidx.core.content.pm.ShortcutInfoCompat;
+import androidx.core.content.pm.ShortcutManagerCompat;
 import androidx.databinding.DataBindingUtil;
-
 import com.google.android.material.color.MaterialColors;
 import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 import com.google.common.base.Strings;
@@ -86,6 +89,7 @@ import eu.siacs.conversations.entities.Presences;
 import eu.siacs.conversations.entities.Reaction;
 import eu.siacs.conversations.services.AvatarService;
 import eu.siacs.conversations.services.BarcodeProvider;
+import eu.siacs.conversations.services.NotificationService;
 import eu.siacs.conversations.services.QuickConversationsService;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
@@ -95,17 +99,14 @@ import eu.siacs.conversations.ui.util.SettingsUtils;
 import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
 import eu.siacs.conversations.utils.AccountUtils;
 import eu.siacs.conversations.utils.Compatibility;
-import eu.siacs.conversations.utils.ExceptionHelper;
 import eu.siacs.conversations.utils.SignupUtils;
 import eu.siacs.conversations.utils.ThemeHelper;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
 import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
-
 import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.RejectedExecutionException;
@@ -131,52 +132,60 @@ public abstract class XmppActivity extends ActionBarActivity {
     protected boolean mUsingEnterKey = false;
     protected boolean mUseTor = false;
     protected Toast mToast;
-    public Runnable onOpenPGPKeyPublished = () -> Toast.makeText(XmppActivity.this, R.string.openpgp_has_been_published, Toast.LENGTH_SHORT).show();
+    public Runnable onOpenPGPKeyPublished =
+            () ->
+                    Toast.makeText(
+                                    XmppActivity.this,
+                                    R.string.openpgp_has_been_published,
+                                    Toast.LENGTH_SHORT)
+                            .show();
     protected ConferenceInvite mPendingConferenceInvite = null;
     protected PriorityQueue<Pair<Integer, ValueCallback<Uri[]>>> activityCallbacks =
         Build.VERSION.SDK_INT >= 24 ? new PriorityQueue<>((x, y) -> y.first.compareTo(x.first)) : new PriorityQueue<>();
-    protected ServiceConnection mConnection = new ServiceConnection() {
+    protected ServiceConnection mConnection =
+            new ServiceConnection() {
 
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            XmppConnectionBinder binder = (XmppConnectionBinder) service;
-            xmppConnectionService = binder.getService();
-            xmppConnectionServiceBound = true;
-            registerListeners();
-            onBackendConnected();
-        }
+                @Override
+                public void onServiceConnected(ComponentName className, IBinder service) {
+                    XmppConnectionBinder binder = (XmppConnectionBinder) service;
+                    xmppConnectionService = binder.getService();
+                    xmppConnectionServiceBound = true;
+                    registerListeners();
+                    onBackendConnected();
+                }
 
-        @Override
-        public void onServiceDisconnected(ComponentName arg0) {
-            xmppConnectionServiceBound = false;
-        }
-    };
+                @Override
+                public void onServiceDisconnected(ComponentName arg0) {
+                    xmppConnectionServiceBound = false;
+                }
+            };
     private DisplayMetrics metrics;
     private long mLastUiRefresh = 0;
     private final Handler mRefreshUiHandler = new Handler();
-    private final Runnable mRefreshUiRunnable = () -> {
-        mLastUiRefresh = SystemClock.elapsedRealtime();
-        refreshUiReal();
-    };
-    private final UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
-        @Override
-        public void success(final Conversation conversation) {
-            runOnUiThread(() -> {
-                switchToConversation(conversation);
-                hideToast();
-            });
-        }
-
-        @Override
-        public void error(final int errorCode, Conversation object) {
-            runOnUiThread(() -> replaceToast(getString(errorCode)));
-        }
+    private final Runnable mRefreshUiRunnable =
+            () -> {
+                mLastUiRefresh = SystemClock.elapsedRealtime();
+                refreshUiReal();
+            };
+    private final UiCallback<Conversation> adhocCallback =
+            new UiCallback<Conversation>() {
+                @Override
+                public void success(final Conversation conversation) {
+                    runOnUiThread(
+                            () -> {
+                                switchToConversation(conversation);
+                                hideToast();
+                            });
+                }
 
-        @Override
-        public void userInputRequired(PendingIntent pi, Conversation object) {
+                @Override
+                public void error(final int errorCode, Conversation object) {
+                    runOnUiThread(() -> replaceToast(getString(errorCode)));
+                }
 
-        }
-    };
+                @Override
+                public void userInputRequired(PendingIntent pi, Conversation object) {}
+            };
 
     public static boolean cancelPotentialWork(Message message, ImageView imageView) {
         final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
@@ -233,7 +242,7 @@ public abstract class XmppActivity extends ActionBarActivity {
         }
     }
 
-    abstract protected void refreshUiReal();
+    protected abstract void refreshUiReal();
 
     @Override
     public void onStart() {
@@ -272,6 +281,31 @@ public abstract class XmppActivity extends ActionBarActivity {
         }
     }
 
+    @RequiresApi(api = Build.VERSION_CODES.R)
+    protected void configureCustomNotification(final ShortcutInfoCompat shortcut) {
+        final var notificationManager = getSystemService(NotificationManager.class);
+        final var channel =
+                notificationManager.getNotificationChannel(
+                        NotificationService.MESSAGES_NOTIFICATION_CHANNEL, shortcut.getId());
+        if (channel != null && channel.getConversationId() != null) {
+            ShortcutManagerCompat.pushDynamicShortcut(this, shortcut);
+            openNotificationSettings(shortcut);
+        } else {
+            NotificationService.createConversationChannel(this, shortcut);
+            ShortcutManagerCompat.pushDynamicShortcut(this, shortcut);
+            openNotificationSettings(shortcut);
+        }
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.R)
+    protected void openNotificationSettings(final ShortcutInfoCompat shortcut) {
+        final var intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
+        intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
+        intent.putExtra(
+                Settings.EXTRA_CHANNEL_ID, NotificationService.MESSAGES_NOTIFICATION_CHANNEL);
+        intent.putExtra(Settings.EXTRA_CONVERSATION_ID, shortcut.getId());
+        startActivity(intent);
+    }
 
     public boolean hasPgp() {
         return xmppConnectionService.getPgpEngine() != null;
@@ -281,16 +315,20 @@ public abstract class XmppActivity extends ActionBarActivity {
         final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
         builder.setTitle(getString(R.string.openkeychain_required));
         builder.setIconAttribute(android.R.attr.alertDialogIcon);
-        builder.setMessage(Html.fromHtml(getString(R.string.openkeychain_required_long, getString(R.string.app_name))));
+        builder.setMessage(
+                Html.fromHtml(
+                        getString(
+                                R.string.openkeychain_required_long,
+                                getString(R.string.app_name))));
         builder.setNegativeButton(getString(R.string.cancel), null);
-        builder.setNeutralButton(getString(R.string.restart),
+        builder.setNeutralButton(
+                getString(R.string.restart),
                 (dialog, which) -> {
                     if (xmppConnectionServiceBound) {
                         unbindService(mConnection);
                         xmppConnectionServiceBound = false;
                     }
-                    stopService(new Intent(XmppActivity.this,
-                            XmppConnectionService.class));
+                    stopService(new Intent(XmppActivity.this, XmppConnectionService.class));
                     finish();
                 });
         builder.setPositiveButton(
@@ -350,6 +388,14 @@ public abstract class XmppActivity extends ActionBarActivity {
                         dialog.dismiss();
                     });
         }
+        viewBinding.more.setOnClickListener(
+                v -> {
+                    dialog.dismiss();
+                    final var intent = new Intent(this, AddReactionActivity.class);
+                    intent.putExtra("conversation", message.getConversation().getUuid());
+                    intent.putExtra("message", message.getUuid());
+                    startActivity(intent);
+                });
         dialog.show();
     }
 
@@ -360,58 +406,89 @@ public abstract class XmppActivity extends ActionBarActivity {
     protected void deleteAccount(final Account account, final Runnable postDelete) {
         final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
         final View dialogView = getLayoutInflater().inflate(R.layout.dialog_delete_account, null);
-        final CheckBox deleteFromServer =
-                dialogView.findViewById(R.id.delete_from_server);
+        final CheckBox deleteFromServer = dialogView.findViewById(R.id.delete_from_server);
         builder.setView(dialogView);
         builder.setTitle(R.string.mgmt_account_delete);
-        builder.setPositiveButton(getString(R.string.delete),null);
+        builder.setPositiveButton(getString(R.string.delete), null);
         builder.setNegativeButton(getString(R.string.cancel), null);
         final AlertDialog dialog = builder.create();
-        dialog.setOnShowListener(dialogInterface->{
-            final Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
-            button.setOnClickListener(v -> {
-                final boolean unregister = deleteFromServer.isChecked();
-                if (unregister) {
-                    if (account.isOnlineAndConnected()) {
-                        deleteFromServer.setEnabled(false);
-                        button.setText(R.string.please_wait);
-                        button.setEnabled(false);
-                        xmppConnectionService.unregisterAccount(account, result -> {
-                            runOnUiThread(()->{
-                                if (result) {
-                                    dialog.dismiss();
-                                    if (postDelete != null) {
-                                        postDelete.run();
+        dialog.setOnShowListener(
+                dialogInterface -> {
+                    final Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+                    button.setOnClickListener(
+                            v -> {
+                                final boolean unregister = deleteFromServer.isChecked();
+                                if (unregister) {
+                                    if (account.isOnlineAndConnected()) {
+                                        deleteFromServer.setEnabled(false);
+                                        button.setText(R.string.please_wait);
+                                        button.setEnabled(false);
+                                        xmppConnectionService.unregisterAccount(
+                                                account,
+                                                result -> {
+                                                    runOnUiThread(
+                                                            () -> {
+                                                                if (result) {
+                                                                    dialog.dismiss();
+                                                                    if (postDelete != null) {
+                                                                        postDelete.run();
+                                                                    }
+                                                                    if (xmppConnectionService
+                                                                                            .getAccounts()
+                                                                                            .size()
+                                                                                    == 0
+                                                                            && Config
+                                                                                            .MAGIC_CREATE_DOMAIN
+                                                                                    != null) {
+                                                                        final Intent intent =
+                                                                                SignupUtils
+                                                                                        .getSignUpIntent(
+                                                                                                this);
+                                                                        intent.setFlags(
+                                                                                Intent
+                                                                                                .FLAG_ACTIVITY_NEW_TASK
+                                                                                        | Intent
+                                                                                                .FLAG_ACTIVITY_CLEAR_TASK);
+                                                                        startActivity(intent);
+                                                                    }
+                                                                } else {
+                                                                    deleteFromServer.setEnabled(
+                                                                            true);
+                                                                    button.setText(R.string.delete);
+                                                                    button.setEnabled(true);
+                                                                    Toast.makeText(
+                                                                                    this,
+                                                                                    R.string
+                                                                                            .could_not_delete_account_from_server,
+                                                                                    Toast
+                                                                                            .LENGTH_LONG)
+                                                                            .show();
+                                                                }
+                                                            });
+                                                });
+                                    } else {
+                                        Toast.makeText(
+                                                        this,
+                                                        R.string.not_connected_try_again,
+                                                        Toast.LENGTH_LONG)
+                                                .show();
                                     }
-                                    if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) {
+                                } else {
+                                    xmppConnectionService.deleteAccount(account);
+                                    dialog.dismiss();
+                                    if (xmppConnectionService.getAccounts().size() == 0
+                                            && Config.MAGIC_CREATE_DOMAIN != null) {
                                         final Intent intent = SignupUtils.getSignUpIntent(this);
-                                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+                                        intent.setFlags(
+                                                Intent.FLAG_ACTIVITY_NEW_TASK
+                                                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                                         startActivity(intent);
+                                    } else if (postDelete != null) {
+                                        postDelete.run();
                                     }
-                                } else {
-                                    deleteFromServer.setEnabled(true);
-                                    button.setText(R.string.delete);
-                                    button.setEnabled(true);
-                                    Toast.makeText(this,R.string.could_not_delete_account_from_server,Toast.LENGTH_LONG).show();
                                 }
                             });
-                        });
-                    } else {
-                        Toast.makeText(this,R.string.not_connected_try_again,Toast.LENGTH_LONG).show();
-                    }
-                } else {
-                    xmppConnectionService.deleteAccount(account);
-                    dialog.dismiss();
-                    if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) {
-                        final Intent intent = SignupUtils.getSignUpIntent(this);
-                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-                        startActivity(intent);
-                    } else if (postDelete != null) {
-                        postDelete.run();
-                    }
-                }
-            });
-        });
+                });
         dialog.show();
     }
 
@@ -419,61 +496,75 @@ public abstract class XmppActivity extends ActionBarActivity {
 
     protected void registerListeners() {
         if (this instanceof XmppConnectionService.OnConversationUpdate) {
-            this.xmppConnectionService.setOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this);
+            this.xmppConnectionService.setOnConversationListChangedListener(
+                    (XmppConnectionService.OnConversationUpdate) this);
         }
         if (this instanceof XmppConnectionService.OnAccountUpdate) {
-            this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this);
+            this.xmppConnectionService.setOnAccountListChangedListener(
+                    (XmppConnectionService.OnAccountUpdate) this);
         }
         if (this instanceof XmppConnectionService.OnCaptchaRequested) {
-            this.xmppConnectionService.setOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this);
+            this.xmppConnectionService.setOnCaptchaRequestedListener(
+                    (XmppConnectionService.OnCaptchaRequested) this);
         }
         if (this instanceof XmppConnectionService.OnRosterUpdate) {
-            this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this);
+            this.xmppConnectionService.setOnRosterUpdateListener(
+                    (XmppConnectionService.OnRosterUpdate) this);
         }
         if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
-            this.xmppConnectionService.setOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this);
+            this.xmppConnectionService.setOnMucRosterUpdateListener(
+                    (XmppConnectionService.OnMucRosterUpdate) this);
         }
         if (this instanceof OnUpdateBlocklist) {
             this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this);
         }
         if (this instanceof XmppConnectionService.OnShowErrorToast) {
-            this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this);
+            this.xmppConnectionService.setOnShowErrorToastListener(
+                    (XmppConnectionService.OnShowErrorToast) this);
         }
         if (this instanceof OnKeyStatusUpdated) {
             this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this);
         }
         if (this instanceof XmppConnectionService.OnJingleRtpConnectionUpdate) {
-            this.xmppConnectionService.setOnRtpConnectionUpdateListener((XmppConnectionService.OnJingleRtpConnectionUpdate) this);
+            this.xmppConnectionService.setOnRtpConnectionUpdateListener(
+                    (XmppConnectionService.OnJingleRtpConnectionUpdate) this);
         }
     }
 
     protected void unregisterListeners() {
         if (this instanceof XmppConnectionService.OnConversationUpdate) {
-            this.xmppConnectionService.removeOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this);
+            this.xmppConnectionService.removeOnConversationListChangedListener(
+                    (XmppConnectionService.OnConversationUpdate) this);
         }
         if (this instanceof XmppConnectionService.OnAccountUpdate) {
-            this.xmppConnectionService.removeOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this);
+            this.xmppConnectionService.removeOnAccountListChangedListener(
+                    (XmppConnectionService.OnAccountUpdate) this);
         }
         if (this instanceof XmppConnectionService.OnCaptchaRequested) {
-            this.xmppConnectionService.removeOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this);
+            this.xmppConnectionService.removeOnCaptchaRequestedListener(
+                    (XmppConnectionService.OnCaptchaRequested) this);
         }
         if (this instanceof XmppConnectionService.OnRosterUpdate) {
-            this.xmppConnectionService.removeOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this);
+            this.xmppConnectionService.removeOnRosterUpdateListener(
+                    (XmppConnectionService.OnRosterUpdate) this);
         }
         if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
-            this.xmppConnectionService.removeOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this);
+            this.xmppConnectionService.removeOnMucRosterUpdateListener(
+                    (XmppConnectionService.OnMucRosterUpdate) this);
         }
         if (this instanceof OnUpdateBlocklist) {
             this.xmppConnectionService.removeOnUpdateBlocklistListener((OnUpdateBlocklist) this);
         }
         if (this instanceof XmppConnectionService.OnShowErrorToast) {
-            this.xmppConnectionService.removeOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this);
+            this.xmppConnectionService.removeOnShowErrorToastListener(
+                    (XmppConnectionService.OnShowErrorToast) this);
         }
         if (this instanceof OnKeyStatusUpdated) {
             this.xmppConnectionService.removeOnNewKeysAvailableListener((OnKeyStatusUpdated) this);
         }
         if (this instanceof XmppConnectionService.OnJingleRtpConnectionUpdate) {
-            this.xmppConnectionService.removeRtpConnectionUpdateListener((XmppConnectionService.OnJingleRtpConnectionUpdate) this);
+            this.xmppConnectionService.removeRtpConnectionUpdateListener(
+                    (XmppConnectionService.OnJingleRtpConnectionUpdate) this);
         }
     }
 
@@ -481,7 +572,9 @@ public abstract class XmppActivity extends ActionBarActivity {
     public boolean onOptionsItemSelected(final MenuItem item) {
         switch (item.getItemId()) {
             case R.id.action_settings:
-                startActivity(new Intent(this, eu.siacs.conversations.ui.activity.SettingsActivity.class));
+                startActivity(
+                        new Intent(
+                                this, eu.siacs.conversations.ui.activity.SettingsActivity.class));
                 break;
             case R.id.action_privacy_policy:
                 openPrivacyPolicy();
@@ -516,7 +609,8 @@ public abstract class XmppActivity extends ActionBarActivity {
         }
     }
 
-    public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) {
+    public void selectPresence(
+            final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) {
         final Contact contact = conversation.getContact();
         if (contact.showInRoster() || contact.isSelf()) {
             final Presences presences = contact.getPresences();
@@ -537,7 +631,8 @@ public abstract class XmppActivity extends ActionBarActivity {
                 }
             } else if (presences.size() == 1) {
                 final String presence = presences.toResourceArray()[0];
-                conversation.setNextCounterpart(PresenceSelector.getNextCounterpart(contact, presence));
+                conversation.setNextCounterpart(
+                        PresenceSelector.getNextCounterpart(contact, presence));
                 listener.onPresenceSelected();
             } else {
                 PresenceSelector.showPresenceSelectionDialog(this, conversation, listener);
@@ -552,7 +647,8 @@ public abstract class XmppActivity extends ActionBarActivity {
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         metrics = getResources().getDisplayMetrics();
-        this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
+        this.isCameraFeatureAvailable =
+                getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
         this.mCustomColors = ThemeHelper.applyCustomColors(this);
     }
 
@@ -563,14 +659,16 @@ public abstract class XmppActivity extends ActionBarActivity {
     protected boolean isOptimizingBattery() {
         final PowerManager pm = getSystemService(PowerManager.class);
         return !pm.isIgnoringBatteryOptimizations(getPackageName());
-}
+    }
 
     protected boolean isAffectedByDataSaver() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            final ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+            final ConnectivityManager cm =
+                    (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
             return cm != null
                     && cm.isActiveNetworkMetered()
-                    && Compatibility.getRestrictBackgroundStatus(cm) == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+                    && Compatibility.getRestrictBackgroundStatus(cm)
+                            == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
         } else {
             return false;
         }
@@ -647,9 +745,16 @@ public abstract class XmppActivity extends ActionBarActivity {
         switchToConversation(conversation, text, asQuote, nick, pm, doNotAppend, postInit, null);
     }
 
-    public void switchToConversation(Conversation conversation, String text, boolean asQuote, String nick, boolean pm, boolean doNotAppend, String postInit, String thread) {
+    public void switchToConversation(
+            Conversation conversation,
+            String text,
+            boolean asQuote,
+            String nick,
+            boolean pm,
+            boolean doNotAppend,
+            String postInit,
+            String thread) {
         if (conversation == null) return;
-
         Intent intent = new Intent(this, ConversationsActivity.class);
         intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
         intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid());
@@ -699,7 +804,10 @@ public abstract class XmppActivity extends ActionBarActivity {
         intent.putExtra("jid", account.getJid().asBareJid().toEscapedString());
         intent.putExtra("init", init);
         if (init) {
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
+            intent.setFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK
+                            | Intent.FLAG_ACTIVITY_CLEAR_TASK
+                            | Intent.FLAG_ACTIVITY_NO_ANIMATION);
         }
         if (fingerprint != null) {
             intent.putExtra("fingerprint", fingerprint);
@@ -723,85 +831,113 @@ public abstract class XmppActivity extends ActionBarActivity {
     }
 
     protected void inviteToConversation(Conversation conversation) {
-        startActivityForResult(ChooseContactActivity.create(this, conversation), REQUEST_INVITE_TO_CONVERSATION);
+        startActivityForResult(
+                ChooseContactActivity.create(this, conversation), REQUEST_INVITE_TO_CONVERSATION);
     }
 
-    protected void announcePgp(final Account account, final Conversation conversation, Intent intent, final Runnable onSuccess) {
+    protected void announcePgp(
+            final Account account,
+            final Conversation conversation,
+            Intent intent,
+            final Runnable onSuccess) {
         if (account.getPgpId() == 0) {
             choosePgpSignId(account);
         } else {
             final String status = Strings.nullToEmpty(account.getPresenceStatusMessage());
-            xmppConnectionService.getPgpEngine().generateSignature(intent, account, status, new UiCallback<String>() {
-
-                @Override
-                public void userInputRequired(final PendingIntent pi, final String signature) {
-                    try {
-                        startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0,Compatibility.pgpStartIntentSenderOptions());
-                    } catch (final SendIntentException ignored) {
-                    }
-                }
+            xmppConnectionService
+                    .getPgpEngine()
+                    .generateSignature(
+                            intent,
+                            account,
+                            status,
+                            new UiCallback<String>() {
+
+                                @Override
+                                public void userInputRequired(
+                                        final PendingIntent pi, final String signature) {
+                                    try {
+                                        startIntentSenderForResult(
+                                                pi.getIntentSender(),
+                                                REQUEST_ANNOUNCE_PGP,
+                                                null,
+                                                0,
+                                                0,
+                                                0,
+                                                Compatibility.pgpStartIntentSenderOptions());
+                                    } catch (final SendIntentException ignored) {
+                                    }
+                                }
 
-                @Override
-                public void success(String signature) {
-                    account.setPgpSignature(signature);
-                    xmppConnectionService.databaseBackend.updateAccount(account);
-                    xmppConnectionService.sendPresence(account);
-                    if (conversation != null) {
-                        conversation.setNextEncryption(Message.ENCRYPTION_PGP);
-                        xmppConnectionService.updateConversation(conversation);
-                        refreshUi();
-                    }
-                    if (onSuccess != null) {
-                        runOnUiThread(onSuccess);
-                    }
-                }
+                                @Override
+                                public void success(String signature) {
+                                    account.setPgpSignature(signature);
+                                    xmppConnectionService.databaseBackend.updateAccount(account);
+                                    xmppConnectionService.sendPresence(account);
+                                    if (conversation != null) {
+                                        conversation.setNextEncryption(Message.ENCRYPTION_PGP);
+                                        xmppConnectionService.updateConversation(conversation);
+                                        refreshUi();
+                                    }
+                                    if (onSuccess != null) {
+                                        runOnUiThread(onSuccess);
+                                    }
+                                }
 
-                @Override
-                public void error(int error, String signature) {
-                    if (error == 0) {
-                        account.setPgpSignId(0);
-                        account.unsetPgpSignature();
-                        xmppConnectionService.databaseBackend.updateAccount(account);
-                        choosePgpSignId(account);
-                    } else {
-                        displayErrorDialog(error);
-                    }
-                }
-            });
+                                @Override
+                                public void error(int error, String signature) {
+                                    if (error == 0) {
+                                        account.setPgpSignId(0);
+                                        account.unsetPgpSignature();
+                                        xmppConnectionService.databaseBackend.updateAccount(
+                                                account);
+                                        choosePgpSignId(account);
+                                    } else {
+                                        displayErrorDialog(error);
+                                    }
+                                }
+                            });
         }
     }
 
     protected void choosePgpSignId(final Account account) {
-        xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback<>() {
-            @Override
-            public void success(final Account a) {
-            }
-
-            @Override
-            public void error(int errorCode, Account object) {
-
-            }
-
-            @Override
-            public void userInputRequired(PendingIntent pi, Account object) {
-                try {
-                    startIntentSenderForResult(pi.getIntentSender(),
-                            REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0, Compatibility.pgpStartIntentSenderOptions());
-                } catch (final SendIntentException ignored) {
-                }
-            }
-        });
+        xmppConnectionService
+                .getPgpEngine()
+                .chooseKey(
+                        account,
+                        new UiCallback<>() {
+                            @Override
+                            public void success(final Account a) {}
+
+                            @Override
+                            public void error(int errorCode, Account object) {}
+
+                            @Override
+                            public void userInputRequired(PendingIntent pi, Account object) {
+                                try {
+                                    startIntentSenderForResult(
+                                            pi.getIntentSender(),
+                                            REQUEST_CHOOSE_PGP_ID,
+                                            null,
+                                            0,
+                                            0,
+                                            0,
+                                            Compatibility.pgpStartIntentSenderOptions());
+                                } catch (final SendIntentException ignored) {
+                                }
+                            }
+                        });
     }
 
     protected void displayErrorDialog(final int errorCode) {
-        runOnUiThread(() -> {
-            final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(XmppActivity.this);
-            builder.setTitle(getString(R.string.error));
-            builder.setMessage(errorCode);
-            builder.setNeutralButton(R.string.accept, null);
-            builder.create().show();
-        });
-
+        runOnUiThread(
+                () -> {
+                    final MaterialAlertDialogBuilder builder =
+                            new MaterialAlertDialogBuilder(XmppActivity.this);
+                    builder.setTitle(getString(R.string.error));
+                    builder.setMessage(errorCode);
+                    builder.setNeutralButton(R.string.accept, null);
+                    builder.create().show();
+                });
     }
 
     protected void showAddToRosterDialog(final Contact contact) {
@@ -821,13 +957,15 @@ public abstract class XmppActivity extends ActionBarActivity {
         builder.setTitle(contact.getJid().toString());
         builder.setMessage(R.string.request_presence_updates);
         builder.setNegativeButton(R.string.cancel, null);
-        builder.setPositiveButton(R.string.request_now,
+        builder.setPositiveButton(
+                R.string.request_now,
                 (dialog, which) -> {
                     if (xmppConnectionServiceBound) {
-                        xmppConnectionService.sendPresencePacket(contact
-                                .getAccount(), xmppConnectionService
-                                .getPresenceGenerator()
-                                .requestPresenceUpdatesFrom(contact));
+                        xmppConnectionService.sendPresencePacket(
+                                contact.getAccount(),
+                                xmppConnectionService
+                                        .getPresenceGenerator()
+                                        .requestPresenceUpdatesFrom(contact));
                     }
                 });
         builder.create().show();
@@ -837,7 +975,11 @@ public abstract class XmppActivity extends ActionBarActivity {
         quickEdit(previousValue, callback, hint, false, false);
     }
 
-    protected void quickEdit(String previousValue, @StringRes int hint, OnValueEdited callback, boolean permitEmpty) {
+    protected void quickEdit(
+            String previousValue,
+            @StringRes int hint,
+            OnValueEdited callback,
+            boolean permitEmpty) {
         quickEdit(previousValue, callback, hint, false, permitEmpty);
     }
 
@@ -862,9 +1004,12 @@ public abstract class XmppActivity extends ActionBarActivity {
                            boolean alwaysCallback,
                            boolean startSelected) {
         final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
-        final DialogQuickeditBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.dialog_quickedit, null, false);
+        final DialogQuickeditBinding binding =
+                DataBindingUtil.inflate(
+                        getLayoutInflater(), R.layout.dialog_quickedit, null, false);
         if (password) {
-            binding.inputEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+            binding.inputEditText.setInputType(
+                    InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
         }
         builder.setPositiveButton(R.string.accept, null);
         if (hint != 0) {
@@ -882,33 +1027,39 @@ public abstract class XmppActivity extends ActionBarActivity {
         if (startSelected) {
             binding.inputEditText.selectAll();
         }
-        View.OnClickListener clickListener = v -> {
-            String value = binding.inputEditText.getText().toString();
-            if ((alwaysCallback || !value.equals(previousValue)) && (!value.trim().isEmpty() || permitEmpty)) {
-                String error = callback.onValueEdited(value);
-                if (error != null) {
-                    binding.inputLayout.setError(error);
-                    return;
-                }
-            }
-            SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText);
-            dialog.dismiss();
-        };
+        View.OnClickListener clickListener =
+                v -> {
+                    String value = binding.inputEditText.getText().toString();
+                    if ((alwaysCallback || !value.equals(previousValue)) && (!value.trim().isEmpty() || permitEmpty)) {
+                        String error = callback.onValueEdited(value);
+                        if (error != null) {
+                            binding.inputLayout.setError(error);
+                            return;
+                        }
+                    }
+                    SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText);
+                    dialog.dismiss();
+                };
         dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(clickListener);
-        dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener((v -> {
-            SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText);
-            dialog.dismiss();
-        }));
+        dialog.getButton(DialogInterface.BUTTON_NEGATIVE)
+                .setOnClickListener(
+                        (v -> {
+                            SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText);
+                            dialog.dismiss();
+                        }));
         dialog.setCanceledOnTouchOutside(false);
-        dialog.setOnDismissListener(dialog1 -> {
-            SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText);
-        });
+        dialog.setOnDismissListener(
+                dialog1 -> {
+                    SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText);
+                });
     }
 
     protected boolean hasStoragePermission(int requestCode) {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
-            if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
-                requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
+            if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                requestPermissions(
+                        new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
                 return false;
             } else {
                 return true;
@@ -966,7 +1117,8 @@ public abstract class XmppActivity extends ActionBarActivity {
     }
 
     protected boolean manuallyChangePresence() {
-        return getBooleanPreference(AppSettings.MANUALLY_CHANGE_PRESENCE, R.bool.manually_change_presence);
+        return getBooleanPreference(
+                AppSettings.MANUALLY_CHANGE_PRESENCE, R.bool.manually_change_presence);
     }
 
     protected String getShareableUri() {
@@ -996,16 +1148,21 @@ public abstract class XmppActivity extends ActionBarActivity {
         PgpEngine pgp = XmppActivity.this.xmppConnectionService.getPgpEngine();
         try {
             startIntentSenderForResult(
-                    pgp.getIntentForKey(keyId).getIntentSender(), 0, null, 0,
-                    0, 0, Compatibility.pgpStartIntentSenderOptions());
+                    pgp.getIntentForKey(keyId).getIntentSender(),
+                    0,
+                    null,
+                    0,
+                    0,
+                    0,
+                    Compatibility.pgpStartIntentSenderOptions());
         } catch (final Throwable e) {
-            Log.d(Config.LOGTAG,"could not launch OpenKeyChain", e);
+            Log.d(Config.LOGTAG, "could not launch OpenKeyChain", e);
             Toast.makeText(XmppActivity.this, R.string.openpgp_error, Toast.LENGTH_SHORT).show();
         }
     }
 
     @Override
-    protected void onResume(){
+    protected void onResume() {
         super.onResume();
         SettingsUtils.applyScreenshotSetting(this);
     }
@@ -1058,11 +1215,27 @@ public abstract class XmppActivity extends ActionBarActivity {
         final int black;
         final int white;
         if (Activities.isNightMode(this)) {
-            black = MaterialColors.getColor(this, com.google.android.material.R.attr.colorSurfaceContainerHighest,"No surface color configured");
-            white = MaterialColors.getColor(this, com.google.android.material.R.attr.colorSurfaceInverse,"No inverse surface color configured");
+            black =
+                    MaterialColors.getColor(
+                            this,
+                            com.google.android.material.R.attr.colorSurfaceContainerHighest,
+                            "No surface color configured");
+            white =
+                    MaterialColors.getColor(
+                            this,
+                            com.google.android.material.R.attr.colorSurfaceInverse,
+                            "No inverse surface color configured");
         } else {
-            black = MaterialColors.getColor(this, com.google.android.material.R.attr.colorSurfaceInverse,"No inverse surface color configured");
-            white = MaterialColors.getColor(this, com.google.android.material.R.attr.colorSurfaceContainerHighest,"No surface color configured");
+            black =
+                    MaterialColors.getColor(
+                            this,
+                            com.google.android.material.R.attr.colorSurfaceInverse,
+                            "No inverse surface color configured");
+            white =
+                    MaterialColors.getColor(
+                            this,
+                            com.google.android.material.R.attr.colorSurfaceContainerHighest,
+                            "No surface color configured");
         }
         final var bitmap = BarcodeProvider.create2dBarcodeBitmap(uri, width, black, white);
         final ImageView view = new ImageView(this);
@@ -1089,7 +1262,10 @@ public abstract class XmppActivity extends ActionBarActivity {
     public void loadBitmap(Message message, ImageView imageView) {
         Drawable bm;
         try {
-            bm = xmppConnectionService.getFileBackend().getThumbnail(message, getResources(), (int) (metrics.density * 288), true);
+            bm =
+                    xmppConnectionService
+                            .getFileBackend()
+                            .getThumbnail(message, getResources(), (int) (metrics.density * 288), true);
         } catch (IOException e) {
             bm = null;
         }
@@ -1148,7 +1324,8 @@ public abstract class XmppActivity extends ActionBarActivity {
                 return false;
             } else {
                 jids.add(conversation.getJid().asBareJid());
-                return service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback);
+                return service.createAdhocConference(
+                        conversation.getAccount(), null, jids, activity.adhocCallback);
             }
         }
     }
  
  
  
    
    @@ -189,15 +189,22 @@ public class ConversationAdapter
             if (status == Message.STATUS_RECEIVED) {
                 if (conversation.getMode() == Conversation.MODE_MULTI) {
                     viewHolder.binding.senderName.setVisibility(View.VISIBLE);
-                    final String dname = UIHelper.getMessageDisplayName(message);
-                    final String[] words = dname.split("\\s+");
-                    viewHolder.binding.senderName.setText((words.length > 0 ? words[0] : dname) + ':');
+                    final var displayName = UIHelper.getMessageDisplayName(message);
+                    final var displayNameParts = displayName.split("\\s+");
+                    // Skip when nickname only consists of blank chars
+                    if (displayNameParts.length == 0) {
+                        viewHolder.binding.senderName.setText(String.format("%s:", displayName));
+                    } else {
+                        viewHolder.binding.senderName.setText(
+                                String.format("%s:", displayNameParts[0]));
+                    }
                 } else {
                     viewHolder.binding.senderName.setVisibility(View.GONE);
                 }
             } else if (message.getType() != Message.TYPE_STATUS) {
                 viewHolder.binding.senderName.setVisibility(View.VISIBLE);
-                viewHolder.binding.senderName.setText(activity.getString(R.string.me) + ':');
+                viewHolder.binding.senderName.setText(
+                        String.format("%s:", activity.getString(R.string.me)));
             } else {
                 viewHolder.binding.senderName.setVisibility(View.GONE);
             }
  
  
  
    
    @@ -35,7 +35,6 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.widget.ArrayAdapter;
-import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ListAdapter;
@@ -43,12 +42,12 @@ import android.widget.ListView;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 import android.widget.Toast;
-
 import androidx.annotation.AttrRes;
 import androidx.annotation.ColorInt;
 import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.core.app.ActivityCompat;
 import androidx.core.content.ContextCompat;
 import androidx.core.content.res.ResourcesCompat;
@@ -77,6 +76,7 @@ import com.google.android.material.color.MaterialColors;
 import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
+import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
@@ -106,6 +106,11 @@ import eu.siacs.conversations.R;
 import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
 import eu.siacs.conversations.databinding.LinkDescriptionBinding;
 import eu.siacs.conversations.databinding.DialogAddReactionBinding;
+import eu.siacs.conversations.databinding.ItemMessageDateBubbleBinding;
+import eu.siacs.conversations.databinding.ItemMessageEndBinding;
+import eu.siacs.conversations.databinding.ItemMessageRtpSessionBinding;
+import eu.siacs.conversations.databinding.ItemMessageStartBinding;
+import eu.siacs.conversations.databinding.ItemMessageStatusBinding;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
@@ -153,15 +158,14 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
-import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public class MessageAdapter extends ArrayAdapter<Message> {
 
     public static final String DATE_SEPARATOR_BODY = "DATE_SEPARATOR";
-    private static final int SENT = 0;
-    private static final int RECEIVED = 1;
+    private static final int END = 0;
+    private static final int START = 1;
     private static final int STATUS = 2;
     private static final int DATE_SEPARATOR = 3;
     private static final int RTP_SESSION = 4;
@@ -175,8 +179,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
     private OnContactPictureClicked mOnMessageBoxSwipedListener;
     private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
     private OnInlineImageLongClicked mOnInlineImageLongClickedListener;
-    private boolean mUseGreenBackground = false;
-    private BubbleDesign bubbleDesign = new BubbleDesign(false, false);
+    private BubbleDesign bubbleDesign = new BubbleDesign(false, false, false, true);
     private final boolean mForceNames;
     private final Map<String, WebxdcUpdate> lastWebxdcUpdate = new HashMap<>();
     private String selectionUuid = null;
@@ -256,7 +259,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
         return 5;
     }
 
-    private int getItemViewType(Message message) {
+    private static int getItemViewType(final Message message, final boolean alignStart) {
         if (message.getType() == Message.TYPE_STATUS) {
             if (DATE_SEPARATOR_BODY.equals(message.getBody())) {
                 return DATE_SEPARATOR;
@@ -265,32 +268,32 @@ public class MessageAdapter extends ArrayAdapter<Message> {
             }
         } else if (message.getType() == Message.TYPE_RTP_SESSION) {
             return RTP_SESSION;
-        } else if (message.getStatus() <= Message.STATUS_RECEIVED) {
-            return RECEIVED;
+        } else if (message.getStatus() <= Message.STATUS_RECEIVED || alignStart) {
+            return START;
         } else {
-            return SENT;
+            return END;
         }
     }
 
     @Override
-    public int getItemViewType(int position) {
-        return this.getItemViewType(getItem(position));
+    public int getItemViewType(final int position) {
+        return getItemViewType(getItem(position), bubbleDesign.alignStart);
     }
 
     private void displayStatus(
-            final ViewHolder viewHolder,
+            final BubbleMessageItemViewHolder viewHolder,
             final Message message,
-            final int type,
             final BubbleColor bubbleColor) {
-        final int mergedStatus = message.getMergedStatus();
+        final int status = message.getStatus();
         final boolean error;
-        if (viewHolder.indicatorReceived != null) {
-            viewHolder.indicatorReceived.setVisibility(View.GONE);
-        }
         final Transferable transferable = message.getTransferable();
         final boolean multiReceived =
                 message.getConversation().getMode() == Conversation.MODE_MULTI
-                        && mergedStatus <= Message.STATUS_RECEIVED;
+                        && message.getStatus() <= Message.STATUS_RECEIVED;
+        final boolean sent = status != Message.STATUS_RECEIVED;
+        final boolean showUserNickname =
+                message.getConversation().getMode() == Conversation.MODE_MULTI
+                        && viewHolder instanceof StartBubbleMessageItemViewHolder;
         final String fileSize;
         if (message.isFileOrImage()
                 || transferable != null
@@ -310,106 +313,101 @@ public class MessageAdapter extends ArrayAdapter<Message> {
             fileSize = null;
             error = message.getStatus() == Message.STATUS_SEND_FAILED;
         }
-        if (type == SENT && viewHolder.indicatorReceived != null) {
+
+        if (sent) {
             final @DrawableRes Integer receivedIndicator =
-                    getMessageStatusAsDrawable(message, mergedStatus);
+                    getMessageStatusAsDrawable(message, status);
             if (receivedIndicator == null) {
-                viewHolder.indicatorReceived.setVisibility(View.INVISIBLE);
+                viewHolder.indicatorReceived().setVisibility(View.INVISIBLE);
             } else {
-                viewHolder.indicatorReceived.setImageResource(receivedIndicator);
-                if (mergedStatus == Message.STATUS_SEND_FAILED) {
-                    setImageTintError(viewHolder.indicatorReceived);
+                viewHolder.indicatorReceived().setImageResource(receivedIndicator);
+                if (status == Message.STATUS_SEND_FAILED) {
+                    setImageTintError(viewHolder.indicatorReceived());
                 } else {
-                    setImageTint(viewHolder.indicatorReceived, bubbleColor);
+                    setImageTint(viewHolder.indicatorReceived(), bubbleColor);
                 }
-                viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
+                viewHolder.indicatorReceived().setVisibility(View.VISIBLE);
             }
+        } else {
+            viewHolder.indicatorReceived().setVisibility(View.GONE);
         }
-        final var additionalStatusInfo = getAdditionalStatusInfo(message, mergedStatus);
-
-        if (error && type == SENT) {
-            viewHolder.time.setTextColor(
-                    MaterialColors.getColor(
-                            viewHolder.time, com.google.android.material.R.attr.colorError));
+        final var additionalStatusInfo = getAdditionalStatusInfo(message, status);
+
+        if (error && sent) {
+            viewHolder
+                    .time()
+                    .setTextColor(
+                            MaterialColors.getColor(
+                                    viewHolder.time(),
+                                    com.google.android.material.R.attr.colorError));
         } else {
-            setTextColor(viewHolder.time, bubbleColor);
+            setTextColor(viewHolder.time(), bubbleColor);
         }
-        setTextColor(viewHolder.subject, bubbleColor);
+        setTextColor(viewHolder.subject(), bubbleColor);
         if (message.getEncryption() == Message.ENCRYPTION_NONE) {
-            viewHolder.indicator.setVisibility(View.GONE);
+            viewHolder.indicatorSecurity().setVisibility(View.GONE);
         } else {
             boolean verified = false;
             if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
-                final FingerprintStatus status =
+                final FingerprintStatus fingerprintStatus =
                         message.getConversation()
                                 .getAccount()
                                 .getAxolotlService()
                                 .getFingerprintTrust(message.getFingerprint());
-                if (status != null && status.isVerified()) {
+                if (fingerprintStatus != null && fingerprintStatus.isVerified()) {
                     verified = true;
                 }
             }
             if (verified) {
-                viewHolder.indicator.setImageResource(R.drawable.ic_verified_user_24dp);
+                viewHolder.indicatorSecurity().setImageResource(R.drawable.ic_verified_user_24dp);
             } else {
-                viewHolder.indicator.setImageResource(R.drawable.ic_lock_24dp);
+                viewHolder.indicatorSecurity().setImageResource(R.drawable.ic_lock_24dp);
             }
-            if (error && type == SENT) {
-                setImageTintError(viewHolder.indicator);
+            if (error && sent) {
+                setImageTintError(viewHolder.indicatorSecurity());
             } else {
-                setImageTint(viewHolder.indicator, bubbleColor);
+                setImageTint(viewHolder.indicatorSecurity(), bubbleColor);
             }
-            viewHolder.indicator.setVisibility(View.VISIBLE);
+            viewHolder.indicatorSecurity().setVisibility(View.VISIBLE);
         }
 
-        if (viewHolder.edit_indicator != null) {
-            if (message.edited()) {
-                viewHolder.edit_indicator.setVisibility(View.VISIBLE);
-                if (error && type == SENT) {
-                    setImageTintError(viewHolder.edit_indicator);
-                } else {
-                    setImageTint(viewHolder.edit_indicator, bubbleColor);
-                }
+        if (message.edited()) {
+            viewHolder.indicatorEdit().setVisibility(View.VISIBLE);
+            if (error && sent) {
+                setImageTintError(viewHolder.indicatorEdit());
             } else {
-                viewHolder.edit_indicator.setVisibility(View.GONE);
+                setImageTint(viewHolder.indicatorEdit(), bubbleColor);
             }
+        } else {
+            viewHolder.indicatorEdit().setVisibility(View.GONE);
         }
 
         final String formattedTime =
-                UIHelper.readableTimeDifferenceFull(getContext(), message.getMergedTimeSent());
+                UIHelper.readableTimeDifferenceFull(getContext(), message.getTimeSent());
         final String bodyLanguage = message.getBodyLanguage();
         final ImmutableList.Builder<String> timeInfoBuilder = new ImmutableList.Builder<>();
-        if (message.getStatus() <= Message.STATUS_RECEIVED) {
-            timeInfoBuilder.add(formattedTime);
-            if (fileSize != null) {
-                timeInfoBuilder.add(fileSize);
-            }
-            if (mForceNames || multiReceived || (message.getTrueCounterpart() != null && message.getContact() != null)) {
-                final String displayName = UIHelper.getMessageDisplayName(message);
-                if (displayName != null) {
-                    timeInfoBuilder.add(displayName);
-                }
-            }
-            if (bodyLanguage != null) {
-                timeInfoBuilder.add(bodyLanguage.toUpperCase(Locale.US));
+
+        if (mForceNames || multiReceived || showUserNickname || (message.getTrueCounterpart() != null && message.getContact() != null)) {
+            final String displayName = UIHelper.getMessageDisplayName(message);
+            if (displayName != null) {
+                timeInfoBuilder.add(displayName);
             }
+        }
+        if (fileSize != null) {
+            timeInfoBuilder.add(fileSize);
+        }
+        if (bodyLanguage != null) {
+            timeInfoBuilder.add(bodyLanguage.toUpperCase(Locale.US));
+        }
+        // for space reasons we display only 'additional status info' (send progress or concrete
+        // failure reason) or the time
+        if (additionalStatusInfo != null) {
+            timeInfoBuilder.add(additionalStatusInfo);
         } else {
-            if (bodyLanguage != null) {
-                timeInfoBuilder.add(bodyLanguage.toUpperCase(Locale.US));
-            }
-            if (fileSize != null) {
-                timeInfoBuilder.add(fileSize);
-            }
-            // for space reasons we display only 'additional status info' (send progress or concrete
-            // failure reason) or the time
-            if (additionalStatusInfo != null) {
-                timeInfoBuilder.add(additionalStatusInfo);
-            } else {
-                timeInfoBuilder.add(formattedTime);
-            }
+            timeInfoBuilder.add(formattedTime);
         }
         final var timeInfo = timeInfoBuilder.build();
-        viewHolder.time.setText(Joiner.on(" \u00B7 ").join(timeInfo));
+        viewHolder.time().setText(Joiner.on(" · ").join(timeInfo));
     }
 
     public static @DrawableRes Integer getMessageStatusAsDrawable(
@@ -419,8 +417,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
             case Message.STATUS_WAITING -> R.drawable.ic_more_horiz_24dp;
             case Message.STATUS_UNSEND -> transferable == null ? null : R.drawable.ic_upload_24dp;
             case Message.STATUS_SEND -> R.drawable.ic_done_24dp;
-            case Message.STATUS_SEND_RECEIVED, Message.STATUS_SEND_DISPLAYED -> R.drawable
-                    .ic_done_all_24dp;
+            case Message.STATUS_SEND_RECEIVED, Message.STATUS_SEND_DISPLAYED ->
+                    R.drawable.ic_done_all_24dp;
             case Message.STATUS_SEND_FAILED -> {
                 final String errorMessage = message.getErrorMessage();
                 if (Message.ERROR_MESSAGE_CANCELLED.equals(errorMessage)) {
@@ -458,31 +456,38 @@ public class MessageAdapter extends ArrayAdapter<Message> {
     }
 
     private void displayInfoMessage(
-            ViewHolder viewHolder, CharSequence text, final BubbleColor bubbleColor) {
-        viewHolder.download_button.setVisibility(View.GONE);
-        viewHolder.audioPlayer.setVisibility(View.GONE);
-        viewHolder.image.setVisibility(View.GONE);
-        viewHolder.messageBody.setVisibility(View.VISIBLE);
-        viewHolder.messageBody.setText(text);
-        viewHolder.messageBody.setTextColor(
-                bubbleToOnSurfaceVariant(viewHolder.messageBody, bubbleColor));
-        viewHolder.messageBody.setTextIsSelectable(false);
+            BubbleMessageItemViewHolder viewHolder,
+            CharSequence text,
+            final BubbleColor bubbleColor) {
+        viewHolder.downloadButton().setVisibility(View.GONE);
+        viewHolder.audioPlayer().setVisibility(View.GONE);
+        viewHolder.image().setVisibility(View.GONE);
+        viewHolder.messageBody().setTypeface(null, Typeface.ITALIC);
+        viewHolder.messageBody().setVisibility(View.VISIBLE);
+        viewHolder.messageBody().setText(text);
+        viewHolder
+                .messageBody()
+                .setTextColor(bubbleToOnSurfaceVariant(viewHolder.messageBody(), bubbleColor));
+        viewHolder.messageBody().setTextIsSelectable(false);
     }
 
     private void displayEmojiMessage(
-            final ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor, int type) {
-        displayTextMessage(viewHolder, message, bubbleColor, type);
-        viewHolder.download_button.setVisibility(View.GONE);
-        viewHolder.audioPlayer.setVisibility(View.GONE);
-        viewHolder.image.setVisibility(View.GONE);
-        viewHolder.messageBody.setVisibility(View.VISIBLE);
-        setTextColor(viewHolder.messageBody, bubbleColor);
+            final BubbleMessageItemViewHolder viewHolder,
+            final Message message,
+            final BubbleColor bubbleColor) {
+        displayTextMessage(viewHolder, message, bubbleColor);
+        viewHolder.downloadButton().setVisibility(View.GONE);
+        viewHolder.audioPlayer().setVisibility(View.GONE);
+        viewHolder.image().setVisibility(View.GONE);
+        viewHolder.messageBody().setTypeface(null, Typeface.NORMAL);
+        viewHolder.messageBody().setVisibility(View.VISIBLE);
+        setTextColor(viewHolder.messageBody(), bubbleColor);
         final var body = getSpannableBody(message);
         ImageSpan[] imageSpans = body.getSpans(0, body.length(), ImageSpan.class);
         float size = imageSpans.length == 1 || Emoticons.isEmoji(body.toString()) ? 5.0f : 2.0f;
         body.setSpan(
                 new RelativeSizeSpan(size), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-        viewHolder.messageBody.setText(body);
+        viewHolder.messageBody().setText(body);
     }
 
     private void applyQuoteSpan(
@@ -596,210 +601,199 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 
     private SpannableStringBuilder getSpannableBody(final Message message) {
         Drawable fallbackImg = ResourcesCompat.getDrawable(activity.getResources(), R.drawable.ic_photo_24dp, null);
-        return message.getMergedBody(new Thumbnailer(message), fallbackImg);
+        return message.getSpannableBody(new Thumbnailer(message), fallbackImg);
     }
 
     private void displayTextMessage(
-            final ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor, final int type) {
-        viewHolder.inReplyToQuote.setVisibility(View.GONE);
-        viewHolder.download_button.setVisibility(View.GONE);
-        viewHolder.image.setVisibility(View.GONE);
-        viewHolder.audioPlayer.setVisibility(View.GONE);
-        viewHolder.messageBody.setVisibility(View.VISIBLE);
-        setTextColor(viewHolder.messageBody, bubbleColor);
-        setTextSize(viewHolder.messageBody, this.bubbleDesign.largeFont);
-
-        final ViewGroup.LayoutParams layoutParams = viewHolder.messageBody.getLayoutParams();
+            final BubbleMessageItemViewHolder viewHolder,
+            final Message message,
+            final BubbleColor bubbleColor) {
+        viewHolder.inReplyToQuote().setVisibility(View.GONE);
+        viewHolder.downloadButton().setVisibility(View.GONE);
+        viewHolder.image().setVisibility(View.GONE);
+        viewHolder.audioPlayer().setVisibility(View.GONE);
+        viewHolder.messageBody().setVisibility(View.VISIBLE);
+        setTextColor(viewHolder.messageBody(), bubbleColor);
+        setTextSize(viewHolder.messageBody(), this.bubbleDesign.largeFont);
+        viewHolder.messageBody().setTypeface(null, Typeface.NORMAL);
+
+        final ViewGroup.LayoutParams layoutParams = viewHolder.messageBody().getLayoutParams();
         layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
-        viewHolder.messageBody.setLayoutParams(layoutParams);
+        viewHolder.messageBody().setLayoutParams(layoutParams);
 
-        final ViewGroup.LayoutParams qlayoutParams = viewHolder.inReplyToQuote.getLayoutParams();
+        final ViewGroup.LayoutParams qlayoutParams = viewHolder.inReplyToQuote().getLayoutParams();
         qlayoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
-        viewHolder.messageBody.setLayoutParams(qlayoutParams);
-
-        viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
-
-        if (message.getBody() != null && !message.getBody().equals("")) {
-            viewHolder.messageBody.setTextIsSelectable(true);
-            viewHolder.messageBody.setVisibility(View.VISIBLE);
-            final String nick = UIHelper.getMessageDisplayName(message);
-            SpannableStringBuilder body = getSpannableBody(message);
-            final var processMarkup = body.getSpans(0, body.length(), Message.PlainTextSpan.class).length > 0;
-            if (body.length() > Config.MAX_DISPLAY_MESSAGE_CHARS) {
-                body = new SpannableStringBuilder(body, 0, Config.MAX_DISPLAY_MESSAGE_CHARS);
-                body.append("\u2026");
-            }
-            Message.MergeSeparator[] mergeSeparators =
-                    body.getSpans(0, body.length(), Message.MergeSeparator.class);
-            for (Message.MergeSeparator mergeSeparator : mergeSeparators) {
-                int start = body.getSpanStart(mergeSeparator);
-                int end = body.getSpanEnd(mergeSeparator);
-                body.setSpan(new DividerSpan(true), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            if (processMarkup) StylingHelper.format(body, viewHolder.messageBody.getCurrentTextColor());
-            MyLinkify.addLinks(body, message.getConversation().getAccount(), message.getConversation().getJid());
-            boolean startsWithQuote = processMarkup ? handleTextQuotes(viewHolder.messageBody, body, bubbleColor, true) : false;
-            for (final android.text.style.QuoteSpan quote : body.getSpans(0, body.length(), android.text.style.QuoteSpan.class)) {
-                int start = body.getSpanStart(quote);
-                int end = body.getSpanEnd(quote);
-                if (start < 0 || end < 0) continue;
-
-                body.removeSpan(quote);
-                applyQuoteSpan(viewHolder.messageBody, body, start, end, bubbleColor, true);
-                if (start == 0) {
-                    if (message.getInReplyTo() == null) {
-                        startsWithQuote = true;
-                    } else {
-                        viewHolder.inReplyToQuote.setText(body.subSequence(start, end));
-                        viewHolder.inReplyToQuote.setVisibility(View.VISIBLE);
-                        body.delete(start, end);
-                        while (body.length() > start && body.charAt(start) == '\n') body.delete(start, 1); // Newlines after quote
-                        continue;
-                    }
+        viewHolder.inReplyToQuote().setLayoutParams(qlayoutParams);
+
+        final var rawBody = message.getBody();
+        if (Strings.isNullOrEmpty(rawBody)) {
+            viewHolder.messageBody().setText("");
+            viewHolder.messageBody().setTextIsSelectable(false);
+            toggleWhisperInfo(viewHolder, message, bubbleColor);
+            return;
+        }
+        viewHolder.messageBody().setTextIsSelectable(true);
+        final String nick = UIHelper.getMessageDisplayName(message);
+        SpannableStringBuilder body = getSpannableBody(message);
+        final var processMarkup = body.getSpans(0, body.length(), Message.PlainTextSpan.class).length > 0;
+        if (body.length() > Config.MAX_DISPLAY_MESSAGE_CHARS) {
+            body = new SpannableStringBuilder(body, 0, Config.MAX_DISPLAY_MESSAGE_CHARS);
+            body.append("…");
+        }
+        if (processMarkup) StylingHelper.format(body, viewHolder.messageBody().getCurrentTextColor());
+        MyLinkify.addLinks(body, message.getConversation().getAccount(), message.getConversation().getJid());
+        boolean startsWithQuote = processMarkup ? handleTextQuotes(viewHolder.messageBody(), body, bubbleColor, true) : false;
+        for (final android.text.style.QuoteSpan quote : body.getSpans(0, body.length(), android.text.style.QuoteSpan.class)) {
+            int start = body.getSpanStart(quote);
+            int end = body.getSpanEnd(quote);
+            if (start < 0 || end < 0) continue;
+
+            body.removeSpan(quote);
+            applyQuoteSpan(viewHolder.messageBody(), body, start, end, bubbleColor, true);
+            if (start == 0) {
+                if (message.getInReplyTo() == null) {
+                    startsWithQuote = true;
+                } else {
+                    viewHolder.inReplyToQuote().setText(body.subSequence(start, end));
+                    viewHolder.inReplyToQuote().setVisibility(View.VISIBLE);
+                    body.delete(start, end);
+                    while (body.length() > start && body.charAt(start) == '\n') body.delete(start, 1); // Newlines after quote
+                    continue;
                 }
             }
-            boolean hasMeCommand = body.toString().startsWith(Message.ME_COMMAND);
-            if (hasMeCommand) {
-                body = body.replace(0, Message.ME_COMMAND.length(), nick + " ");
+        }
+        boolean hasMeCommand = body.toString().startsWith(Message.ME_COMMAND);
+        if (hasMeCommand) {
+            body.replace(0, Message.ME_COMMAND.length(), String.format("%s ", nick));
+        }
+        if (!message.isPrivateMessage()) {
+            if (hasMeCommand && body.length() > nick.length()) {
+                body.setSpan(
+                        new StyleSpan(Typeface.BOLD_ITALIC),
+                        0,
+                        nick.length(),
+                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
             }
-            if (!message.isPrivateMessage()) {
-                if (hasMeCommand && body.length() > nick.length()) {
-                    body.setSpan(
-                            new StyleSpan(Typeface.BOLD_ITALIC),
-                            0,
-                            nick.length(),
-                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-                }
+        } else {
+            String privateMarker;
+            if (message.getStatus() <= Message.STATUS_RECEIVED) {
+                privateMarker = activity.getString(R.string.private_message);
             } else {
-                String privateMarker;
-                if (message.getStatus() <= Message.STATUS_RECEIVED) {
-                    privateMarker = activity.getString(R.string.private_message);
-                } else {
-                    Jid cp = message.getCounterpart();
-                    privateMarker =
-                            activity.getString(
-                                    R.string.private_message_to,
-                                    Strings.nullToEmpty(cp == null ? null : cp.getResource()));
-                }
-                body.insert(0, privateMarker);
-                int privateMarkerIndex = privateMarker.length();
-                if (startsWithQuote) {
-                    body.insert(privateMarkerIndex, "\n\n");
-                    body.setSpan(
-                            new DividerSpan(false),
-                            privateMarkerIndex,
-                            privateMarkerIndex + 2,
-                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {
-                    body.insert(privateMarkerIndex, " ");
-                }
+                Jid cp = message.getCounterpart();
+                privateMarker =
+                        activity.getString(
+                                R.string.private_message_to,
+                                Strings.nullToEmpty(cp == null ? null : cp.getResource()));
+            }
+            body.insert(0, privateMarker);
+            int privateMarkerIndex = privateMarker.length();
+            if (startsWithQuote) {
+                body.insert(privateMarkerIndex, "\n\n");
                 body.setSpan(
-                        new ForegroundColorSpan(
-                                bubbleToOnSurfaceVariant(viewHolder.messageBody, bubbleColor)),
-                        0,
+                        new DividerSpan(false),
                         privateMarkerIndex,
+                        privateMarkerIndex + 2,
                         Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {
+                body.insert(privateMarkerIndex, " ");
+            }
+            body.setSpan(
+                    new ForegroundColorSpan(
+                            bubbleToOnSurfaceVariant(viewHolder.messageBody(), bubbleColor)),
+                    0,
+                    privateMarkerIndex,
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            body.setSpan(
+                    new StyleSpan(Typeface.BOLD),
+                    0,
+                    privateMarkerIndex,
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            if (hasMeCommand) {
                 body.setSpan(
-                        new StyleSpan(Typeface.BOLD),
-                        0,
-                        privateMarkerIndex,
+                        new StyleSpan(Typeface.BOLD_ITALIC),
+                        privateMarkerIndex + 1,
+                        privateMarkerIndex + 1 + nick.length(),
                         Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-                if (hasMeCommand) {
+            }
+        }
+        if (message.getConversation().getMode() == Conversation.MODE_MULTI
+                && message.getStatus() == Message.STATUS_RECEIVED) {
+            if (message.getConversation() instanceof Conversation conversation) {
+                Pattern pattern =
+                        NotificationService.generateNickHighlightPattern(
+                                conversation.getMucOptions().getActualNick());
+                Matcher matcher = pattern.matcher(body);
+                while (matcher.find()) {
                     body.setSpan(
-                            new StyleSpan(Typeface.BOLD_ITALIC),
-                            privateMarkerIndex + 1,
-                            privateMarkerIndex + 1 + nick.length(),
+                            new StyleSpan(Typeface.BOLD),
+                            matcher.start(),
+                            matcher.end(),
                             Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                 }
             }
-            if (message.getConversation().getMode() == Conversation.MODE_MULTI
-                    && message.getStatus() == Message.STATUS_RECEIVED) {
-                if (message.getConversation() instanceof Conversation conversation) {
-                    Pattern pattern =
-                            NotificationService.generateNickHighlightPattern(
-                                    conversation.getMucOptions().getActualNick());
-                    Matcher matcher = pattern.matcher(body);
-                    while (matcher.find()) {
-                        body.setSpan(
-                                new StyleSpan(Typeface.BOLD),
-                                matcher.start(),
-                                matcher.end(),
-                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    }
+        }
 
-                    pattern = NotificationService.generateNickHighlightPattern(conversation.getMucOptions().getActualName());
-                    matcher = pattern.matcher(body);
-                    while (matcher.find()) {
-                        body.setSpan(new StyleSpan(Typeface.BOLD), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    }
-                }
-            }
-            for (final var emoji : EmojiManager.extractEmojisInOrderWithIndex(body.toString())) {
-                var end = emoji.getCharIndex() + emoji.getEmoji().getEmoji().length();
-                if (body.length() > end && body.charAt(end) == '\uFE0F') end++;
-                body.setSpan(
-                        new RelativeSizeSpan(1.2f),
-                        emoji.getCharIndex(),
-                        end,
-                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            // Make custom emoji bigger too, to match emoji
-            for (final var span : body.getSpans(0, body.length(), com.cheogram.android.InlineImageSpan.class)) {
-                body.setSpan(
-                        new RelativeSizeSpan(1.2f),
-                        body.getSpanStart(span),
-                        body.getSpanEnd(span),
-                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
+        for (final var emoji : EmojiManager.extractEmojisInOrderWithIndex(body.toString())) {
+            var end = emoji.getCharIndex() + emoji.getEmoji().getEmoji().length();
+            if (body.length() > end && body.charAt(end) == '\uFE0F') end++;
+            body.setSpan(
+                    new RelativeSizeSpan(1.2f),
+                    emoji.getCharIndex(),
+                    end,
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        }
+        // Make custom emoji bigger too, to match emoji
+        for (final var span : body.getSpans(0, body.length(), com.cheogram.android.InlineImageSpan.class)) {
+            body.setSpan(
+                    new RelativeSizeSpan(1.2f),
+                    body.getSpanStart(span),
+                    body.getSpanEnd(span),
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        }
 
-            if (highlightedTerm != null) {
-                StylingHelper.highlight(viewHolder.messageBody, body, highlightedTerm);
-            }
+        if (highlightedTerm != null) {
+            StylingHelper.highlight(viewHolder.messageBody(), body, highlightedTerm);
+        }
 
-            viewHolder.messageBody.setAutoLinkMask(0);
-            viewHolder.messageBody.setText(body);
-            if (body.length() <= 0) viewHolder.messageBody.setVisibility(View.GONE);
-            BetterLinkMovementMethod method = new BetterLinkMovementMethod() {
-                @Override
-                protected void dispatchUrlLongClick(TextView tv, ClickableSpan span) {
-                    if (span instanceof URLSpan || mOnInlineImageLongClickedListener == null) {
-                        tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0));
-                        super.dispatchUrlLongClick(tv, span);
-                        return;
-                    }
+        viewHolder.messageBody().setAutoLinkMask(0);
+        viewHolder.messageBody().setText(body);
+        if (body.length() <= 0) viewHolder.messageBody().setVisibility(View.GONE);
+        BetterLinkMovementMethod method = new BetterLinkMovementMethod() {
+            @Override
+            protected void dispatchUrlLongClick(TextView tv, ClickableSpan span) {
+                if (span instanceof URLSpan || mOnInlineImageLongClickedListener == null) {
+                    tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0));
+                    super.dispatchUrlLongClick(tv, span);
+                    return;
+                }
 
-                    Spannable body = (Spannable) tv.getText();
-                    ImageSpan[] imageSpans = body.getSpans(body.getSpanStart(span), body.getSpanEnd(span), ImageSpan.class);
-                    if (imageSpans.length > 0) {
-                        Uri uri = Uri.parse(imageSpans[0].getSource());
-                        Cid cid = BobTransfer.cid(uri);
-                        if (cid == null) return;
-                        if (mOnInlineImageLongClickedListener.onInlineImageLongClicked(cid)) {
-                            tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0));
-                        }
+                Spannable body = (Spannable) tv.getText();
+                ImageSpan[] imageSpans = body.getSpans(body.getSpanStart(span), body.getSpanEnd(span), ImageSpan.class);
+                if (imageSpans.length > 0) {
+                    Uri uri = Uri.parse(imageSpans[0].getSource());
+                    Cid cid = BobTransfer.cid(uri);
+                    if (cid == null) return;
+                    if (mOnInlineImageLongClickedListener.onInlineImageLongClicked(cid)) {
+                        tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0));
                     }
                 }
-            };
-            method.setOnLinkLongClickListener((tv, url) -> {
-                tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0));
-                ShareUtil.copyLinkToClipboard(activity, url);
-                return true;
-            });
-            viewHolder.messageBody.setMovementMethod(method);
-        } else {
-            viewHolder.messageBody.setText("");
-            viewHolder.messageBody.setTextIsSelectable(false);
-            toggleWhisperInfo(viewHolder, message, bubbleColor);
-        }
+            }
+        };
+        method.setOnLinkLongClickListener((tv, url) -> {
+            tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0));
+            ShareUtil.copyLinkToClipboard(activity, url);
+            return true;
+        });
+        viewHolder.messageBody().setMovementMethod(method);
     }
 
     private void displayDownloadableMessage(
-            ViewHolder viewHolder,
+            final BubbleMessageItemViewHolder viewHolder,
             final Message message,
             String text,
-            final BubbleColor bubbleColor, final int type) {
-        displayTextMessage(viewHolder, message, bubbleColor, type);
-        viewHolder.image.setVisibility(View.GONE);
+            final BubbleColor bubbleColor) {
+        displayTextMessage(viewHolder, message, bubbleColor);
+        viewHolder.image().setVisibility(View.GONE);
         List<Element> thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null;
         if (thumbs != null && !thumbs.isEmpty()) {
             for (Element thumb : thumbs) {
@@ -832,40 +826,41 @@ public class MessageAdapter extends ArrayAdapter<Message> {
                 if (height < 1 && thumb.getAttribute("height") != null) height = Integer.parseInt(thumb.getAttribute("height"));
                 if (height < 1) height = 1080;
 
-                viewHolder.image.setVisibility(View.VISIBLE);
-                imagePreviewLayout(width, height, viewHolder.image, message.getInReplyTo() != null, true, type, viewHolder);
-                activity.loadBitmap(message, viewHolder.image);
-                viewHolder.image.setOnClickListener(v -> ConversationFragment.downloadFile(activity, message));
+                viewHolder.image().setVisibility(View.VISIBLE);
+                imagePreviewLayout(width, height, viewHolder.image(), message.getInReplyTo() != null, true, viewHolder);
+                activity.loadBitmap(message, viewHolder.image());
+                viewHolder.image().setOnClickListener(v -> ConversationFragment.downloadFile(activity, message));
 
                 break;
             }
         }
-        viewHolder.audioPlayer.setVisibility(View.GONE);
-        viewHolder.download_button.setVisibility(View.VISIBLE);
-        viewHolder.download_button.setText(text);
+        viewHolder.audioPlayer().setVisibility(View.GONE);
+        viewHolder.downloadButton().setVisibility(View.VISIBLE);
+        viewHolder.downloadButton().setText(text);
         final var attachment = Attachment.of(message);
         final @DrawableRes int imageResource = MediaAdapter.getImageDrawable(attachment);
-        viewHolder.download_button.setIconResource(imageResource);
-        viewHolder.download_button.setOnClickListener(
-                v -> ConversationFragment.downloadFile(activity, message));
+        viewHolder.downloadButton().setIconResource(imageResource);
+        viewHolder
+                .downloadButton()
+                .setOnClickListener(v -> ConversationFragment.downloadFile(activity, message));
     }
 
-    private void displayWebxdcMessage(ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor, final int type) {
+    private void displayWebxdcMessage(BubbleMessageItemViewHolder viewHolder, final Message message, final BubbleColor bubbleColor) {
         Cid webxdcCid = message.getFileParams().getCids().get(0);
         WebxdcPage webxdc = new WebxdcPage(activity, webxdcCid, message);
-        displayTextMessage(viewHolder, message, bubbleColor, type);
-        viewHolder.image.setVisibility(View.GONE);
-        viewHolder.audioPlayer.setVisibility(View.GONE);
-        viewHolder.download_button.setVisibility(View.VISIBLE);
-        viewHolder.download_button.setIconResource(0);
-        viewHolder.download_button.setText("Open " + webxdc.getName());
-        viewHolder.download_button.setOnClickListener(v -> {
+        displayTextMessage(viewHolder, message, bubbleColor);
+        viewHolder.image().setVisibility(View.GONE);
+        viewHolder.audioPlayer().setVisibility(View.GONE);
+        viewHolder.downloadButton().setVisibility(View.VISIBLE);
+        viewHolder.downloadButton().setIconResource(0);
+        viewHolder.downloadButton().setText("Open " + webxdc.getName());
+        viewHolder.downloadButton().setOnClickListener(v -> {
             Conversation conversation = (Conversation) message.getConversation();
             if (!conversation.switchToSession("webxdc\0" + message.getUuid())) {
                 conversation.startWebxdc(webxdc);
             }
         });
-        viewHolder.image.setOnClickListener(v -> {
+        viewHolder.image().setOnClickListener(v -> {
             Conversation conversation = (Conversation) message.getConversation();
             if (!conversation.switchToSession("webxdc\0" + message.getUuid())) {
                 conversation.startWebxdc(webxdc);
@@ -884,8 +879,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
             }).start();
         } else {
             if (lastUpdate != null && (lastUpdate.getSummary() != null || lastUpdate.getDocument() != null)) {
-                viewHolder.messageBody.setVisibility(View.VISIBLE);
-                viewHolder.messageBody.setText(
+                viewHolder.messageBody().setVisibility(View.VISIBLE);
+                viewHolder.messageBody().setText(
                     (lastUpdate.getDocument() == null ? "" : lastUpdate.getDocument() + "\n") +
                     (lastUpdate.getSummary() == null ? "" : lastUpdate.getSummary())
                 );
@@ -903,103 +898,112 @@ public class MessageAdapter extends ArrayAdapter<Message> {
                 }
             }).start();
         } else {
-            viewHolder.image.setVisibility(View.VISIBLE);
-            viewHolder.image.setImageDrawable(d);
-            imagePreviewLayout(d.getIntrinsicWidth(), d.getIntrinsicHeight(), viewHolder.image, message.getInReplyTo() != null, true, type, viewHolder);
+            viewHolder.image().setVisibility(View.VISIBLE);
+            viewHolder.image().setImageDrawable(d);
+            imagePreviewLayout(d.getIntrinsicWidth(), d.getIntrinsicHeight(), viewHolder.image(), message.getInReplyTo() != null, true, viewHolder);
         }
     }
 
     private void displayOpenableMessage(
-            ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor, final int type) {
-        displayTextMessage(viewHolder, message, bubbleColor, type);
-        viewHolder.image.setVisibility(View.GONE);
-        viewHolder.audioPlayer.setVisibility(View.GONE);
-        viewHolder.download_button.setVisibility(View.VISIBLE);
-        viewHolder.download_button.setText(
-                activity.getString(
-                        R.string.open_x_file,
-                        UIHelper.getFileDescriptionString(activity, message)));
+            final BubbleMessageItemViewHolder viewHolder,
+            final Message message,
+            final BubbleColor bubbleColor) {
+        displayTextMessage(viewHolder, message, bubbleColor);
+        viewHolder.image().setVisibility(View.GONE);
+        viewHolder.audioPlayer().setVisibility(View.GONE);
+        viewHolder.downloadButton().setVisibility(View.VISIBLE);
+        viewHolder
+                .downloadButton()
+                .setText(
+                        activity.getString(
+                                R.string.open_x_file,
+                                UIHelper.getFileDescriptionString(activity, message)));
         final var attachment = Attachment.of(message);
         final @DrawableRes int imageResource = MediaAdapter.getImageDrawable(attachment);
-        viewHolder.download_button.setIconResource(imageResource);
-        viewHolder.download_button.setOnClickListener(v -> openDownloadable(message));
+        viewHolder.downloadButton().setIconResource(imageResource);
+        viewHolder.downloadButton().setOnClickListener(v -> openDownloadable(message));
     }
 
     private void displayURIMessage(
-            ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor, final int type) {
-        displayTextMessage(viewHolder, message, bubbleColor, type);
-        viewHolder.messageBody.setVisibility(View.GONE);
-        viewHolder.image.setVisibility(View.GONE);
-        viewHolder.audioPlayer.setVisibility(View.GONE);
-        viewHolder.download_button.setVisibility(View.VISIBLE);
+            BubbleMessageItemViewHolder viewHolder, final Message message, final BubbleColor bubbleColor) {
+        displayTextMessage(viewHolder, message, bubbleColor);
+        viewHolder.messageBody().setVisibility(View.GONE);
+        viewHolder.image().setVisibility(View.GONE);
+        viewHolder.audioPlayer().setVisibility(View.GONE);
+        viewHolder.downloadButton().setVisibility(View.VISIBLE);
         final var uri = message.wholeIsKnownURI();
         if ("bitcoin".equals(uri.getScheme())) {
             final var amount = uri.getQueryParameter("amount");
             final var formattedAmount = amount == null || amount.equals("") ? "" : amount + " ";
-            viewHolder.download_button.setIconResource(R.drawable.bitcoin_24dp);
-            viewHolder.download_button.setText("Send " + formattedAmount + "Bitcoin");
+            viewHolder.downloadButton().setIconResource(R.drawable.bitcoin_24dp);
+            viewHolder.downloadButton().setText("Send " + formattedAmount + "Bitcoin");
         } else if ("bitcoincash".equals(uri.getScheme())) {
             final var amount = uri.getQueryParameter("amount");
             final var formattedAmount = amount == null || amount.equals("") ? "" : amount + " ";
-            viewHolder.download_button.setIconResource(R.drawable.bitcoin_cash_24dp);
-            viewHolder.download_button.setText("Send " + formattedAmount + "Bitcoin Cash");
+            viewHolder.downloadButton().setIconResource(R.drawable.bitcoin_cash_24dp);
+            viewHolder.downloadButton().setText("Send " + formattedAmount + "Bitcoin Cash");
         } else if ("ethereum".equals(uri.getScheme())) {
             final var amount = uri.getQueryParameter("value");
             final var formattedAmount = amount == null || amount.equals("") ? "" : amount + " ";
-            viewHolder.download_button.setIconResource(R.drawable.eth_24dp);
-            viewHolder.download_button.setText("Send " + formattedAmount + "via Ethereum");
+            viewHolder.downloadButton().setIconResource(R.drawable.eth_24dp);
+            viewHolder.downloadButton().setText("Send " + formattedAmount + "via Ethereum");
         } else if ("monero".equals(uri.getScheme())) {
             final var amount = uri.getQueryParameter("tx_amount");
             final var formattedAmount = amount == null || amount.equals("") ? "" : amount + " ";
-            viewHolder.download_button.setIconResource(R.drawable.monero_24dp);
-            viewHolder.download_button.setText("Send " + formattedAmount + "Monero");
+            viewHolder.downloadButton().setIconResource(R.drawable.monero_24dp);
+            viewHolder.downloadButton().setText("Send " + formattedAmount + "Monero");
         } else if ("wownero".equals(uri.getScheme())) {
             final var amount = uri.getQueryParameter("tx_amount");
             final var formattedAmount = amount == null || amount.equals("") ? "" : amount + " ";
-            viewHolder.download_button.setIconResource(R.drawable.wownero_24dp);
-            viewHolder.download_button.setText("Send " + formattedAmount + "Wownero");
+            viewHolder.downloadButton().setIconResource(R.drawable.wownero_24dp);
+            viewHolder.downloadButton().setText("Send " + formattedAmount + "Wownero");
         }
-        viewHolder.download_button.setOnClickListener(v -> new FixedURLSpan(message.getRawBody()).onClick(v));
+        viewHolder.downloadButton().setOnClickListener(v -> new FixedURLSpan(message.getRawBody()).onClick(v));
     }
 
     private void displayLocationMessage(
-            ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor, final int type) {
-        displayTextMessage(viewHolder, message, bubbleColor, type);
-        viewHolder.messageBody.setVisibility(View.GONE);
-        viewHolder.image.setVisibility(View.GONE);
-        viewHolder.audioPlayer.setVisibility(View.GONE);
-        viewHolder.download_button.setVisibility(View.VISIBLE);
-        viewHolder.download_button.setText(R.string.show_location);
+            final BubbleMessageItemViewHolder viewHolder,
+            final Message message,
+            final BubbleColor bubbleColor) {
+        displayTextMessage(viewHolder, message, bubbleColor);
+        viewHolder.image().setVisibility(View.GONE);
+        viewHolder.audioPlayer().setVisibility(View.GONE);
+        viewHolder.downloadButton().setVisibility(View.VISIBLE);
+        viewHolder.downloadButton().setText(R.string.show_location);
         final var attachment = Attachment.of(message);
         final @DrawableRes int imageResource = MediaAdapter.getImageDrawable(attachment);
-        viewHolder.download_button.setIconResource(imageResource);
-        viewHolder.download_button.setOnClickListener(v -> showLocation(message));
+        viewHolder.downloadButton().setIconResource(imageResource);
+        viewHolder.downloadButton().setOnClickListener(v -> showLocation(message));
     }
 
     private void displayAudioMessage(
-            ViewHolder viewHolder, Message message, final BubbleColor bubbleColor, final int type) {
-        displayTextMessage(viewHolder, message, bubbleColor, type);
-        viewHolder.image.setVisibility(View.GONE);
-        viewHolder.download_button.setVisibility(View.GONE);
-        final RelativeLayout audioPlayer = viewHolder.audioPlayer;
+            final BubbleMessageItemViewHolder viewHolder,
+            Message message,
+            final BubbleColor bubbleColor) {
+        displayTextMessage(viewHolder, message, bubbleColor);
+        viewHolder.image().setVisibility(View.GONE);
+        viewHolder.downloadButton().setVisibility(View.GONE);
+        final RelativeLayout audioPlayer = viewHolder.audioPlayer();
         audioPlayer.setVisibility(View.VISIBLE);
         AudioPlayer.ViewHolder.get(audioPlayer).setBubbleColor(bubbleColor);
         this.audioPlayer.init(audioPlayer, message);
     }
 
     private void displayMediaPreviewMessage(
-            ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor, final int type) {
-        displayTextMessage(viewHolder, message, bubbleColor, type);
-        viewHolder.download_button.setVisibility(View.GONE);
-        viewHolder.audioPlayer.setVisibility(View.GONE);
-        viewHolder.image.setVisibility(View.VISIBLE);
+            final BubbleMessageItemViewHolder viewHolder,
+            final Message message,
+            final BubbleColor bubbleColor) {
+        displayTextMessage(viewHolder, message, bubbleColor);
+        viewHolder.downloadButton().setVisibility(View.GONE);
+        viewHolder.audioPlayer().setVisibility(View.GONE);
+        viewHolder.image().setVisibility(View.VISIBLE);
         final FileParams params = message.getFileParams();
-        imagePreviewLayout(params.width, params.height, viewHolder.image, message.getInReplyTo() != null, viewHolder.messageBody.getVisibility() != View.GONE, type, viewHolder);
-        activity.loadBitmap(message, viewHolder.image);
-        viewHolder.image.setOnClickListener(v -> openDownloadable(message));
+        imagePreviewLayout(params.width, params.height, viewHolder.image(), message.getInReplyTo() != null, viewHolder.messageBody().getVisibility() != View.GONE, viewHolder);
+        activity.loadBitmap(message, viewHolder.image());
+        viewHolder.image().setOnClickListener(v -> openDownloadable(message));
     }
 
-    private void imagePreviewLayout(int w, int h, ShapeableImageView image, boolean otherAbove, boolean otherBelow, int type, ViewHolder viewHolder) {
+    private void imagePreviewLayout(int w, int h, ShapeableImageView image, boolean otherAbove, boolean otherBelow, BubbleMessageItemViewHolder viewHolder) {
         final float target = activity.getResources().getDimension(R.dimen.image_preview_width);
         final int scaledW;
         final int scaledH;
@@ -1016,7 +1020,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
             scaledW = (int) target;
             scaledH = (int) (h / ((double) w / target));
         }
-        final var bodyWidth = Math.max(viewHolder.messageBody.getWidth(), viewHolder.download_button.getWidth() + (20 * metrics.density));
+        final var bodyWidth = Math.max(viewHolder.messageBody().getWidth(), viewHolder.downloadButton().getWidth() + (20 * metrics.density));
         var targetImageWidth = 200 * metrics.density;
         if (!otherBelow) targetImageWidth = 110 * metrics.density;
         if (bodyWidth > 0 && bodyWidth < targetImageWidth) targetImageWidth = bodyWidth;
@@ -1029,7 +1033,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
         var shape = new ShapeAppearanceModel.Builder();
         if (!otherAbove) {
             shape = shape.setTopRightCorner(CornerFamily.ROUNDED, bubbleRadius);
-            if (type == SENT) {
+            if (viewHolder instanceof EndBubbleMessageItemViewHolder) {
                 shape = shape.setTopLeftCorner(CornerFamily.ROUNDED, bubbleRadius);
             }
         }
@@ -1043,18 +1047,20 @@ public class MessageAdapter extends ArrayAdapter<Message> {
         image.setShapeAppearanceModel(shape.build());
 
         if (!small) {
-            final ViewGroup.LayoutParams blayoutParams = viewHolder.messageBody.getLayoutParams();
+            final ViewGroup.LayoutParams blayoutParams = viewHolder.messageBody().getLayoutParams();
             blayoutParams.width = (int) (scaledW - (22 * metrics.density));
-            viewHolder.messageBody.setLayoutParams(blayoutParams);
+            viewHolder.messageBody().setLayoutParams(blayoutParams);
 
-            final ViewGroup.LayoutParams qlayoutParams = viewHolder.inReplyToQuote.getLayoutParams();
+            final ViewGroup.LayoutParams qlayoutParams = viewHolder.inReplyToQuote().getLayoutParams();
             qlayoutParams.width = (int) (scaledW - (22 * metrics.density));
-            viewHolder.messageBody.setLayoutParams(qlayoutParams);
+            viewHolder.messageBody().setLayoutParams(qlayoutParams);
         }
     }
 
     private void toggleWhisperInfo(
-            ViewHolder viewHolder, final Message message, final BubbleColor bubbleColor) {
+            final BubbleMessageItemViewHolder viewHolder,
+            final Message message,
+            final BubbleColor bubbleColor) {
         if (message.isPrivateMessage()) {
             final String privateMarker;
             if (message.getStatus() <= Message.STATUS_RECEIVED) {
  
  
  
    
    @@ -0,0 +1,19 @@
+package eu.siacs.conversations.ui.fragment.settings;
+
+import android.os.Bundle;
+import androidx.annotation.Nullable;
+import eu.siacs.conversations.R;
+
+public class InterfaceBubblesSettingsFragment extends XmppPreferenceFragment {
+
+    @Override
+    public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
+        setPreferencesFromResource(R.xml.preferences_interface_bubbles, rootKey);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        requireActivity().setTitle(R.string.pref_title_bubbles);
+    }
+}
  
  
  
    
    @@ -18,7 +18,8 @@ public class MainSettingsFragment extends PreferenceFragmentCompat {
         setPreferencesFromResource(R.xml.preferences_main, rootKey);
         final var about = findPreference("about");
         final var connection = findPreference("connection");
-        if (about == null || connection == null) {
+        final var up = findPreference("up");
+        if (about == null || connection == null || up == null) {
             throw new IllegalStateException(
                     "The preference resource file is missing some preferences");
         }
@@ -43,6 +44,8 @@ public class MainSettingsFragment extends PreferenceFragmentCompat {
                 .commit();
             return true;
         });
+
+        up.setVisible(!Strings.isNullOrEmpty(getString(R.string.default_push_server)));
     }
 
     @Override
  
  
  
    
    @@ -10,7 +10,6 @@ import android.os.Bundle;
 import android.provider.Settings;
 import android.util.Log;
 import android.widget.Toast;
-
 import androidx.activity.result.ActivityResultLauncher;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -18,10 +17,10 @@ import androidx.preference.Preference;
 import androidx.preference.ListPreference;
 
 import com.google.common.base.Optional;
-
 import eu.siacs.conversations.AppSettings;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
+import eu.siacs.conversations.services.CallIntegration;
 import eu.siacs.conversations.services.NotificationService;
 import eu.siacs.conversations.ui.activity.result.PickRingtone;
 import eu.siacs.conversations.utils.Compatibility;
@@ -68,13 +67,15 @@ public class NotificationsSettingsFragment extends XmppPreferenceFragment {
         final var notificationLed = findPreference(AppSettings.NOTIFICATION_LED);
         final var chatRequests = (ListPreference) findPreference("chat_requests");
         final var foregroundService = findPreference(AppSettings.KEEP_FOREGROUND_SERVICE);
+        final var callIntegration = findPreference(AppSettings.CALL_INTEGRATION);
         if (messageNotificationSettings == null
                 || fullscreenNotification == null
                 || notificationRingtone == null
                 || notificationHeadsUp == null
                 || notificationVibrate == null
                 || notificationLed == null
-                || foregroundService == null) {
+                || foregroundService == null
+                || callIntegration == null) {
             throw new IllegalStateException("The preference resource file is missing preferences");
         }
         if (Compatibility.runsTwentySix()) {
@@ -98,6 +99,8 @@ public class NotificationsSettingsFragment extends XmppPreferenceFragment {
         if (!sharedPreferences.getBoolean("notifications_from_strangers", true) && sharedPreferences.getString("chat_requests", null) == null) {
             chatRequests.setValue("strangers");
         }
+
+        callIntegration.setVisible(CallIntegration.selfManagedAvailable(requireContext()));
     }
 
     @Override
  
  
  
    
    @@ -53,114 +53,118 @@ import eu.siacs.conversations.xmpp.Jid;
 
 public class ShareUtil {
 
-	public static void share(XmppActivity activity, Message message) {
-		Intent shareIntent = new Intent();
-		shareIntent.setAction(Intent.ACTION_SEND);
-		if (message.isGeoUri()) {
-			shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody());
-			shareIntent.setType("text/plain");
-		} else if (!message.isFileOrImage()) {
-			shareIntent.putExtra(Intent.EXTRA_TEXT, message.getMergedBody().toString());
-			shareIntent.setType("text/plain");
-			shareIntent.putExtra(ConversationsActivity.EXTRA_AS_QUOTE, message.getStatus() == Message.STATUS_RECEIVED);
-		} else {
-			final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
-			final var fp = message.getFileParams();
-			final var name = fp == null ? null : fp.getName();
-			final var displayName = name == null ? file.getName() : name;
-			try {
-				shareIntent.putExtra(Intent.EXTRA_STREAM, FileBackend.getUriForFile(activity, file, displayName));
-			} catch (SecurityException e) {
-				Toast.makeText(activity, activity.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show();
-				return;
-			}
-			shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-			String mime = message.getMimeType();
-			if (mime == null) {
-				mime = "*/*";
-			}
-			shareIntent.setType(mime);
-		}
-		try {
-			activity.startActivity(Intent.createChooser(shareIntent, activity.getText(R.string.share_with)));
-		} catch (ActivityNotFoundException e) {
-			//This should happen only on faulty androids because normally chooser is always available
-			Toast.makeText(activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show();
-		}
-	}
-
-	public static void copyToClipboard(XmppActivity activity, Message message) {
-		if (activity.copyTextToClipboard(message.getQuoteableBody(), R.string.message)) {
-			Toast.makeText(activity, R.string.message_copied_to_clipboard, Toast.LENGTH_SHORT).show();
-		}
-	}
+    public static void share(XmppActivity activity, Message message) {
+        Intent shareIntent = new Intent();
+        shareIntent.setAction(Intent.ACTION_SEND);
+        if (message.isGeoUri()) {
+            shareIntent.putExtra(Intent.EXTRA_TEXT, message.getRawBody());
+            shareIntent.setType("text/plain");
+        } else if (!message.isFileOrImage()) {
+            shareIntent.putExtra(Intent.EXTRA_TEXT, message.getQuoteableBody());
+            shareIntent.setType("text/plain");
+            shareIntent.putExtra(
+                    ConversationsActivity.EXTRA_AS_QUOTE,
+                    message.getStatus() == Message.STATUS_RECEIVED);
+        } else {
+            final DownloadableFile file =
+                    activity.xmppConnectionService.getFileBackend().getFile(message);
+            final var fp = message.getFileParams();
+            final var name = fp == null ? null : fp.getName();
+            final var displayName = name == null ? file.getName() : name;
+            try {
+                shareIntent.putExtra(
+                        Intent.EXTRA_STREAM, FileBackend.getUriForFile(activity, file, displayName));
+            } catch (SecurityException e) {
+                Toast.makeText(
+                                activity,
+                                activity.getString(
+                                        R.string.no_permission_to_access_x, file.getAbsolutePath()),
+                                Toast.LENGTH_SHORT)
+                        .show();
+                return;
+            }
+            shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            String mime = message.getMimeType();
+            if (mime == null) {
+                mime = "*/*";
+            }
+            shareIntent.setType(mime);
+        }
+        try {
+            activity.startActivity(
+                    Intent.createChooser(shareIntent, activity.getText(R.string.share_with)));
+        } catch (ActivityNotFoundException e) {
+            // This should happen only on faulty androids because normally chooser is always
+            // available
+            Toast.makeText(activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT)
+                    .show();
+        }
+    }
 
-	public static void copyUrlToClipboard(XmppActivity activity, Message message) {
-		final String url;
-		final int resId;
-		if (message.isGeoUri()) {
-			resId = R.string.location;
-			url = message.getRawBody();
-		} else if (message.hasFileOnRemoteHost()) {
-			resId = R.string.file_url;
-			url = message.getFileParams().url;
-		} else {
-			final Message.FileParams fileParams = message.getFileParams();
-			url = (fileParams != null && fileParams.url != null) ? fileParams.url : message.getBody().trim();
-			resId = R.string.file_url;
-		}
-		if (activity.copyTextToClipboard(url, resId)) {
-			Toast.makeText(activity, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show();
-		}
-	}
+    public static void copyToClipboard(XmppActivity activity, Message message) {
+        if (activity.copyTextToClipboard(message.getQuoteableBody(), R.string.message)) {
+            Toast.makeText(activity, R.string.message_copied_to_clipboard, Toast.LENGTH_SHORT)
+                    .show();
+        }
+    }
 
-	public static void copyLinkToClipboard(final Context context, final String url) {
-		final Uri uri = Uri.parse(url);
-		if ("xmpp".equals(uri.getScheme())) {
-			try {
-				final Jid jid = new XmppUri(uri).getJid();
-				if (copyTextToClipboard(context, jid.asBareJid().toString(), R.string.account_settings_jabber_id)) {
-					Toast.makeText(context, R.string.jabber_id_copied_to_clipboard, Toast.LENGTH_SHORT).show();
-				}
-			} catch (final Exception e) { }
-		} else {
-			if (copyTextToClipboard(context, url, R.string.web_address)) {
-				Toast.makeText(context, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show();
-			}
-		}
-	}
+    public static void copyUrlToClipboard(XmppActivity activity, Message message) {
+        final String url;
+        final int resId;
+        if (message.isGeoUri()) {
+            resId = R.string.location;
+            url = message.getRawBody();
+        } else if (message.hasFileOnRemoteHost()) {
+            resId = R.string.file_url;
+            url = message.getFileParams().url;
+        } else {
+            final Message.FileParams fileParams = message.getFileParams();
+            url =
+                    (fileParams != null && fileParams.url != null)
+                            ? fileParams.url
+                            : message.getRawBody().trim();
+            resId = R.string.file_url;
+        }
+        if (activity.copyTextToClipboard(url, resId)) {
+            Toast.makeText(activity, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show();
+        }
+    }
 
-	public static void copyLinkToClipboard(final XmppActivity activity, final Message message) {
-		final SpannableStringBuilder body = message.getMergedBody();
-		MyLinkify.addLinks(body, true);
-		for (final URLSpan urlspan : body.getSpans(0, body.length() - 1, URLSpan.class)) {
-			copyLinkToClipboard(activity, urlspan.getURL());
-			return;
-		}
-	}
+    public static void copyLinkToClipboard(final Context context, final String url) {
+        final Uri uri = Uri.parse(url);
+        if ("xmpp".equals(uri.getScheme())) {
+            try {
+                final Jid jid = new XmppUri(uri).getJid();
+                if (copyTextToClipboard(context, jid.asBareJid().toString(), R.string.account_settings_jabber_id)) {
+                    Toast.makeText(context, R.string.jabber_id_copied_to_clipboard, Toast.LENGTH_SHORT).show();
+                }
+            } catch (final Exception e) { }
+        } else {
+            if (copyTextToClipboard(context, url, R.string.web_address)) {
+                Toast.makeText(context, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show();
+            }
+        }
+    }
 
-	public static boolean containsXmppUri(String body) {
-		Matcher xmppPatternMatcher = Patterns.XMPP_PATTERN.matcher(body);
-		if (xmppPatternMatcher.find()) {
-			try {
-				return new XmppUri(body.substring(xmppPatternMatcher.start(), xmppPatternMatcher.end())).isValidJid();
-			} catch (Exception e) {
-				return false;
-			}
-		}
-		return false;
-	}
+    public static void copyLinkToClipboard(final XmppActivity activity, final Message message) {
+        final SpannableStringBuilder body = message.getSpannableBody();
+        MyLinkify.addLinks(body, true);
+        for (final URLSpan urlspan : body.getSpans(0, body.length() - 1, URLSpan.class)) {
+            copyLinkToClipboard(activity, urlspan.getURL());
+            return;
+        }
+    }
 
-	public static boolean copyTextToClipboard(Context context, String text, int labelResId) {
-		ClipboardManager mClipBoardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
-		String label = context.getResources().getString(labelResId);
-		if (mClipBoardManager != null) {
-			ClipData mClipData = ClipData.newPlainText(label, text);
-			mClipBoardManager.setPrimaryClip(mClipData);
-			return true;
-		}
-		return false;
-	}
+    public static boolean copyTextToClipboard(Context context, String text, int labelResId) {
+        ClipboardManager mClipBoardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+        String label = context.getResources().getString(labelResId);
+        if (mClipBoardManager != null) {
+            ClipData mClipData = ClipData.newPlainText(label, text);
+            mClipBoardManager.setPrimaryClip(mClipData);
+            return true;
+        }
+        return false;
+    }
 
     public static String getLinkScheme(final SpannableStringBuilder body) {
         MyLinkify.addLinks(body, false);
  
  
  
    
    @@ -1,8 +1,70 @@
 package eu.siacs.conversations.utils;
 
 import net.fellbaum.jemoji.EmojiManager;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
 
 public class Emoticons {
+
+    private static final int VARIATION_16 = 0xFE0F;
+    private static final int VARIATION_15 = 0xFE0E;
+    private static final String VARIATION_16_STRING = new String(new char[] {VARIATION_16});
+    private static final String VARIATION_15_STRING = new String(new char[] {VARIATION_15});
+
+    private static final Set<String> TEXT_DEFAULT_TO_VS16 =
+            ImmutableSet.of(
+                    "❤",
+                    "✔",
+                    "✖",
+                    "➕",
+                    "➖",
+                    "➗",
+                    "⭐",
+                    "⚡",
+                    "\uD83C\uDF96",
+                    "\uD83C\uDFC6",
+                    "\uD83E\uDD47",
+                    "\uD83E\uDD48",
+                    "\uD83E\uDD49",
+                    "\uD83D\uDC51",
+                    "⚓",
+                    "⛵",
+                    "✈",
+                    "⚖",
+                    "⛑",
+                    "⚒",
+                    "⛏",
+                    "☎",
+                    "⛄",
+                    "⛅",
+                    "⚠",
+                    "⚛",
+                    "✡",
+                    "☮",
+                    "☯",
+                    "☀",
+                    "⬅",
+                    "➡",
+                    "⬆",
+                    "⬇");
+
+    public static String normalizeToVS16(final String input) {
+        return TEXT_DEFAULT_TO_VS16.contains(input) && !input.endsWith(VARIATION_15_STRING)
+                ? input + VARIATION_16_STRING
+                : input;
+    }
+
+    public static String existingVariant(final String original, final Set<String> existing) {
+        if (existing.contains(original) || original.endsWith(VARIATION_15_STRING)) {
+            return original;
+        }
+        final var variant =
+                original.endsWith(VARIATION_16_STRING)
+                        ? original.substring(0, original.length() - 1)
+                        : original + VARIATION_16_STRING;
+        return existing.contains(variant) ? variant : original;
+    }
+
     public static boolean isEmoji(String input) {
         return EmojiManager.isEmoji(input);
     }
  
  
  
    
    @@ -1,20 +1,30 @@
 package eu.siacs.conversations.utils;
 
 import com.google.common.net.InetAddresses;
-
+import java.net.InetAddress;
 import java.util.regex.Pattern;
 
 public class IP {
 
-    private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
-    private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
-    private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
-    private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
-    private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
-
-    public static boolean matches(String server) {
-        return server != null && (
-                PATTERN_IPV4.matcher(server).matches()
+    private static final Pattern PATTERN_IPV4 =
+            Pattern.compile(
+                    "\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+    private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED =
+            Pattern.compile(
+                    "\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)"
+                        + " ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+    private static final Pattern PATTERN_IPV6_6HEX4DEC =
+            Pattern.compile(
+                    "\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+    private static final Pattern PATTERN_IPV6_HEXCOMPRESSED =
+            Pattern.compile(
+                    "\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
+    private static final Pattern PATTERN_IPV6 =
+            Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
+
+    public static boolean matches(final String server) {
+        return server != null
+                && (PATTERN_IPV4.matcher(server).matches()
                         || PATTERN_IPV6.matcher(server).matches()
                         || PATTERN_IPV6_6HEX4DEC.matcher(server).matches()
                         || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches()
@@ -22,8 +32,14 @@ public class IP {
     }
 
     public static String wrapIPv6(final String host) {
-        if (matches(host)) {
-            return String.format("[%s]", host);
+        if (InetAddresses.isInetAddress(host)) {
+            final InetAddress inetAddress;
+            try {
+                inetAddress = InetAddresses.forString(host);
+            } catch (final IllegalArgumentException e) {
+                return host;
+            }
+            return InetAddresses.toUriString(inetAddress);
         } else {
             return host;
         }
@@ -31,12 +47,11 @@ public class IP {
 
     public static String unwrapIPv6(final String host) {
         if (host.length() > 2 && host.charAt(0) == '[' && host.charAt(host.length() - 1) == ']') {
-            final String ip = host.substring(1,host.length() -1);
+            final String ip = host.substring(1, host.length() - 1);
             if (InetAddresses.isInetAddress(ip)) {
                 return ip;
             }
         }
         return host;
     }
-
 }
  
  
  
    
    @@ -30,16 +30,14 @@
 package eu.siacs.conversations.utils;
 
 import com.google.common.base.Strings;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.regex.Pattern;
-
 import eu.siacs.conversations.entities.Conversational;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.http.AesGcmURL;
 import eu.siacs.conversations.http.URL;
 import eu.siacs.conversations.ui.util.QuoteHelper;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.regex.Pattern;
 
 public class MessageUtils {
 
@@ -47,7 +45,7 @@ public class MessageUtils {
 
     public static final String EMPTY_STRING = "";
 
-    public static String prepareQuote(Message message) {
+    public static String prepareQuote(final Message message) {
         final StringBuilder builder = new StringBuilder();
         final String body;
         if (message.hasMeCommand()) {
@@ -102,8 +100,12 @@ public class MessageUtils {
         final String protocol = uri.getScheme();
         final boolean encrypted = ref != null && AesGcmURL.IV_KEY.matcher(ref).matches();
         final boolean followedByDataUri = lines.length == 2 && lines[1].startsWith("data:");
-        final boolean validAesGcm = AesGcmURL.PROTOCOL_NAME.equalsIgnoreCase(protocol) && encrypted && (lines.length == 1 || followedByDataUri);
-        final boolean validProtocol = "http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol);
+        final boolean validAesGcm =
+                AesGcmURL.PROTOCOL_NAME.equalsIgnoreCase(protocol)
+                        && encrypted
+                        && (lines.length == 1 || followedByDataUri);
+        final boolean validProtocol =
+                "http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol);
         final boolean validOob = validProtocol && (oob || encrypted || (legacyEncryption && uri.getPath() != null && (uri.getPath().endsWith(".xdc") || uri.getPath().endsWith(".webp") || uri.getPath().endsWith(".gif") || uri.getPath().endsWith(".png")))) && lines.length == 1;
         return validAesGcm || validOob;
     }
@@ -140,7 +142,10 @@ public class MessageUtils {
     }
 
     public static boolean unInitiatedButKnownSize(Message message) {
-        return message.getType() == Message.TYPE_TEXT && message.getTransferable() == null && message.isOOb() && message.getFileParams().url != null &&
-               (message.getFileParams().size != null || (message.getOob() != null && message.getOob().getScheme() != null && message.getOob().getScheme().equalsIgnoreCase("cid")));
+        return message.getType() == Message.TYPE_TEXT
+                && message.getTransferable() == null
+                && message.isOOb()
+                && (message.getFileParams().size != null || (message.getOob() != null && message.getOob().getScheme() != null && message.getOob().getScheme().equalsIgnoreCase("cid")))
+                && message.getFileParams().url != null;
     }
 }
  
  
  
    
    @@ -3,13 +3,12 @@ package eu.siacs.conversations.utils;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.util.Log;
-
 import androidx.annotation.NonNull;
-
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 import com.google.common.base.Throwables;
+import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
@@ -20,21 +19,6 @@ import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
 
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.Conversations;
-import eu.siacs.conversations.xmpp.Jid;
-
-import org.minidns.dnsmessage.Question;
-import org.minidns.dnsname.DnsName;
-import org.minidns.dnsname.InvalidDnsNameException;
-import org.minidns.dnsqueryresult.DnsQueryResult;
-import org.minidns.record.A;
-import org.minidns.record.AAAA;
-import org.minidns.record.CNAME;
-import org.minidns.record.Data;
-import org.minidns.record.InternetAddressRR;
-import org.minidns.record.Record;
-import org.minidns.record.SRV;
 
 import java.io.IOException;
 import java.net.Inet4Address;
@@ -52,6 +36,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
 import eu.siacs.conversations.Config;
+import eu.siacs.conversations.Conversations;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.xmpp.Jid;
@@ -62,6 +47,7 @@ import org.minidns.DnsClient;
 import org.minidns.cache.LruCache;
 import org.minidns.dnsmessage.Question;
 import org.minidns.dnsname.DnsName;
+import org.minidns.dnsname.InvalidDnsNameException;
 import org.minidns.dnssec.DnssecResultNotAuthenticException;
 import org.minidns.dnssec.DnssecValidationFailedException;
 import org.minidns.dnsserverlookup.AndroidUsingExec;
@@ -96,7 +82,7 @@ public class Resolver {
                             return left.ip != null ? -1 : 1;
                         }
                     } else {
-                        return left.directTls ? -1 : 1;
+                        return left.directTls ? 1 : -1;
                     }
                 } else {
                     return left.priority - right.priority;
@@ -105,7 +91,8 @@ public class Resolver {
 
     private static final ExecutorService DNS_QUERY_EXECUTOR = Executors.newFixedThreadPool(12);
 
-    public static final int DEFAULT_PORT_XMPP = 5222;
+    public static final int XMPP_PORT_STARTTLS = 5222;
+    private static final int XMPP_PORT_DIRECT_TLS = 5223;
 
     private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
     private static final String STARTTLS_SERVICE = "_xmpp-client";
@@ -284,7 +271,7 @@ public class Resolver {
     }
 
     public static boolean useDirectTls(final int port) {
-        return port == 443 || port == 5223;
+        return port == 443 || port == XMPP_PORT_DIRECT_TLS;
     }
 
     public static List<Result> resolve(final String domain) {
@@ -331,14 +318,11 @@ public class Resolver {
         if (IP.matches(domain)) {
             final InetAddress inetAddress;
             try {
-                inetAddress = InetAddress.getByName(domain);
-            } catch (final UnknownHostException e) {
+                inetAddress = InetAddresses.forString(domain);
+            } catch (final IllegalArgumentException e) {
                 return Collections.emptyList();
             }
-            final Result result = new Result();
-            result.ip = inetAddress;
-            result.port = DEFAULT_PORT_XMPP;
-            return Collections.singletonList(result);
+            return Result.createWithDefaultPorts(null, inetAddress);
         } else {
             return Collections.emptyList();
         }
@@ -477,7 +461,7 @@ public class Resolver {
                 noSrvFallbacks,
                 results -> {
                     if (results.isEmpty()) {
-                        return Collections.singletonList(Result.createDefault(dnsName));
+                        return Result.createDefaults(dnsName);
                     } else {
                         return results;
                     }
@@ -529,7 +513,7 @@ public class Resolver {
         public static final String AUTHENTICATED = "authenticated";
         private InetAddress ip;
         private DnsName hostname;
-        private int port = DEFAULT_PORT_XMPP;
+        private int port = XMPP_PORT_STARTTLS;
         private boolean directTls = false;
         private boolean authenticated = false;
         private int priority;
@@ -543,17 +527,40 @@ public class Resolver {
             return result;
         }
 
-        static Result createDefault(final DnsName hostname, final InetAddress ip, final boolean authenticated) {
+        static List<Result> createWithDefaultPorts(final DnsName hostname, final InetAddress ip) {
+            return Lists.transform(
+                    Arrays.asList(XMPP_PORT_STARTTLS),
+                    p -> createDefault(hostname, ip, p, false));
+        }
+
+        static Result createDefault(final DnsName hostname, final InetAddress ip, final int port, final boolean authenticated) {
             Result result = new Result();
-            result.port = DEFAULT_PORT_XMPP;
+            result.port = port;
             result.hostname = hostname;
             result.ip = ip;
             result.authenticated = authenticated;
             return result;
         }
 
+        static Result createDefault(final DnsName hostname, final InetAddress ip, final boolean authenticated) {
+            return createDefault(hostname, ip, XMPP_PORT_STARTTLS, authenticated);
+        }
+
         static Result createDefault(final DnsName hostname) {
-            return createDefault(hostname, null, false);
+            return createDefault(hostname, null, XMPP_PORT_STARTTLS, false);
+        }
+
+        static List<Result> createDefaults(
+                final DnsName hostname, final Collection<InetAddress> inetAddresses) {
+            final ImmutableList.Builder<Result> builder = new ImmutableList.Builder<>();
+            for (final InetAddress inetAddress : inetAddresses) {
+                builder.addAll(createWithDefaultPorts(hostname, inetAddress));
+            }
+            return builder.build();
+        }
+
+        static List<Result> createDefaults(final DnsName hostname) {
+            return createWithDefaultPorts(hostname, null);
         }
 
         public static Result fromCursor(final Cursor cursor) {
@@ -624,6 +631,10 @@ public class Resolver {
                     .toString();
         }
 
+        public String asDestination() {
+            return ip != null ? InetAddresses.toAddrString(ip) : hostname.toString();
+        }
+
         public ContentValues toContentValues() {
             final ContentValues contentValues = new ContentValues();
             contentValues.put(IP, ip == null ? null : ip.getAddress());
  
  
  
    
    @@ -1,37 +1,56 @@
 package eu.siacs.conversations.utils;
 
 import com.google.common.io.ByteStreams;
-
+import com.google.common.net.InetAddresses;
+import eu.siacs.conversations.Config;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.nio.ByteBuffer;
 
-import eu.siacs.conversations.Config;
-
 public class SocksSocketFactory {
 
-    private static final byte[] LOCALHOST = new byte[]{127, 0, 0, 1};
+    private static final byte[] LOCALHOST = new byte[] {127, 0, 0, 1};
 
-    public static void createSocksConnection(final Socket socket, final String destination, final int port) throws IOException {
-        //TODO use different Socks Addr Type if destination is IP or IPv6
+    public static void createSocksConnection(
+            final Socket socket, final String destination, final int port) throws IOException {
         final InputStream proxyIs = socket.getInputStream();
         final OutputStream proxyOs = socket.getOutputStream();
-        proxyOs.write(new byte[]{0x05, 0x01, 0x00});
+        proxyOs.write(new byte[] {0x05, 0x01, 0x00});
         proxyOs.flush();
         final byte[] handshake = new byte[2];
         ByteStreams.readFully(proxyIs, handshake);
         if (handshake[0] != 0x05 || handshake[1] != 0x00) {
             throw new SocksConnectionException("Socks 5 handshake failed");
         }
-        final byte[] dest = destination.getBytes();
-        final ByteBuffer request = ByteBuffer.allocate(7 + dest.length);
-        request.put(new byte[]{0x05, 0x01, 0x00, 0x03});
-        request.put((byte) dest.length);
-        request.put(dest);
+        final byte type;
+        final ByteBuffer request;
+        if (InetAddresses.isInetAddress(destination)) {
+            final var ip = InetAddresses.forString(destination);
+            final var dest = ip.getAddress();
+            request = ByteBuffer.allocate(6 + dest.length);
+            if (ip instanceof Inet4Address) {
+                type = 0x01;
+            } else if (ip instanceof Inet6Address) {
+                type = 0x04;
+            } else {
+                throw new IOException("IP address is of unknown subtype");
+            }
+            request.put(new byte[] {0x05, 0x01, 0x00, type});
+            request.put(dest);
+        } else {
+            final byte[] dest = destination.getBytes();
+            type = 0x03;
+            request = ByteBuffer.allocate(7 + dest.length);
+            request.put(new byte[] {0x05, 0x01, 0x00, type});
+            request.put((byte) dest.length);
+            request.put(dest);
+        }
         request.putShort((short) port);
         proxyOs.write(request.array());
         proxyOs.flush();
@@ -42,13 +61,16 @@ public class SocksSocketFactory {
             throw new IOException(String.format("Unknown Socks version %02X ", ver));
         }
         final byte status = response[1];
-        final byte bndAddrType = response[3];
-        final byte[] bndDestination = readDestination(bndAddrType, proxyIs);
+        final byte bndAddressType = response[3];
+        final byte[] bndDestination = readDestination(bndAddressType, proxyIs);
         final byte[] bndPort = new byte[2];
-        if (bndAddrType == 0x03) {
+        if (bndAddressType == 0x03) {
             final String receivedDestination = new String(bndDestination);
             if (!receivedDestination.equalsIgnoreCase(destination)) {
-                throw new IOException(String.format("Destination mismatch. Received %s Expected %s", receivedDestination, destination));
+                throw new IOException(
+                        String.format(
+                                "Destination mismatch. Received %s Expected %s",
+                                receivedDestination, destination));
             }
         }
         ByteStreams.readFully(proxyIs, bndPort);
@@ -63,7 +85,8 @@ public class SocksSocketFactory {
         }
     }
 
-    private static byte[] readDestination(final byte type, final InputStream inputStream) throws IOException {
+    private static byte[] readDestination(final byte type, final InputStream inputStream)
+            throws IOException {
         final byte[] bndDestination;
         if (type == 0x01) {
             bndDestination = new byte[4];
@@ -88,7 +111,8 @@ public class SocksSocketFactory {
         return false;
     }
 
-    private static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException {
+    private static Socket createSocket(InetSocketAddress address, String destination, int port)
+            throws IOException {
         Socket socket = new Socket();
         try {
             socket.connect(address, Config.CONNECT_TIMEOUT * 1000);
@@ -100,7 +124,10 @@ public class SocksSocketFactory {
     }
 
     public static Socket createSocketOverTor(String destination, int port) throws IOException {
-        return createSocket(new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050), destination, port);
+        return createSocket(
+                new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050),
+                destination,
+                port);
     }
 
     private static class SocksConnectionException extends IOException {
@@ -109,9 +136,7 @@ public class SocksSocketFactory {
         }
     }
 
-    public static class SocksProxyNotFoundException extends IOException {
-
-    }
+    public static class SocksProxyNotFoundException extends IOException {}
 
     public static class HostNotFoundException extends SocksConnectionException {
         HostNotFoundException(String message) {
  
  
  
    
    @@ -29,7 +29,6 @@
 
 package eu.siacs.conversations.utils;
 
-import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Typeface;
 import android.preference.PreferenceManager;
@@ -47,12 +46,9 @@ import android.text.style.StyleSpan;
 import android.text.style.TypefaceSpan;
 import android.widget.EditText;
 import android.widget.TextView;
-
 import androidx.annotation.ColorInt;
-import androidx.core.content.ContextCompat;
-
 import com.google.android.material.color.MaterialColors;
-
+import eu.siacs.conversations.ui.text.QuoteSpan;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -64,150 +60,162 @@ import eu.siacs.conversations.ui.text.QuoteSpan;
 
 public class StylingHelper {
 
-	public static final int XHTML_IGNORE = 1;
-	public static final int XHTML_REMOVE = 2;
-	public static final int XHTML_EMPHASIS = 3;
-	public static final int XHTML_CODE = 4;
-	public static final int NOLINKIFY = 0xf0;
-
-	private static final List<? extends Class<? extends ParcelableSpan>> SPAN_CLASSES = Arrays.asList(
-			StyleSpan.class,
-			StrikethroughSpan.class,
-			TypefaceSpan.class,
-			ForegroundColorSpan.class,
-			RelativeSizeSpan.class
-	);
-
-	public static void clear(final Editable editable) {
-		final int end = editable.length() - 1;
-		for (Class<? extends ParcelableSpan> clazz : SPAN_CLASSES) {
-			for (ParcelableSpan span : editable.getSpans(0, end, clazz)) {
-				editable.removeSpan(span);
-			}
-		}
-	}
-
-	public static void format(final Editable editable, int start, int end, @ColorInt int textColor, final boolean composing) {
-		for (ImStyleParser.Style style : ImStyleParser.parse(editable, start, end)) {
-			final int keywordLength = style.getKeyword().length();
-			int keywordLengthStart = keywordLength;
-			if ("```".equals(style.getKeyword())) {
-				int i;
-				for (i = style.getStart(); i < editable.length(); i++) {
-					if (editable.charAt(i) == '\n') break;
-				}
-				keywordLengthStart = i - style.getStart() + 1;
-			}
-
-			editable.setSpan(
-				createSpanForStyle(style),
-				style.getStart() + keywordLengthStart,
-				style.getEnd() - keywordLength + 1,
-				Spanned.SPAN_EXCLUSIVE_EXCLUSIVE |
-					("*".equals(style.getKeyword()) || "_".equals(style.getKeyword()) ? XHTML_EMPHASIS << Spanned.SPAN_USER_SHIFT : 0) |
-					("```".equals(style.getKeyword()) && keywordLengthStart > 4 ? XHTML_CODE << Spanned.SPAN_USER_SHIFT : 0) |
-					("`".equals(style.getKeyword()) || "```".equals(style.getKeyword()) ? NOLINKIFY << Spanned.SPAN_USER_SHIFT : 0)
-			);
-			makeKeywordOpaque(editable, style.getStart(), style.getStart() + keywordLengthStart, textColor, composing);
-			makeKeywordOpaque(editable, style.getEnd() - keywordLength + 1, style.getEnd() + 1, textColor, composing);
-		}
-	}
-
-	public static void format(final Editable editable, @ColorInt int textColor) {
-		format(editable, textColor, false);
-	}
-
-	public static void format(final Editable editable, @ColorInt int textColor, final boolean composing) {
-		int end = 0;
-		Message.MergeSeparator[] spans = editable.getSpans(0, editable.length() - 1, Message.MergeSeparator.class);
-		for (Message.MergeSeparator span : spans) {
-			format(editable, end, editable.getSpanStart(span), textColor, composing);
-			end = editable.getSpanEnd(span);
-		}
-		format(editable, end, editable.length() - 1, textColor, composing);
-	}
-
-	public static void highlight(final TextView view, final Editable editable, final List<String> needles) {
-		for (final String needle : needles) {
-			if (!FtsUtils.isKeyword(needle)) {
-				highlight(view, editable, needle);
-			}
-		}
-	}
-
-	public static List<String> filterHighlightedWords(List<String> terms) {
-		List<String> words = new ArrayList<>();
-		for (String term : terms) {
-			if (!FtsUtils.isKeyword(term)) {
-				StringBuilder builder = new StringBuilder();
-				for (int codepoint, i = 0; i < term.length(); i += Character.charCount(codepoint)) {
-					codepoint = term.codePointAt(i);
-					if (Character.isLetterOrDigit(codepoint)) {
-						builder.append(Character.toChars(codepoint));
-					} else if (builder.length() > 0) {
-						words.add(builder.toString());
-						builder.delete(0, builder.length());
-					}
-				}
-				if (builder.length() > 0) {
-					words.add(builder.toString());
-				}
-			}
-		}
-		return words;
-	}
-
-	private static void highlight(final TextView view, final Editable editable, final String needle) {
-		final int length = needle.length();
-		String string = editable.toString();
-		int start = indexOfIgnoreCase(string, needle, 0);
-		while (start != -1) {
-			int end = start + length;
-			editable.setSpan(new BackgroundColorSpan(MaterialColors.getColor(view, com.google.android.material.R.attr.colorPrimaryFixedDim)), start, end, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
-			editable.setSpan(new ForegroundColorSpan(MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimaryFixed)), start, end, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
-			start = indexOfIgnoreCase(string, needle, start + length);
-		}
-
-	}
-
-	static CharSequence subSequence(CharSequence charSequence, int start, int end) {
-		if (start == 0 && charSequence.length() + 1 == end) {
-			return charSequence;
-		}
-		if (charSequence instanceof Spannable spannable) {
-			Spannable sub = (Spannable) spannable.subSequence(start, end);
-			for (Class<? extends ParcelableSpan> clazz : SPAN_CLASSES) {
-				ParcelableSpan[] spannables = spannable.getSpans(start, end, clazz);
-				for (ParcelableSpan parcelableSpan : spannables) {
-					int beginSpan = spannable.getSpanStart(parcelableSpan);
-					int endSpan = spannable.getSpanEnd(parcelableSpan);
-					if (beginSpan >= start && endSpan <= end) {
-						continue;
-					}
-					sub.setSpan(clone(parcelableSpan), Math.max(beginSpan - start, 0), Math.min(Math.max(sub.length() - 1, 0), endSpan), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-				}
-			}
-			return sub;
-		} else {
-			return charSequence.subSequence(start, end);
-		}
-	}
-
-	private static ParcelableSpan clone(ParcelableSpan span) {
-		if (span instanceof ForegroundColorSpan) {
-			return new ForegroundColorSpan(((ForegroundColorSpan) span).getForegroundColor());
-		} else if (span instanceof TypefaceSpan) {
-			return new TypefaceSpan(((TypefaceSpan) span).getFamily());
-		} else if (span instanceof StyleSpan) {
-			return new StyleSpan(((StyleSpan) span).getStyle());
-		} else if (span instanceof StrikethroughSpan) {
-			return new StrikethroughSpan();
-		} else {
-			throw new AssertionError("Unknown Span");
-		}
-	}
-
-	private static ParcelableSpan createSpanForStyle(final ImStyleParser.Style style) {
+    public static final int XHTML_IGNORE = 1;
+    public static final int XHTML_REMOVE = 2;
+    public static final int XHTML_EMPHASIS = 3;
+    public static final int XHTML_CODE = 4;
+    public static final int NOLINKIFY = 0xf0;
+
+    private static final List<? extends Class<? extends ParcelableSpan>> SPAN_CLASSES =
+            Arrays.asList(
+                    StyleSpan.class,
+                    StrikethroughSpan.class,
+                    TypefaceSpan.class,
+                    RelativeSizeSpan.class,
+                    ForegroundColorSpan.class);
+
+    public static void clear(final Editable editable) {
+        final int end = editable.length() - 1;
+        for (Class<? extends ParcelableSpan> clazz : SPAN_CLASSES) {
+            for (ParcelableSpan span : editable.getSpans(0, end, clazz)) {
+                editable.removeSpan(span);
+            }
+        }
+    }
+
+    public static void format(final Editable editable, int start, int end, @ColorInt int textColor, final boolean composing) {
+        for (ImStyleParser.Style style : ImStyleParser.parse(editable, start, end)) {
+            final int keywordLength = style.getKeyword().length();
+            int keywordLengthStart = keywordLength;
+            if ("```".equals(style.getKeyword())) {
+                int i;
+                for (i = style.getStart(); i < editable.length(); i++) {
+                    if (editable.charAt(i) == '\n') break;
+                }
+                keywordLengthStart = i - style.getStart() + 1;
+            }
+
+            editable.setSpan(
+                createSpanForStyle(style),
+                style.getStart() + keywordLengthStart,
+                style.getEnd() - keywordLength + 1,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE |
+                    ("*".equals(style.getKeyword()) || "_".equals(style.getKeyword()) ? XHTML_EMPHASIS << Spanned.SPAN_USER_SHIFT : 0) |
+                    ("```".equals(style.getKeyword()) && keywordLengthStart > 4 ? XHTML_CODE << Spanned.SPAN_USER_SHIFT : 0) |
+                    ("`".equals(style.getKeyword()) || "```".equals(style.getKeyword()) ? NOLINKIFY << Spanned.SPAN_USER_SHIFT : 0)
+            );
+            makeKeywordOpaque(editable, style.getStart(), style.getStart() + keywordLengthStart, textColor, composing);
+            makeKeywordOpaque(editable, style.getEnd() - keywordLength + 1, style.getEnd() + 1, textColor, composing);
+        }
+    }
+
+    public static void format(final Editable editable, @ColorInt int textColor) {
+        format(editable, textColor, false);
+    }
+
+    public static void format(final Editable editable, @ColorInt int textColor, final boolean composing) {
+        int end = 0;
+        format(editable, end, editable.length() - 1, textColor, composing);
+    }
+
+    public static void highlight(
+            final TextView view, final Editable editable, final List<String> needles) {
+        for (final String needle : needles) {
+            if (!FtsUtils.isKeyword(needle)) {
+                highlight(view, editable, needle);
+            }
+        }
+    }
+
+    public static List<String> filterHighlightedWords(List<String> terms) {
+        List<String> words = new ArrayList<>();
+        for (String term : terms) {
+            if (!FtsUtils.isKeyword(term)) {
+                StringBuilder builder = new StringBuilder();
+                for (int codepoint, i = 0; i < term.length(); i += Character.charCount(codepoint)) {
+                    codepoint = term.codePointAt(i);
+                    if (Character.isLetterOrDigit(codepoint)) {
+                        builder.append(Character.toChars(codepoint));
+                    } else if (builder.length() > 0) {
+                        words.add(builder.toString());
+                        builder.delete(0, builder.length());
+                    }
+                }
+                if (builder.length() > 0) {
+                    words.add(builder.toString());
+                }
+            }
+        }
+        return words;
+    }
+
+    private static void highlight(
+            final TextView view, final Editable editable, final String needle) {
+        final int length = needle.length();
+        String string = editable.toString();
+        int start = indexOfIgnoreCase(string, needle, 0);
+        while (start != -1) {
+            int end = start + length;
+            editable.setSpan(
+                    new BackgroundColorSpan(
+                            MaterialColors.getColor(
+                                    view, com.google.android.material.R.attr.colorPrimaryFixedDim)),
+                    start,
+                    end,
+                    SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
+            editable.setSpan(
+                    new ForegroundColorSpan(
+                            MaterialColors.getColor(
+                                    view, com.google.android.material.R.attr.colorOnPrimaryFixed)),
+                    start,
+                    end,
+                    SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
+            start = indexOfIgnoreCase(string, needle, start + length);
+        }
+    }
+
+    static CharSequence subSequence(CharSequence charSequence, int start, int end) {
+        if (start == 0 && charSequence.length() + 1 == end) {
+            return charSequence;
+        }
+        if (charSequence instanceof Spannable spannable) {
+            Spannable sub = (Spannable) spannable.subSequence(start, end);
+            for (Class<? extends ParcelableSpan> clazz : SPAN_CLASSES) {
+                ParcelableSpan[] spannables = spannable.getSpans(start, end, clazz);
+                for (ParcelableSpan parcelableSpan : spannables) {
+                    int beginSpan = spannable.getSpanStart(parcelableSpan);
+                    int endSpan = spannable.getSpanEnd(parcelableSpan);
+                    if (beginSpan >= start && endSpan <= end) {
+                        continue;
+                    }
+                    sub.setSpan(
+                            clone(parcelableSpan),
+                            Math.max(beginSpan - start, 0),
+                            Math.min(sub.length() - 1, endSpan),
+                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+            }
+            return sub;
+        } else {
+            return charSequence.subSequence(start, end);
+        }
+    }
+
+    private static ParcelableSpan clone(ParcelableSpan span) {
+        if (span instanceof ForegroundColorSpan) {
+            return new ForegroundColorSpan(((ForegroundColorSpan) span).getForegroundColor());
+        } else if (span instanceof TypefaceSpan) {
+            return new TypefaceSpan(((TypefaceSpan) span).getFamily());
+        } else if (span instanceof StyleSpan) {
+            return new StyleSpan(((StyleSpan) span).getStyle());
+        } else if (span instanceof StrikethroughSpan) {
+            return new StrikethroughSpan();
+        } else {
+            throw new AssertionError("Unknown Span");
+        }
+    }
+
+    private static ParcelableSpan createSpanForStyle(final ImStyleParser.Style style) {
         return switch (style.getKeyword()) {
             case "*" -> new StyleSpan(Typeface.BOLD);
             case "_" -> new StyleSpan(Typeface.ITALIC);
@@ -215,78 +223,74 @@ public class StylingHelper {
             case "`", "```" -> new TypefaceSpan("monospace");
             default -> throw new AssertionError("Unknown Style");
         };
-	}
-
-	private static void makeKeywordOpaque(final Editable editable, int start, int end, @ColorInt int fallbackTextColor, final boolean composing) {
-		QuoteSpan[] quoteSpans = editable.getSpans(start, end, QuoteSpan.class);
-		@ColorInt int textColor = quoteSpans.length > 0 ? quoteSpans[0].getColor() : fallbackTextColor;
-		@ColorInt int keywordColor = transformColor(textColor);
-		if (composing) {
-			if (end-start > 1) {
-				editable.setSpan(new ForegroundColorSpan(keywordColor), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | XHTML_REMOVE << Spanned.SPAN_USER_SHIFT);
-			} else {
-				editable.setSpan(new RelativeSizeSpan(0), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | XHTML_REMOVE << Spanned.SPAN_USER_SHIFT);
-			}
-		} else {
-			editable.setSpan(new ForegroundColorSpan(keywordColor), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-		}
-	}
-
-	private static
-	@ColorInt
-	int transformColor(@ColorInt int c) {
-		return Color.argb(Math.round(Color.alpha(c) * 0.45f), Color.red(c), Color.green(c), Color.blue(c));
-	}
-
-	private static int indexOfIgnoreCase(final String haystack, final String needle, final int start) {
-		if (haystack == null || needle == null) {
-			return -1;
-		}
-		final int endLimit = haystack.length() - needle.length() + 1;
-		if (start > endLimit) {
-			return -1;
-		}
-		if (needle.length() == 0) {
-			return start;
-		}
-		for (int i = start; i < endLimit; i++) {
-			if (haystack.regionMatches(true, i, needle, 0, needle.length())) {
-				return i;
-			}
-		}
-		return -1;
-	}
-
-	public static class MessageEditorStyler implements TextWatcher {
-
-		private final EditText mEditText;
-		private final MessageAdapter mAdapter;
-
-		public MessageEditorStyler(EditText editText, MessageAdapter adapter) {
-			this.mEditText = editText;
-			this.mAdapter = adapter;
-		}
-
-		@Override
-		public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
-
-		}
-
-		@Override
-		public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
-
-		}
-
-		@Override
-		public void afterTextChanged(Editable editable) {
-			clear(editable);
-			final var p = PreferenceManager.getDefaultSharedPreferences(mEditText.getContext());
-			if (!p.getBoolean("compose_rich_text", mEditText.getContext().getResources().getBoolean(R.bool.compose_rich_text))) return;
-			for (final var span : editable.getSpans(0, editable.length() - 1, QuoteSpan.class)) {
-				editable.removeSpan(span);
-			}
-			format(editable, mEditText.getCurrentTextColor(), true);
-			mAdapter.handleTextQuotes(mEditText, editable, false);
-		}
-	}
+    }
+
+    private static void makeKeywordOpaque(final Editable editable, int start, int end, @ColorInt int fallbackTextColor, final boolean composing) {
+        QuoteSpan[] quoteSpans = editable.getSpans(start, end, QuoteSpan.class);
+        @ColorInt int textColor = quoteSpans.length > 0 ? quoteSpans[0].getColor() : fallbackTextColor;
+        @ColorInt int keywordColor = transformColor(textColor);
+        if (composing) {
+            if (end-start > 1) {
+                editable.setSpan(new ForegroundColorSpan(keywordColor), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | XHTML_REMOVE << Spanned.SPAN_USER_SHIFT);
+            } else {
+                editable.setSpan(new RelativeSizeSpan(0), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | XHTML_REMOVE << Spanned.SPAN_USER_SHIFT);
+            }
+        } else {
+            editable.setSpan(new ForegroundColorSpan(keywordColor), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        }
+    }
+
+    private static @ColorInt int transformColor(@ColorInt int c) {
+        return Color.argb(
+                Math.round(Color.alpha(c) * 0.45f), Color.red(c), Color.green(c), Color.blue(c));
+    }
+
+    private static int indexOfIgnoreCase(
+            final String haystack, final String needle, final int start) {
+        if (haystack == null || needle == null) {
+            return -1;
+        }
+        final int endLimit = haystack.length() - needle.length() + 1;
+        if (start > endLimit) {
+            return -1;
+        }
+        if (needle.length() == 0) {
+            return start;
+        }
+        for (int i = start; i < endLimit; i++) {
+            if (haystack.regionMatches(true, i, needle, 0, needle.length())) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public static class MessageEditorStyler implements TextWatcher {
+
+        private final EditText mEditText;
+        private final MessageAdapter mAdapter;
+
+        public MessageEditorStyler(EditText editText, MessageAdapter adapter) {
+            this.mEditText = editText;
+            this.mAdapter = adapter;
+        }
+
+        @Override
+        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+        @Override
+        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+        @Override
+        public void afterTextChanged(Editable editable) {
+            clear(editable);
+            final var p = PreferenceManager.getDefaultSharedPreferences(mEditText.getContext());
+            if (!p.getBoolean("compose_rich_text", mEditText.getContext().getResources().getBoolean(R.bool.compose_rich_text))) return;
+            for (final var span : editable.getSpans(0, editable.length() - 1, QuoteSpan.class)) {
+                editable.removeSpan(span);
+            }
+            format(editable, mEditText.getCurrentTextColor(), true);
+            mAdapter.handleTextQuotes(mEditText, editable, false);
+        }
+    }
 }
  
  
  
    
    @@ -12,7 +12,6 @@ import android.util.Base64;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -21,6 +20,8 @@ import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.primitives.Ints;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -57,6 +58,7 @@ import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 import java.util.regex.Matcher;
 
 import javax.net.ssl.KeyManager;
@@ -76,8 +78,10 @@ import eu.siacs.conversations.crypto.XmppDomainVerifier;
 import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.crypto.sasl.ChannelBinding;
 import eu.siacs.conversations.crypto.sasl.ChannelBindingMechanism;
+import eu.siacs.conversations.crypto.sasl.DowngradeProtection;
 import eu.siacs.conversations.crypto.sasl.HashedToken;
 import eu.siacs.conversations.crypto.sasl.SaslMechanism;
+import eu.siacs.conversations.crypto.sasl.ScramMechanism;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.ServiceDiscoveryResult;
@@ -91,6 +95,7 @@ import eu.siacs.conversations.services.MemorizingTrustManager;
 import eu.siacs.conversations.services.MessageArchiveService;
 import eu.siacs.conversations.services.NotificationService;
 import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.ui.util.PendingItem;
 import eu.siacs.conversations.utils.AccountUtils;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.Patterns;
@@ -108,13 +113,13 @@ import eu.siacs.conversations.xml.XmlReader;
 import eu.siacs.conversations.xmpp.bind.Bind2;
 import eu.siacs.conversations.xmpp.forms.Data;
 import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
-
 import im.conversations.android.xmpp.model.AuthenticationFailure;
 import im.conversations.android.xmpp.model.AuthenticationRequest;
 import im.conversations.android.xmpp.model.AuthenticationStreamFeature;
 import im.conversations.android.xmpp.model.StreamElement;
 import im.conversations.android.xmpp.model.bind2.Bind;
 import im.conversations.android.xmpp.model.bind2.Bound;
+import im.conversations.android.xmpp.model.cb.SaslChannelBinding;
 import im.conversations.android.xmpp.model.csi.Active;
 import im.conversations.android.xmpp.model.csi.Inactive;
 import im.conversations.android.xmpp.model.error.Condition;
@@ -141,54 +146,12 @@ import im.conversations.android.xmpp.model.sm.StreamManagement;
 import im.conversations.android.xmpp.model.stanza.Iq;
 import im.conversations.android.xmpp.model.stanza.Presence;
 import im.conversations.android.xmpp.model.stanza.Stanza;
+import im.conversations.android.xmpp.model.streams.StreamError;
 import im.conversations.android.xmpp.model.tls.Proceed;
 import im.conversations.android.xmpp.model.tls.StartTls;
 import im.conversations.android.xmpp.processor.BindProcessor;
-
 import okhttp3.HttpUrl;
 
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.ConnectException;
-import java.net.IDN;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
-import java.util.regex.Matcher;
-
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.X509KeyManager;
-import javax.net.ssl.X509TrustManager;
-
 public class XmppConnection implements Runnable {
 
     protected final Account account;
@@ -217,7 +180,7 @@ public class XmppConnection implements Runnable {
     private int stanzasSentBeforeAuthentication;
     private long lastPacketReceived = 0;
     private long lastPingSent = 0;
-    private long lastConnect = 0;
+    private long lastConnectionStarted = 0;
     private long lastSessionStarted = 0;
     private long lastDiscoStarted = 0;
     private boolean isMamPreferenceAlways = false;
@@ -235,6 +198,7 @@ public class XmppConnection implements Runnable {
     private OnStatusChanged statusListener = null;
     private final Runnable bindListener;
     private OnMessageAcknowledged acknowledgedListener = null;
+    private final PendingItem<String> pendingResumeId = new PendingItem<>();
     private LoginInfo loginInfo;
     private HashedToken.Mechanism hashTokenRequest;
     private HttpUrl redirectionUrl = null;
@@ -269,10 +233,10 @@ public class XmppConnection implements Runnable {
         }
     }
 
-    private static boolean validBase64(String input) {
+    private static boolean validBase64(final String input) {
         try {
             return Base64.decode(input, Base64.URL_SAFE).length == 3;
-        } catch (Throwable throwable) {
+        } catch (final Throwable throwable) {
             return false;
         }
     }
@@ -325,7 +289,7 @@ public class XmppConnection implements Runnable {
     }
 
     public void prepareNewConnection() {
-        this.lastConnect = SystemClock.elapsedRealtime();
+        this.lastConnectionStarted = SystemClock.elapsedRealtime();
         this.lastPingSent = SystemClock.elapsedRealtime();
         this.lastDiscoStarted = Long.MAX_VALUE;
         this.mWaitingForSmCatchup.set(false);
@@ -345,49 +309,60 @@ public class XmppConnection implements Runnable {
             mXmppConnectionService.resetSendingToWaiting(account);
         }
         Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": connecting");
+        this.pendingResumeId.clear();
         this.loginInfo = null;
         this.features.encryptionEnabled = false;
         this.inSmacksSession = false;
         this.quickStartInProgress = false;
         this.isBound = false;
         this.attempt++;
-        this.verifiedHostname = null; // will be set if user entered hostname is being used or hostname was verified
         this.dane = false;
-        // with dnssec
+        this.currentResolverResult = null;
+        // will be set if user entered hostname is being used or hostname was verified with dnssec
+        this.verifiedHostname = null;
         try {
             Socket localSocket;
             shouldAuthenticate = !account.isOptionSet(Account.OPTION_REGISTER);
             this.changeStatus(Account.State.CONNECTING);
             final boolean useTor = mXmppConnectionService.useTorToConnect() || account.isOnion();
             final boolean extended = mXmppConnectionService.showExtendedConnectionOptions();
+            // TODO collapse Tor usage into normal connection code path
             if (useTor) {
-                String destination;
-                if (account.getHostname().isEmpty() || account.isOnion()) {
-                    destination = account.getServer();
+                final var seeOtherHost = this.seeOtherHostResolverResult;
+                final var hostname = account.getHostname().trim();
+                final var port = account.getPort();
+                final Resolver.Result resume = streamId == null ? null : streamId.location;
+                final Resolver.Result viaTor;
+                if (resume != null) {
+                    viaTor = resume;
+                } else if (seeOtherHost != null) {
+                    viaTor = seeOtherHost;
+                } else if (hostname.isEmpty() || port < 0) {
+                    viaTor =
+                            Iterables.getOnlyElement(
+                                    Resolver.fromHardCoded(
+                                            account.getServer(), Resolver.XMPP_PORT_STARTTLS));
                 } else {
-                    destination = account.getHostname();
-                    this.verifiedHostname = destination;
+                    viaTor = Iterables.getOnlyElement(Resolver.fromHardCoded(hostname, port));
+                    this.verifiedHostname = hostname;
                 }
 
-                final int port = account.getPort();
-                final boolean directTls = Resolver.useDirectTls(port);
+                Log.d(Config.LOGTAG, account.getJid().asBareJid() + " via Tor: " + viaTor);
 
-                Log.d(
-                        Config.LOGTAG,
-                        account.getJid().asBareJid()
-                                + ": connect to "
-                                + destination
-                                + " via Tor. directTls="
-                                + directTls);
-                localSocket = SocksSocketFactory.createSocketOverTor(destination, port);
+                localSocket =
+                        SocksSocketFactory.createSocketOverTor(
+                                viaTor.asDestination(), viaTor.getPort());
 
-                if (directTls) {
+                if (viaTor.isDirectTls()) {
                     localSocket = upgradeSocketToTls(localSocket);
                     features.encryptionEnabled = true;
                 }
 
                 try {
-                    startXmpp(localSocket);
+                    if (startXmpp(localSocket)) {
+                        this.currentResolverResult = viaTor;
+                        this.seeOtherHostResolverResult = null;
+                    }
                 } catch (final InterruptedException e) {
                     Log.d(
                             Config.LOGTAG,
@@ -398,12 +373,12 @@ public class XmppConnection implements Runnable {
                     throw new IOException("Could not start stream", e);
                 }
             } else {
+                final var hostname = account.getHostname().trim();
                 final String domain = account.getServer();
                 final List<Resolver.Result> results = new ArrayList<>();
-                final boolean hardcoded = extended && !account.getHostname().isEmpty();
+                final boolean hardcoded = extended && !hostname.isEmpty();
                 if (hardcoded) {
-                    results.addAll(
-                            Resolver.fromHardCoded(account.getHostname(), account.getPort()));
+                    results.addAll(Resolver.fromHardCoded(hostname, account.getPort()));
                 } else {
                     results.addAll(Resolver.resolve(domain));
                 }
@@ -496,16 +471,13 @@ public class XmppConnection implements Runnable {
 
                         localSocket = new Socket();
                         localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
-
+                        localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000);
                         if (features.encryptionEnabled) {
                             localSocket = upgradeSocketToTls(localSocket);
                         }
-
-                        localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000);
                         if (startXmpp(localSocket)) {
-                            localSocket.setSoTimeout(
-                                    0); // reset to 0; once the connection is established we don’t
-                            // want this
+                            // reset to 0; once the connection is established we don't want this
+                            localSocket.setSoTimeout(0);
                             if (!hardcoded && !result.equals(storedBackupResult)) {
                                 mXmppConnectionService.databaseBackend.saveResolverResult(
                                         domain, result);
@@ -578,15 +550,17 @@ public class XmppConnection implements Runnable {
         if (Thread.currentThread().isInterrupted()) {
             throw new InterruptedException();
         }
+        // this means we have at least found a socket to connect to. give the connection another 90s
+        this.lastConnectionStarted = SystemClock.elapsedRealtime();
         this.socket = socket;
-        tagReader = new XmlReader();
+        this.tagReader = new XmlReader();
         if (tagWriter != null) {
             tagWriter.forceClose();
         }
-        tagWriter = new TagWriter();
-        tagWriter.setOutputStream(socket.getOutputStream());
-        tagReader.setInputStream(socket.getInputStream());
-        tagWriter.beginDocument();
+        this.tagWriter = new TagWriter();
+        this.tagWriter.setOutputStream(socket.getOutputStream());
+        this.tagReader.setInputStream(socket.getInputStream());
+        this.tagWriter.beginDocument();
         final boolean quickStart;
         if (socket instanceof SSLSocket sslSocket) {
             SSLSockets.log(account, sslSocket);
@@ -658,24 +632,28 @@ public class XmppConnection implements Runnable {
         this.mStreamCountDownLatch = streamCountDownLatch;
         Tag nextTag = tagReader.readTag();
         while (nextTag != null && !nextTag.isEnd("stream")) {
-            if (nextTag.isStart("error")) {
-                processStreamError(nextTag);
+            if (nextTag.isStart("error", Namespace.STREAMS)) {
+                processStreamError(tagReader.readElement(nextTag, StreamError.class));
             } else if (nextTag.isStart("features", Namespace.STREAMS)) {
                 processStreamFeatures(nextTag);
             } else if (nextTag.isStart("proceed", Namespace.TLS)) {
                 switchOverToTls(nextTag);
             } else if (nextTag.isStart("failure", Namespace.TLS)) {
                 throw new StateChangingException(Account.State.TLS_ERROR);
+            } else if (!isSecure()) {
+                throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
             } else if (account.isOptionSet(Account.OPTION_REGISTER)
                     && nextTag.isStart("iq", Namespace.JABBER_CLIENT)) {
                 processIq(nextTag);
-            } else if (!isSecure() || this.loginInfo == null) {
+            } else if (this.loginInfo == null) {
                 throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
-            } else if (nextTag.isStart("success")) {
-                final Element success = tagReader.readElement(nextTag);
-                if (processSuccess(success)) {
-                    break;
-                }
+            } else if (nextTag.isStart("success", Namespace.SASL)) {
+                processSuccess(tagReader.readElement(nextTag, Success.class));
+                break;
+            } else if (nextTag.isStart("success", Namespace.SASL_2)) {
+                processSuccess(
+                        tagReader.readElement(
+                                nextTag, im.conversations.android.xmpp.model.sasl2.Success.class));
             } else if (nextTag.isStart("failure", Namespace.SASL)) {
                 final var failure = tagReader.readElement(nextTag, Failure.class);
                 processFailure(failure);
@@ -815,7 +793,7 @@ public class XmppConnection implements Runnable {
         tagWriter.writeElement(response);
     }
 
-    private boolean processSuccess(final Element element)
+    private void processSuccess(final StreamElement element)
             throws IOException, XmlPullParserException {
         final LoginInfo currentLoginInfo = this.loginInfo;
         final SaslMechanism currentSaslMechanism = LoginInfo.mechanism(currentLoginInfo);
@@ -964,12 +942,9 @@ public class XmppConnection implements Runnable {
             final Tag tag = tagReader.readTag();
             if (tag != null && tag.isStart("stream", Namespace.STREAMS)) {
                 processStream();
-                return true;
             } else {
                 throw new StateChangingException(Account.State.STREAM_OPENING_ERROR);
             }
-        } else {
-            return false;
         }
     }
 
@@ -1077,7 +1052,8 @@ public class XmppConnection implements Runnable {
             Log.d(
                     Config.LOGTAG,
                     account.getJid().asBareJid()
-                            + ": fast authentication failed. falling back to regular authentication");
+                            + ": fast authentication failed. falling back to regular"
+                            + " authentication");
             authenticate();
         } else {
             throw new StateChangingException(Account.State.UNAUTHORIZED);
@@ -1125,6 +1101,17 @@ public class XmppConnection implements Runnable {
     }
 
     private void processResumed(final Resumed resumed) throws StateChangingException {
+        final var pendingResumeId = this.pendingResumeId.pop();
+        final var prevId = resumed.getPrevId();
+        if (prevId == null || !prevId.equals(pendingResumeId)) {
+            Log.d(
+                    Config.LOGTAG,
+                    account.getJid().asBareJid()
+                            + ": server tried resume with unknown id "
+                            + prevId);
+            resetStreamId();
+            throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
+        }
         this.inSmacksSession = true;
         this.isBound = true;
         this.tagWriter.writeStanzaAsync(new Request());
@@ -1170,7 +1157,15 @@ public class XmppConnection implements Runnable {
             }
             sendPacket(packet);
         }
-        changeStatusToOnline();
+        if (mWaitForDisco.get()) {
+            this.lastDiscoStarted = SystemClock.elapsedRealtime();
+            Log.d(
+                    Config.LOGTAG,
+                    account.getJid().asBareJid() + ": awaiting disco results after resume");
+            changeStatus(Account.State.CONNECTING);
+        } else {
+            changeStatusToOnline();
+        }
     }
 
     private void changeStatusToOnline() {
@@ -1546,9 +1541,9 @@ public class XmppConnection implements Runnable {
                                 + ": resuming after stanza #"
                                 + stanzasReceived);
             }
-            final var resume = new Resume(this.streamId.id, stanzasReceived);
-            this.mSmCatchupMessageCounter.set(0);
-            this.mWaitingForSmCatchup.set(true);
+            final var streamId = this.streamId.id;
+            final var resume = new Resume(streamId, stanzasReceived);
+            prepareForResume(streamId);
             this.tagWriter.writeStanzaAsync(resume);
         } else if (needsBinding) {
             if (this.streamFeatures.hasChild("bind", Namespace.BIND)
@@ -1595,13 +1590,22 @@ public class XmppConnection implements Runnable {
             authElement = this.streamFeatures.getExtension(Authentication.class);
         }
         final Collection<String> mechanisms = authElement.getMechanismNames();
-        final Element cbElement =
-                this.streamFeatures.findChild("sasl-channel-binding", Namespace.CHANNEL_BINDING);
-        final Collection<ChannelBinding> channelBindings = ChannelBinding.of(cbElement);
+        final var cbExtension = this.streamFeatures.getExtension(SaslChannelBinding.class);
+        final Collection<ChannelBinding> channelBindings = ChannelBinding.of(cbExtension);
         final SaslMechanism.Factory factory = new SaslMechanism.Factory(account);
         final SaslMechanism saslMechanism =
                 factory.of(mechanisms, channelBindings, version, SSLSockets.version(this.socket));
         this.validate(saslMechanism, mechanisms);
+        final DowngradeProtection downgradeProtection;
+        if (cbExtension != null) {
+            downgradeProtection =
+                    new DowngradeProtection(mechanisms, cbExtension.getChannelBindingTypes());
+        } else {
+            downgradeProtection = new DowngradeProtection(mechanisms);
+        }
+        if (saslMechanism instanceof ScramMechanism scramMechanism) {
+            scramMechanism.setDowngradeProtection(downgradeProtection);
+        }
         final boolean quickStartAvailable;
         final String firstMessage =
                 saslMechanism.getClientFirstMessage(sslSocketOrNull(this.socket));
@@ -1626,6 +1630,11 @@ public class XmppConnection implements Runnable {
                 hashTokenRequest =
                         HashedToken.Mechanism.best(
                                 inline.getFastMechanisms(), SSLSockets.version(this.socket));
+                // TODO warn or fail early if channel binding priority isn’t high enough compared to
+                // login mechanism
+                // ChannelBinding.priority(hashTokenRequest.channelBinding)
+                //                        <
+                // ChannelBindingMechanism.getPriority(saslMechanism)
             } else {
                 hashTokenRequest = null;
             }
@@ -1641,7 +1650,8 @@ public class XmppConnection implements Runnable {
                     Log.d(
                             Config.LOGTAG,
                             account.getJid().asBareJid()
-                                    + ": interrupted while waiting for DB restore during SASL2 bind");
+                                    + ": interrupted while waiting for DB restore during SASL2"
+                                    + " bind");
                     return;
                 }
             }
@@ -1776,9 +1786,9 @@ public class XmppConnection implements Runnable {
             authenticate.addChild(generateBindRequest(bind));
         }
         if (inlineStreamManagement && streamId != null) {
-            final var resume = new Resume(this.streamId.id, stanzasReceived);
-            this.mSmCatchupMessageCounter.set(0);
-            this.mWaitingForSmCatchup.set(true);
+            final var streamId = this.streamId.id;
+            final var resume = new Resume(streamId, stanzasReceived);
+            prepareForResume(streamId);
             authenticate.addExtension(resume);
         }
         if (hashedTokenRequest != null) {
@@ -1790,6 +1800,12 @@ public class XmppConnection implements Runnable {
         return authenticate;
     }
 
+    private void prepareForResume(final String streamId) {
+        this.mSmCatchupMessageCounter.set(0);
+        this.mWaitingForSmCatchup.set(true);
+        this.pendingResumeId.push(streamId);
+    }
+
     private Bind generateBindRequest(final Collection<String> bindFeatures) {
         Log.d(Config.LOGTAG, "inline bind features: " + bindFeatures);
         final var bind = new Bind();
@@ -2154,9 +2170,9 @@ public class XmppConnection implements Runnable {
         Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": starting service discovery");
         mPendingServiceDiscoveries.set(0);
         mWaitForDisco.set(waitForDisco);
-        lastDiscoStarted = SystemClock.elapsedRealtime();
+        this.lastDiscoStarted = SystemClock.elapsedRealtime();
         mXmppConnectionService.scheduleWakeUpCall(
-                Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode());
+                Config.CONNECT_DISCO_TIMEOUT * 1000L, account.getUuid().hashCode());
         final Element caps = streamFeatures.findChild("c");
         final String hash = caps == null ? null : caps.getAttribute("hash");
         final String ver = caps == null ? null : caps.getAttribute("ver");
@@ -2388,14 +2404,11 @@ public class XmppConnection implements Runnable {
                 });
     }
 
-    private void processStreamError(final Tag currentTag) throws IOException {
-        final Element streamError = tagReader.readElement(currentTag);
-        if (streamError == null) {
-            return;
-        }
-        if (streamError.hasChild("conflict")) {
-            final var loginInfo = this.loginInfo;
-            if (loginInfo != null && loginInfo.saslVersion == SaslMechanism.Version.SASL_2) {
+    private void processStreamError(final StreamError streamError) throws IOException {
+        final var loginInfo = this.loginInfo;
+        final var isSecureLoggedIn = isSecure() && LoginInfo.isSuccess(loginInfo);
+        if (isSecureLoggedIn && streamError.hasChild("conflict")) {
+            if (loginInfo.saslVersion == SaslMechanism.Version.SASL_2) {
                 this.appSettings.resetInstallationId();
             }
             account.setResource(createNewResource());
@@ -2409,10 +2422,12 @@ public class XmppConnection implements Runnable {
         } else if (streamError.hasChild("host-unknown")) {
             throw new StateChangingException(Account.State.HOST_UNKNOWN);
         } else if (streamError.hasChild("policy-violation")) {
-            this.lastConnect = SystemClock.elapsedRealtime();
+            this.lastConnectionStarted = SystemClock.elapsedRealtime();
             final String text = streamError.findChildContent("text");
             Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": policy violation. " + text);
-            failPendingMessages(text);
+            if (isSecureLoggedIn) {
+                failPendingMessages(text);
+            }
             throw new StateChangingException(Account.State.POLICY_VIOLATION);
         } else if (streamError.hasChild("see-other-host")) {
             final String seeOtherHost = streamError.findChildContent("see-other-host");
@@ -2708,6 +2723,7 @@ public class XmppConnection implements Runnable {
     }
 
     private void resetStreamId() {
+        this.pendingResumeId.clear();
         this.streamId = null;
         this.boundStreamFeatures = null;
     }
@@ -2725,11 +2741,11 @@ public class XmppConnection implements Runnable {
     }
 
     public Jid findDiscoItemByFeature(final String feature) {
-        final List<Entry<Jid, ServiceDiscoveryResult>> items = findDiscoItemsByFeature(feature);
-        if (items.size() >= 1) {
-            return items.get(0).getKey();
+        final var items = findDiscoItemsByFeature(feature);
+        if (items.isEmpty()) {
+            return null;
         }
-        return null;
+        return Iterables.getFirst(items, null).getKey();
     }
 
     public boolean r() {
@@ -2764,8 +2780,7 @@ public class XmppConnection implements Runnable {
     }
 
     public String getMucServer() {
-        List<String> servers = getMucServers();
-        return servers.size() > 0 ? servers.get(0) : null;
+        return Iterables.getFirst(getMucServers(), null);
     }
 
     public int getTimeToNextAttempt(final boolean aggressive) {
@@ -2777,9 +2792,8 @@ public class XmppConnection implements Runnable {
                     account.getLastErrorStatus() == Account.State.POLICY_VIOLATION ? 3 : 0;
             interval = Math.min((int) (25 * Math.pow(1.3, (additionalTime + attempt))), 300);
         }
-        final int secondsSinceLast =
-                (int) ((SystemClock.elapsedRealtime() - this.lastConnect) / 1000);
-        return interval - secondsSinceLast;
+        final var connectionDuration = Ints.saturatedCast(getConnectionDuration() / 1000);
+        return interval - connectionDuration;
     }
 
     public int getAttempt() {
@@ -2795,16 +2809,16 @@ public class XmppConnection implements Runnable {
         return System.currentTimeMillis() - diff;
     }
 
-    public long getLastConnect() {
-        return this.lastConnect;
+    public long getConnectionDuration() {
+        return SystemClock.elapsedRealtime() - this.lastConnectionStarted;
     }
 
-    public long getLastPingSent() {
-        return this.lastPingSent;
+    public long getDiscoDuration() {
+        return SystemClock.elapsedRealtime() - this.lastDiscoStarted;
     }
 
-    public long getLastDiscoStarted() {
-        return this.lastDiscoStarted;
+    public long getLastPingSent() {
+        return this.lastPingSent;
     }
 
     public long getLastPacketReceived() {
@@ -2822,7 +2836,7 @@ public class XmppConnection implements Runnable {
     public void resetAttemptCount(boolean resetConnectTime) {
         this.attempt = 0;
         if (resetConnectTime) {
-            this.lastConnect = 0;
+            this.lastConnectionStarted = 0;
         }
     }
 
@@ -2870,6 +2884,22 @@ public class XmppConnection implements Runnable {
         sendIqPacket(iqPacket, unregisteredIqListener);
     }
 
+    public void triggerConnectionTimeout() {
+        final var duration = getConnectionDuration();
+        Log.d(
+                Config.LOGTAG,
+                account.getJid().asBareJid() + ": connection timeout after " + duration + "ms");
+
+        // last connection time gets reset so time to next attempt is calculated correctly
+        this.lastConnectionStarted = SystemClock.elapsedRealtime();
+
+        // interrupt needs to be called before status change; otherwise we interrupt the newly
+        // created thread
+        this.interrupt();
+        this.forceCloseSocket();
+        this.changeStatus(Account.State.CONNECTION_TIMEOUT);
+    }
+
     private class MyKeyManager implements X509KeyManager {
         @Override
         public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
@@ -3133,7 +3163,7 @@ public class XmppConnection implements Runnable {
                         new String[] {Namespace.HTTP_UPLOAD, Namespace.HTTP_UPLOAD_LEGACY}) {
                     List<Entry<Jid, ServiceDiscoveryResult>> items =
                             findDiscoItemsByFeature(namespace);
-                    if (items.size() > 0) {
+                    if (!items.isEmpty()) {
                         try {
                             long maxsize =
                                     Long.parseLong(
@@ -3147,7 +3177,8 @@ public class XmppConnection implements Runnable {
                                 Log.d(
                                         Config.LOGTAG,
                                         account.getJid().asBareJid()
-                                                + ": http upload is not available for files with size "
+                                                + ": http upload is not available for files with"
+                                                + " size "
                                                 + filesize
                                                 + " (max is "
                                                 + maxsize
@@ -3172,7 +3203,7 @@ public class XmppConnection implements Runnable {
             for (String namespace :
                     new String[] {Namespace.HTTP_UPLOAD, Namespace.HTTP_UPLOAD_LEGACY}) {
                 List<Entry<Jid, ServiceDiscoveryResult>> items = findDiscoItemsByFeature(namespace);
-                if (items.size() > 0) {
+                if (!items.isEmpty()) {
                     try {
                         return Long.parseLong(
                                 items.get(0)
@@ -3192,6 +3223,7 @@ public class XmppConnection implements Runnable {
 
         public boolean bookmarks2() {
             return pepPublishOptions()
+                    && pepConfigNodeMax()
                     && hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS2_COMPAT);
         }
 
  
  
  
    
    @@ -20,6 +20,7 @@ import eu.siacs.conversations.entities.ServiceDiscoveryResult;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
+
 import im.conversations.android.xmpp.model.jingle.Jingle;
 import im.conversations.android.xmpp.model.stanza.Iq;
 
  
  
  
    
    @@ -789,6 +789,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
             final var contact = account.getRoster().getContact(with);
             callIntegration.setCallerDisplayName(
                     contact.getDisplayName(), TelecomManager.PRESENTATION_ALLOWED);
+            callIntegration.setInitialized();
             callIntegration.setInitialAudioDevice(CallIntegration.initialAudioDevice(media));
             callIntegration.startAudioRouting();
             final RtpSessionProposal proposal =
  
  
  
    
    @@ -1,9 +1,7 @@
 package eu.siacs.conversations.xmpp.jingle;
 
 import android.util.Log;
-
 import androidx.annotation.NonNull;
-
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.base.Throwables;
@@ -16,7 +14,6 @@ import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.common.util.concurrent.SettableFuture;
-
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
 import eu.siacs.conversations.entities.Conversation;
@@ -38,19 +35,8 @@ import eu.siacs.conversations.xmpp.jingle.transports.InbandBytestreamsTransport;
 import eu.siacs.conversations.xmpp.jingle.transports.SocksByteStreamsTransport;
 import eu.siacs.conversations.xmpp.jingle.transports.Transport;
 import eu.siacs.conversations.xmpp.jingle.transports.WebRTCDataChannelTransport;
-
 import im.conversations.android.xmpp.model.jingle.Jingle;
 import im.conversations.android.xmpp.model.stanza.Iq;
-
-import org.bouncycastle.crypto.engines.AESEngine;
-import org.bouncycastle.crypto.io.CipherInputStream;
-import org.bouncycastle.crypto.io.CipherOutputStream;
-import org.bouncycastle.crypto.modes.AEADBlockCipher;
-import org.bouncycastle.crypto.modes.GCMBlockCipher;
-import org.bouncycastle.crypto.params.AEADParameters;
-import org.bouncycastle.crypto.params.KeyParameter;
-import org.webrtc.IceCandidate;
-
 import java.io.Closeable;
 import java.io.EOFException;
 import java.io.File;
@@ -68,6 +54,14 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.Queue;
 import java.util.concurrent.CountDownLatch;
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.io.CipherInputStream;
+import org.bouncycastle.crypto.io.CipherOutputStream;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.webrtc.IceCandidate;
 
 public class JingleFileTransferConnection extends AbstractJingleConnection
         implements Transport.Callback, Transferable {
@@ -205,8 +199,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
         if (transition(
                 State.SESSION_INITIALIZED,
                 () -> this.initiatorFileTransferContentMap = contentMap)) {
-            final var iq =
-                    contentMap.toJinglePacket(Jingle.Action.SESSION_INITIATE, id.sessionId);
+            final var iq = contentMap.toJinglePacket(Jingle.Action.SESSION_INITIATE, id.sessionId);
             final var jingle = iq.getExtension(Jingle.class);
             if (xmppAxolotlMessage != null) {
                 this.transportSecurity =
@@ -456,8 +449,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
 
     private void sendSessionAccept(final FileTransferContentMap contentMap) {
         setLocalContentMap(contentMap);
-        final var iq =
-                contentMap.toJinglePacket(Jingle.Action.SESSION_ACCEPT, id.sessionId);
+        final var iq = contentMap.toJinglePacket(Jingle.Action.SESSION_ACCEPT, id.sessionId);
         send(iq);
         // this needs to come after session-accept or else our candidate-error might arrive first
         this.transport.connect();
@@ -562,7 +554,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
         Log.d(Config.LOGTAG, "peer confirmed received " + received);
     }
 
-    private void receiveSessionTerminate(final Iq jinglePacket, final Jingle jingle) {
+    private synchronized void receiveSessionTerminate(final Iq jinglePacket, final Jingle jingle) {
         respondOk(jinglePacket);
         final Jingle.ReasonWrapper wrapper = jingle.getReason();
         final State previous = this.state;
@@ -589,7 +581,6 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
         }
         terminateTransport();
         final State target = reasonToState(wrapper.reason);
-        // TODO check if we were already terminated
         transitionOrThrow(target);
         finish();
     }
@@ -865,13 +856,21 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
 
     @Override
     public void onTransportEstablished() {
-        Log.d(Config.LOGTAG, "on transport established");
+        Log.d(Config.LOGTAG, "transport established");
         final AbstractFileTransceiver fileTransceiver;
         try {
             fileTransceiver = setupTransceiver(isResponder());
         } catch (final Exception e) {
-            Log.d(Config.LOGTAG, "failed to set up file transceiver", e);
-            sendSessionTerminate(Reason.ofThrowable(e), e.getMessage());
+            terminateTransport();
+            if (isTerminated()) {
+                Log.d(
+                        Config.LOGTAG,
+                        "failed to set up file transceiver but session has already been"
+                                + " terminated");
+            } else {
+                Log.d(Config.LOGTAG, "failed to set up file transceiver", e);
+                sendSessionTerminate(Reason.ofThrowable(e), e.getMessage());
+            }
             return;
         }
         this.fileTransceiver = fileTransceiver;
@@ -951,6 +950,10 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
     }
 
     private AbstractFileTransceiver setupTransceiver(final boolean receiving) throws IOException {
+        final var transport = this.transport;
+        if (transport == null) {
+            throw new IOException("No transport configured");
+        }
         final var fileDescription = getLocalContentMap().requireOnlyFile();
         final File file = xmppConnectionService.getFileBackend().getFile(message);
         final Runnable updateRunnable = () -> jingleConnectionManager.updateConversationUi(false);
@@ -987,7 +990,8 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
 
     private void sendSessionInfo(final FileTransferDescription.SessionInfo sessionInfo) {
         final var iq = new Iq(Iq.Type.SET);
-        final var jinglePacket = iq.addExtension(new Jingle(Jingle.Action.SESSION_INFO, this.id.sessionId));
+        final var jinglePacket =
+                iq.addExtension(new Jingle(Jingle.Action.SESSION_INFO, this.id.sessionId));
         jinglePacket.addChild(sessionInfo.asElement());
         send(iq);
     }
@@ -996,11 +1000,13 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
     public void onTransportSetupFailed() {
         final var transport = this.transport;
         if (transport == null) {
-            // this can happen on IQ timeouts
-            if (isTerminated()) {
-                return;
+            synchronized (this) {
+                // this can happen on IQ timeouts
+                if (isTerminated()) {
+                    return;
+                }
+                sendSessionTerminate(Reason.FAILED_APPLICATION, null);
             }
-            sendSessionTerminate(Reason.FAILED_APPLICATION, null);
             return;
         }
         Log.d(Config.LOGTAG, "onTransportSetupFailed");
@@ -1071,8 +1077,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
                             + contentName);
             return;
         }
-        final Iq iq =
-                transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
+        final Iq iq = transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
         send(iq);
     }
 
@@ -1175,12 +1180,13 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
         }
         final var state = getState();
         return switch (state) {
-            case NULL, SESSION_INITIALIZED, SESSION_INITIALIZED_PRE_APPROVED -> Transferable
-                    .STATUS_OFFER;
+            case NULL, SESSION_INITIALIZED, SESSION_INITIALIZED_PRE_APPROVED ->
+                    Transferable.STATUS_OFFER;
             case TERMINATED_APPLICATION_FAILURE,
-                    TERMINATED_CONNECTIVITY_ERROR,
-                    TERMINATED_DECLINED_OR_BUSY,
-                    TERMINATED_SECURITY_ERROR -> Transferable.STATUS_FAILED;
+                            TERMINATED_CONNECTIVITY_ERROR,
+                            TERMINATED_DECLINED_OR_BUSY,
+                            TERMINATED_SECURITY_ERROR ->
+                    Transferable.STATUS_FAILED;
             case TERMINATED_CANCEL_OR_TIMEOUT -> Transferable.STATUS_CANCELLED;
             case SESSION_ACCEPTED -> Transferable.STATUS_DOWNLOADING;
             default -> Transferable.STATUS_UNKNOWN;
@@ -1255,7 +1261,8 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
             }
             terminateTransport();
             final Iq iq = new Iq(Iq.Type.SET);
-            final var jingle = iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId));
+            final var jingle =
+                    iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId));
             jingle.setReason(reason, "User requested to stop file transfer");
             send(iq);
             finish();
  
  
  
    
    @@ -249,20 +249,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
             }
             receiveTransportInfo(jinglePacket, contentMap);
         } else {
-            if (isTerminated()) {
-                respondOk(jinglePacket);
-                Log.d(
-                        Config.LOGTAG,
-                        id.account.getJid().asBareJid()
-                                + ": ignoring out-of-order transport info; we where already terminated");
-            } else {
-                Log.d(
-                        Config.LOGTAG,
-                        id.account.getJid().asBareJid()
-                                + ": received transport info while in state="
-                                + this.state);
-                terminateWithOutOfOrder(jinglePacket);
-            }
+            receiveOutOfOrderAction(jinglePacket, Jingle.Action.TRANSPORT_INFO);
         }
     }
 
@@ -350,7 +337,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
                     },
                     MoreExecutors.directExecutor());
         } else {
-            terminateWithOutOfOrder(iq);
+            receiveOutOfOrderAction(iq, Jingle.Action.CONTENT_ADD);
         }
     }
 
@@ -426,7 +413,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
         final RtpContentMap outgoingContentAdd = this.outgoingContentAdd;
         if (outgoingContentAdd == null) {
             Log.d(Config.LOGTAG, "received content-accept when we had no outgoing content add");
-            terminateWithOutOfOrder(jinglePacket);
+            receiveOutOfOrderAction(jinglePacket, Jingle.Action.CONTENT_ACCEPT);
             return;
         }
         final Set<ContentAddition.Summary> ourSummary = ContentAddition.summary(outgoingContentAdd);
@@ -456,7 +443,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
                     MoreExecutors.directExecutor());
         } else {
             Log.d(Config.LOGTAG, "received content-accept did not match our outgoing content-add");
-            terminateWithOutOfOrder(jinglePacket);
+            receiveOutOfOrderAction(jinglePacket, Jingle.Action.CONTENT_ACCEPT);
         }
     }
 
@@ -497,7 +484,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
 
     private void receiveContentModify(final Iq jinglePacket, final Jingle jingle) {
         if (this.state != State.SESSION_ACCEPTED) {
-            terminateWithOutOfOrder(jinglePacket);
+            receiveOutOfOrderAction(jinglePacket, Jingle.Action.CONTENT_MODIFY);
             return;
         }
         final Map<String, Content.Senders> modification =
@@ -623,7 +610,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
         final RtpContentMap outgoingContentAdd = this.outgoingContentAdd;
         if (outgoingContentAdd == null) {
             Log.d(Config.LOGTAG, "received content-reject when we had no outgoing content add");
-            terminateWithOutOfOrder(jinglePacket);
+            receiveOutOfOrderAction(jinglePacket, Jingle.Action.CONTENT_REJECT);
             return;
         }
         final Set<ContentAddition.Summary> ourSummary = ContentAddition.summary(outgoingContentAdd);
@@ -634,7 +621,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
             receiveContentReject(ourSummary);
         } else {
             Log.d(Config.LOGTAG, "received content-reject did not match our outgoing content-add");
-            terminateWithOutOfOrder(jinglePacket);
+            receiveOutOfOrderAction(jinglePacket, Jingle.Action.CONTENT_REJECT);
         }
     }
 
@@ -1678,8 +1665,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
             // in environments where we always use discovery timeouts we always want to respond with
             // 'ringing'
             if (Config.JINGLE_MESSAGE_INIT_STRICT_DEVICE_TIMEOUT
-                    || (xmppConnectionService.confirmMessages()
-                            && id.getContact().showInContactList())) {
+                    || id.getContact().showInContactList()) {
                 sendJingleMessage("ringing");
             }
         } else {
  
  
  
    
    @@ -2,6 +2,7 @@ package eu.siacs.conversations.xmpp.jingle;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableSet;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -9,6 +10,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Presence;
@@ -25,14 +27,14 @@ public class RtpCapability {
             Namespace.JINGLE_APPS_RTP,
             Namespace.JINGLE_APPS_DTLS
     );
-    private static final List<String> VIDEO_REQUIREMENTS = Arrays.asList(
+    private static final Collection<String> VIDEO_REQUIREMENTS = Arrays.asList(
             Namespace.JINGLE_FEATURE_AUDIO,
             Namespace.JINGLE_FEATURE_VIDEO
     );
 
     public static Capability check(final Presence presence) {
         final ServiceDiscoveryResult disco = presence.getServiceDiscoveryResult();
-        final List<String> features = disco == null ? Collections.emptyList() : disco.getFeatures();
+        final Set<String> features = disco == null ? Collections.emptySet() : ImmutableSet.copyOf(disco.getFeatures());
         if (features.containsAll(BASIC_RTP_REQUIREMENTS)) {
             if (features.containsAll(VIDEO_REQUIREMENTS)) {
                 return Capability.VIDEO;
@@ -66,7 +68,7 @@ public class RtpCapability {
     public static Capability check(final Contact contact, final boolean allowFallback) {
         final Presences presences = contact.getPresences();
 
-        if (presences.size() == 0 && allowFallback && contact.getAccount().isEnabled()) {
+        if (presences.isEmpty() && allowFallback && contact.getAccount().isEnabled()) {
             Contact gateway = contact.getAccount().getRoster().getContact(Jid.of(contact.getJid().getDomain()));
             if (gateway.showInRoster() && gateway.getPresences().anyIdentity("gateway", "pstn")) {
                 return Capability.AUDIO;
  
  
  
    
    @@ -22,6 +22,7 @@ import eu.siacs.conversations.xmpp.jingle.IceServers;
 import eu.siacs.conversations.xmpp.jingle.WebRTCWrapper;
 import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
 import eu.siacs.conversations.xmpp.jingle.stanzas.WebRTCDataChannelTransportInfo;
+
 import im.conversations.android.xmpp.model.stanza.Iq;
 
 import org.webrtc.CandidatePairChangeEvent;
@@ -45,6 +46,7 @@ import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -455,7 +457,7 @@ public class WebRTCDataChannelTransport implements Transport {
         if (future != null && future.isDone()) {
             try {
                 return future.get();
-            } catch (final InterruptedException | ExecutionException e) {
+            } catch (final InterruptedException | ExecutionException | CancellationException e) {
                 throw new WebRTCWrapper.PeerConnectionNotInitialized();
             }
         } else {
  
  
  
    
    @@ -1,16 +1,13 @@
 package eu.siacs.conversations.xmpp.pep;
 
 import android.os.Bundle;
-
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
 import im.conversations.android.xmpp.model.stanza.Iq;
 
 public class PublishOptions {
 
-    private PublishOptions() {
-
-    }
+    private PublishOptions() {}
 
     public static Bundle openAccess() {
         final Bundle options = new Bundle();
@@ -18,6 +15,12 @@ public class PublishOptions {
         return options;
     }
 
+    public static Bundle presenceAccess() {
+        final Bundle options = new Bundle();
+        options.putString("pubsub#access_model", "presence");
+        return options;
+    }
+
     public static Bundle persistentWhitelistAccess() {
         final Bundle options = new Bundle();
         options.putString("pubsub#persist_items", "true");
@@ -32,14 +35,15 @@ public class PublishOptions {
         options.putString("pubsub#send_last_published_item", "never");
         options.putString("pubsub#max_items", "max");
         options.putString("pubsub#notify_delete", "true");
-        options.putString("pubsub#notify_retract", "true"); //one could also set notify=true on the retract
+        options.putString(
+                "pubsub#notify_retract", "true"); // one could also set notify=true on the retract
 
         return options;
     }
 
     public static boolean preconditionNotMet(Iq response) {
-        final Element error = response.getType() == Iq.Type.ERROR ? response.findChild("error") : null;
+        final Element error =
+                response.getType() == Iq.Type.ERROR ? response.findChild("error") : null;
         return error != null && error.hasChild("precondition-not-met", Namespace.PUBSUB_ERROR);
     }
-
 }
  
  
  
    
    @@ -0,0 +1,16 @@
+package im.conversations.android.xmpp.model.cb;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class ChannelBinding extends Extension {
+
+    public ChannelBinding() {
+        super(ChannelBinding.class);
+    }
+
+    public String getType() {
+        return this.getAttribute("type");
+    }
+}
  
  
  
    
    @@ -0,0 +1,25 @@
+package im.conversations.android.xmpp.model.cb;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamFeature;
+import java.util.Collection;
+
+@XmlElement
+public class SaslChannelBinding extends StreamFeature {
+
+    public SaslChannelBinding() {
+        super(SaslChannelBinding.class);
+    }
+
+    public Collection<ChannelBinding> getChannelBindings() {
+        return this.getExtensions(ChannelBinding.class);
+    }
+
+    public Collection<String> getChannelBindingTypes() {
+        return Collections2.filter(
+                Collections2.transform(getChannelBindings(), ChannelBinding::getType),
+                Predicates.notNull());
+    }
+}
  
  
  
    
    @@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.CHANNEL_BINDING)
+package im.conversations.android.xmpp.model.cb;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
  
  
  
    
    @@ -14,6 +14,11 @@ public class Replace extends Extension {
         super(Replace.class);
     }
 
+    public Replace(final String id) {
+        this();
+        this.setId(id);
+    }
+
     public String getId() {
         return Strings.emptyToNull(this.getAttribute("id"));
     }
  
  
  
    
    @@ -15,4 +15,8 @@ public class Resumed extends StreamElement {
     public Optional<Integer> getHandled() {
         return this.getOptionalIntAttribute("h");
     }
+
+    public String getPrevId() {
+        return this.getAttribute("previd");
+    }
 }
  
  
  
    
    @@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.streams;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement(name = "error")
+public class StreamError extends StreamElement {
+
+    public StreamError() {
+        super(StreamError.class);
+    }
+}
  
  
  
    
    @@ -1,5 +1,6 @@
 package im.conversations.android.xmpp.model.unique;
 
+import com.google.common.base.Strings;
 import im.conversations.android.annotation.XmlElement;
 import im.conversations.android.xmpp.model.Extension;
 
@@ -9,4 +10,13 @@ public class OriginId extends Extension {
     public OriginId() {
         super(OriginId.class);
     }
+
+    public OriginId(final String id) {
+        this();
+        this.setAttribute("id", id);
+    }
+
+    public String getId() {
+        return Strings.emptyToNull(this.getAttribute("id"));
+    }
 }
  
  
  
    
    @@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?colorControlNormal"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM12,6c1.93,0 3.5,1.57 3.5,3.5S13.93,13 12,13s-3.5,-1.57 -3.5,-3.5S10.07,6 12,6zM12,20c-2.03,0 -4.43,-0.82 -6.14,-2.88C7.55,15.8 9.68,15 12,15s4.45,0.8 6.14,2.12C16.43,19.18 14.03,20 12,20z" />
+
+</vector>
  
  
  
    
    @@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M346,820L100,574Q90,564 85,552Q80,540 80,527Q80,514 85,502Q90,490 100,480L330,251L224,145L286,80L686,480Q696,490 700.5,502Q705,514 705,527Q705,540 700.5,552Q696,564 686,574L440,820Q430,830 418,835Q406,840 393,840Q380,840 368,835Q356,830 346,820ZM393,314L179,528Q179,528 179,528Q179,528 179,528L607,528Q607,528 607,528Q607,528 607,528L393,314ZM792,840Q756,840 731,814.5Q706,789 706,752Q706,725 719.5,701Q733,677 750,654L792,600L836,654Q852,677 866,701Q880,725 880,752Q880,789 854,814.5Q828,840 792,840Z" />
+</vector>
  
  
  
    
    @@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:autoMirrored="true"
+    android:tint="?colorControlNormal"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M15,15L3,15v2h12v-2zM15,7L3,7v2h12L15,7zM3,13h18v-2L3,11v2zM3,21h18v-2L3,19v2zM3,3v2h18L21,3L3,3z" />
+
+</vector>
  
  
  
    
    @@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M240,920Q207,920 183.5,896.5Q160,873 160,840L160,120Q160,87 183.5,63.5Q207,40 240,40L640,40Q673,40 696.5,63.5Q720,87 720,120L720,280L640,280L640,240L240,240L240,720L640,720L640,680L720,680L720,840Q720,873 696.5,896.5Q673,920 640,920L240,920ZM240,800L240,840Q240,840 240,840Q240,840 240,840L640,840Q640,840 640,840Q640,840 640,840L640,800L240,800ZM598,640L428,470L484,414L598,528L824,302L880,358L598,640ZM240,160L640,160L640,120Q640,120 640,120Q640,120 640,120L240,120Q240,120 240,120Q240,120 240,120L240,160ZM240,160L240,120Q240,120 240,120Q240,120 240,120L240,120Q240,120 240,120Q240,120 240,120L240,160ZM240,800L240,800L240,840Q240,840 240,840Q240,840 240,840L240,840Q240,840 240,840Q240,840 240,840L240,800Z" />
+</vector>
  
  
  
    
    @@ -0,0 +1,5 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="48dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="48dp">
+      
+    <path android:fillColor="@android:color/white" android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
+    
+</vector>
  
  
  
    
    @@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:left="7dp" android:top="0dp">
+    <item android:left="@dimen/bubble_avatar_distance" android:top="0dp">
         <shape android:shape="rectangle">
             <corners
                 android:radius="0dp"
@@ -17,7 +17,7 @@
             <solid android:color="?colorSurfaceContainerHigh" />
         </shape>
     </item>
-    <item android:gravity="top|left" android:width="7dp" android:height="7dp" android:top="0dp">
+    <item android:gravity="top|left" android:width="@dimen/bubble_avatar_distance" android:height="@dimen/bubble_avatar_distance" android:top="0dp">
         <vector
            android:width="24dp"
            android:height="24dp"
  
  
  
    
    @@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:right="7dp" android:bottom="0dp">
+    <item android:right="@dimen/bubble_avatar_distance" android:bottom="0dp">
         <shape android:shape="rectangle">
             <corners
                 android:radius="0dp"
@@ -17,7 +17,7 @@
             <solid android:color="?colorSurface" />
         </shape>
     </item>
-    <item android:bottom="0dp" android:gravity="bottom|right" android:width="7dp" android:height="7dp">
+    <item android:bottom="0dp" android:gravity="bottom|right" android:width="@dimen/bubble_avatar_distance" android:height="@dimen/bubble_avatar_distance">
         <vector
            android:width="24dp"
            android:height="24dp"
  
  
  
    
    @@ -0,0 +1,25 @@
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <com.google.android.material.appbar.AppBarLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <com.google.android.material.appbar.MaterialToolbar
+                android:id="@+id/toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:minHeight="?attr/actionBarSize" />
+
+        </com.google.android.material.appbar.AppBarLayout>
+
+        <androidx.emoji2.emojipicker.EmojiPickerView
+            android:id="@+id/emoji_picker"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+    </LinearLayout>
+</layout>
  
  
  
    
    @@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<layout xmlns:android="http://schemas.android.com/apk/res/android">
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
 
     <RelativeLayout
         android:layout_width="match_parent"
@@ -19,18 +20,19 @@
         </com.google.android.material.appbar.AppBarLayout>
 
 
-        <LinearLayout
-            android:layout_width="fill_parent"
+        <ScrollView
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_below="@id/app_bar_layout"
-            android:layout_marginLeft="@dimen/activity_horizontal_margin"
-            android:layout_marginTop="@dimen/activity_vertical_margin"
-            android:layout_marginRight="@dimen/activity_horizontal_margin"
-            android:layout_marginBottom="@dimen/activity_vertical_margin">
+            android:layout_above="@+id/button_bar"
+            android:layout_below="@id/app_bar_layout">
 
             <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/activity_horizontal_margin"
+                android:layout_marginTop="@dimen/activity_vertical_margin"
+                android:layout_marginRight="@dimen/activity_horizontal_margin"
+                android:layout_marginBottom="@dimen/activity_vertical_margin"
                 android:gravity="center_horizontal"
                 android:orientation="vertical">
 
@@ -63,17 +65,25 @@
                     android:text="@string/or_long_press_for_default"
                     android:textAppearance="?textAppearanceBodyMedium" />
 
+                <com.google.android.material.materialswitch.MaterialSwitch
+                    android:id="@+id/contact_only"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginVertical="12dp"
+                    android:text="@string/show_to_contacts_only" />
+
                 <TextView
                     android:id="@+id/hint_or_warning"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:layout_marginTop="8dp"
-                    android:layout_marginBottom="8dp"
+                    android:layout_marginVertical="12dp"
                     android:textAppearance="?textAppearanceBodyMedium"
-                    android:textColor="?colorError" />
-
+                    android:textColor="?colorError"
+                    tools:text="@string/error_saving_avatar" />
             </LinearLayout>
-        </LinearLayout>
+
+
+        </ScrollView>
 
         <RelativeLayout
             android:id="@+id/button_bar"
  
  
  
    
    @@ -79,6 +79,41 @@
             android:layout_above="@+id/button_row"
             android:layout_below="@id/app_bar_layout">
 
+            <com.google.android.material.card.MaterialCardView
+                android:id="@+id/support_warning"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerHorizontal="true"
+                android:layout_marginHorizontal="24dp"
+                android:layout_marginTop="@dimen/rtp_session_duration_top_margin"
+                android:visibility="gone"
+                app:cardBackgroundColor="?colorErrorContainer">
+
+                <RelativeLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:padding="12dp">
+
+                    <ImageView
+                        android:id="@+id/no_support_av_icon"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_centerVertical="true"
+                        android:src="@drawable/ic_warning_48dp"
+                        app:tint="?colorOnErrorContainer" />
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_centerVertical="true"
+                        android:layout_marginStart="16dp"
+                        android:layout_toEndOf="@+id/no_support_av_icon"
+                        android:text="@string/clients_may_not_support_av"
+                        android:textAppearance="?textAppearanceBodyLarge"
+                        android:textColor="?colorOnErrorContainer" />
+                </RelativeLayout>
+            </com.google.android.material.card.MaterialCardView>
+
             <TextView
                 android:id="@+id/duration"
                 android:layout_width="wrap_content"
@@ -121,7 +156,8 @@
             <eu.siacs.conversations.ui.widget.SurfaceViewRenderer
                 android:id="@+id/remote_video"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
+                android:layout_height="wrap_content"
+                android:soundEffectsEnabled="false" />
         </LinearLayout>
 
         <eu.siacs.conversations.ui.widget.SurfaceViewRenderer
@@ -132,6 +168,7 @@
             android:layout_alignParentEnd="true"
             android:layout_marginTop="24dp"
             android:layout_marginEnd="24dp"
+            android:soundEffectsEnabled="false"
             android:visibility="gone"
             app:elevation="4dp" />
 
@@ -170,7 +207,8 @@
             <RelativeLayout
                 android:layout_width="288dp"
                 android:layout_height="wrap_content"
-                android:layout_centerInParent="true">
+                android:layout_centerInParent="true"
+                android:background="@android:color/transparent">
 
                 <com.google.android.material.floatingactionbutton.FloatingActionButton
                     android:id="@+id/reject_call"
  
  
  
    
    @@ -1,18 +1,31 @@
 <?xml version="1.0" encoding="utf-8"?>
-<layout xmlns:android="http://schemas.android.com/apk/res/android">
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
 
     <RelativeLayout
         android:layout_width="wrap_content"
-        android:layout_height="match_parent">
+        android:layout_height="wrap_content"
+        android:padding="?dialogPreferredPadding">
 
-    <com.google.android.material.button.MaterialButtonGroup
-        android:layout_centerInParent="true"
+        <com.google.android.material.button.MaterialButtonGroup
+            android:id="@+id/emojis"
+
+            style="@style/Widget.Material3.MaterialButtonGroup.Connected"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true" />
+
+        <Button
+            android:id="@+id/more"
+            style="?attr/materialIconButtonFilledStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/emojis"
+            android:layout_centerHorizontal="true"
+            android:layout_marginTop="8dp"
+            android:contentDescription="@string/more_reactions"
+            app:icon="@drawable/ic_more_horiz_24dp" />
 
-        android:id="@+id/emojis"
-        style="@style/Widget.Material3.MaterialButtonGroup.Connected"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:padding="?dialogPreferredPadding" />
     </RelativeLayout>
 
 </layout>
  
  
  
    
    @@ -65,6 +65,7 @@
             android:paddingTop="4dp"
             android:autoLink="web"
             android:longClickable="false"
+            android:breakStrategy="simple"
             android:textAppearance="?textAppearanceBodyMedium" />
 
         <com.google.android.material.button.MaterialButton
  
  
  
    
    @@ -5,37 +5,29 @@
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:paddingHorizontal="8dp"
-        android:paddingVertical="4dp">
-
-        <LinearLayout
-            android:id="@+id/message_photo_box"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:layout_constraintBottom_toBottomOf="@id/message_box"
-            app:layout_constraintEnd_toEndOf="parent"
-            android:layout_marginStart="0dp"
-            android:orientation="vertical">
+        android:paddingHorizontal="@dimen/bubble_horizontal_padding"
+        android:paddingVertical="@dimen/bubble_vertical_padding">
 
         <com.google.android.material.imageview.ShapeableImageView
                 android:id="@+id/message_photo"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
+                android:layout_width="@dimen/bubble_avatar_size"
+                android:layout_height="@dimen/bubble_avatar_size"
+                app:layout_constraintBottom_toBottomOf="@id/message_box"
+                app:layout_constraintEnd_toEndOf="parent"
                 android:scaleType="centerCrop"
                 app:shapeAppearance="@style/ShapeAppearanceOverlay.Photo" />
-        </LinearLayout>
 
         <LinearLayout
             android:id="@+id/message_box"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:paddingRight="6dp"
             android:background="@drawable/message_bubble_sent"
             android:backgroundTint="?colorSecondaryContainer"
             android:longClickable="true"
-            android:minHeight="48dp"
-            android:paddingRight="7dp"
+            android:minHeight="@dimen/bubble_avatar_size"
             app:layout_constrainedWidth="true"
-            app:layout_constraintEnd_toStartOf="@id/message_photo_box"
+            app:layout_constraintEnd_toStartOf="@id/message_photo"
             app:layout_constraintHorizontal_bias="1.0"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent">
@@ -147,8 +139,11 @@
             android:orientation="horizontal"
             android:visibility="visible"
             app:chipSpacingHorizontal="2dp"
+            app:chipSpacingVertical="4dp"
+            app:layout_constrainedWidth="true"
             app:layout_constraintEnd_toEndOf="@+id/message_box"
-            app:layout_constraintHorizontal_bias="0.0"
+            app:layout_constraintHorizontal_bias="1.0"
+            app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@+id/reactions_anchor" />
 
     </androidx.constraintlayout.widget.ConstraintLayout>
  
  
  
    
    @@ -6,13 +6,13 @@
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:paddingHorizontal="8dp"
-        android:paddingVertical="4dp">
+        android:paddingHorizontal="@dimen/bubble_horizontal_padding"
+        android:paddingVertical="@dimen/bubble_vertical_padding">
 
         <com.google.android.material.imageview.ShapeableImageView
             android:id="@+id/message_photo"
-            android:layout_width="48dp"
-            android:layout_height="48dp"
+            android:layout_width="@dimen/bubble_avatar_size"
+            android:layout_height="@dimen/bubble_avatar_size"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="@id/message_box"
             android:layout_marginEnd="0dp"
@@ -24,11 +24,11 @@
             android:id="@+id/message_box"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:paddingLeft="6dp"
             android:background="@drawable/message_bubble_received"
             android:backgroundTint="?colorTertiaryContainer"
             android:longClickable="true"
-            android:paddingLeft="7dp"
-            android:minHeight="48dp"
+            android:minHeight="@dimen/bubble_avatar_size"
             app:layout_constrainedWidth="true"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintHorizontal_bias="0.0"
@@ -99,6 +99,16 @@
                         android:src="@drawable/ic_edit_24dp"
                         app:tint="?colorOnTertiaryContainer" />
 
+                    <ImageView
+                        android:id="@+id/indicator_received"
+                        android:layout_width="16sp"
+                        android:layout_height="16sp"
+                        android:layout_gravity="center_vertical"
+                        android:layout_marginEnd="4sp"
+                        android:gravity="center_vertical"
+                        android:src="@drawable/ic_done_24dp"
+                        app:tint="?colorOnTertiaryContainer" />
+
                     <com.lelloman.identicon.view.GithubIdenticonView
                         android:id="@+id/thread_identicon"
                         android:background="@drawable/ic_thread"
@@ -140,6 +150,9 @@
             android:orientation="horizontal"
             android:visibility="visible"
             app:chipSpacingHorizontal="2dp"
+            app:chipSpacingVertical="4dp"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintHorizontal_bias="0.0"
             app:layout_constraintStart_toStartOf="@+id/message_box"
             app:layout_constraintTop_toBottomOf="@+id/reactions_anchor" />
  
  
  
    
    @@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
-      xmlns:app="http://schemas.android.com/apk/res-auto">
+    xmlns:app="http://schemas.android.com/apk/res-auto">
 
     <item
         android:id="@+id/action_edit_contact"
@@ -21,52 +21,58 @@
         android:id="@+id/action_share"
         android:icon="@drawable/ic_share_24dp"
         android:orderInCategory="15"
-        app:showAsAction="always"
-        android:title="@string/share_uri_with">
+        android:title="@string/share_uri_with"
+        app:showAsAction="always">
         <menu>
             <item
                 android:id="@+id/action_share_uri"
-                android:title="@string/share_as_uri"/>
+                android:title="@string/share_as_uri" />
             <item
                 android:id="@+id/action_share_http"
-                android:title="@string/share_as_http"/>
+                android:title="@string/share_as_http" />
             <item
                 android:id="@+id/action_show_qr_code"
-                android:title="@string/show_qr_code"/>
+                android:title="@string/show_qr_code" />
         </menu>
     </item>
     <item
         android:id="@+id/action_delete_contact"
         android:orderInCategory="10"
-        app:showAsAction="never"
-        android:title="@string/action_delete_contact"/>
+        android:title="@string/action_delete_contact"
+        app:showAsAction="never" />
 
     <item
         android:id="@+id/action_block"
         android:orderInCategory="72"
-        app:showAsAction="never"
-        android:title="@string/action_block_contact"/>
+        android:title="@string/action_block_contact"
+        app:showAsAction="never" />
 
     <item
         android:id="@+id/action_unblock"
         android:orderInCategory="73"
-        app:showAsAction="never"
-        android:title="@string/action_unblock_contact"/>
+        android:title="@string/action_unblock_contact"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/action_custom_notifications"
+        android:orderInCategory="75"
+        android:title="@string/custom_notifications"
+        app:showAsAction="never" />
 
     <item
         android:id="@+id/action_accounts"
         android:orderInCategory="90"
-        app:showAsAction="never"
-        android:title="@string/action_accounts"/>
+        android:title="@string/action_accounts"
+        app:showAsAction="never" />
     <item
         android:id="@+id/action_account"
         android:orderInCategory="90"
         android:title="@string/action_account"
-        app:showAsAction="never"/>
+        app:showAsAction="never" />
     <item
         android:id="@+id/action_settings"
         android:orderInCategory="100"
-        app:showAsAction="never"
-        android:title="@string/action_settings"/>
+        android:title="@string/action_settings"
+        app:showAsAction="never" />
 
 </menu>
  
  
  
    
    @@ -26,6 +26,11 @@
         android:title="@string/copy_to_clipboard"
         android:visible="false" />
 
+    <item
+        android:id="@+id/copy_link"
+        android:title="@string/copy_link"
+        android:visible="false" />
+
     <item
         android:id="@+id/quote_message"
         android:title="@string/reply"
  
  
  
    
    @@ -1,57 +1,62 @@
 <?xml version="1.0" encoding="utf-8"?>
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
-      xmlns:app="http://schemas.android.com/apk/res-auto">
+    xmlns:app="http://schemas.android.com/apk/res-auto">
 
     <item
         android:id="@+id/action_share"
         android:icon="@drawable/ic_share_24dp"
         android:orderInCategory="15"
-        app:showAsAction="ifRoom"
-        android:title="@string/share_uri_with">
+        android:title="@string/share_uri_with"
+        app:showAsAction="ifRoom">
         <menu>
             <item
                 android:id="@+id/action_share_uri"
-                android:title="@string/share_as_uri"/>
+                android:title="@string/share_as_uri" />
             <item
                 android:id="@+id/action_share_http"
-                android:title="@string/share_as_http"/>
+                android:title="@string/share_as_http" />
             <item
                 android:id="@+id/action_show_qr_code"
-                android:title="@string/show_qr_code"/>
+                android:title="@string/show_qr_code" />
         </menu>
     </item>
 
     <item
         android:id="@+id/action_save_as_bookmark"
         android:orderInCategory="80"
-        app:showAsAction="never"
-        android:title="@string/save_as_bookmark"/>
+        android:title="@string/save_as_bookmark"
+        app:showAsAction="never" />
     <item
         android:id="@+id/action_destroy_room"
         android:orderInCategory="82"
-        app:showAsAction="never"
-        android:title="@string/destroy_room"/>
+        android:title="@string/destroy_room"
+        app:showAsAction="never" />
     <item
         android:id="@+id/action_advanced_mode"
         android:checkable="true"
         android:checked="false"
         android:orderInCategory="85"
-        app:showAsAction="never"
-        android:title="@string/advanced_mode"/>
+        android:title="@string/advanced_mode"
+        app:showAsAction="never" />
+    <item
+        android:id="@+id/action_custom_notifications"
+        android:orderInCategory="89"
+        android:title="@string/custom_notifications"
+        app:showAsAction="never" />
     <item
         android:id="@+id/action_accounts"
         android:orderInCategory="90"
-        app:showAsAction="never"
-        android:title="@string/action_accounts"/>
+        android:title="@string/action_accounts"
+        app:showAsAction="never" />
     <item
         android:id="@+id/action_account"
         android:orderInCategory="90"
         android:title="@string/action_account"
-        app:showAsAction="never"/>
+        app:showAsAction="never" />
     <item
         android:id="@+id/action_settings"
         android:orderInCategory="100"
-        app:showAsAction="never"
-        android:title="@string/action_settings"/>
+        android:title="@string/action_settings"
+        app:showAsAction="never" />
 
 </menu>
  
  
  
    
    
  
  
  
    
    @@ -92,10 +92,10 @@
     <string name="send_message_to_x">Odeslat zprávu pro %s</string>
     <string name="send_omemo_x509_message">Odeslat v\\OMEMO šifrovanou zprávu</string>
     <string name="your_nick_has_been_changed">Přezdívka změněna</string>
-    <string name="send_unencrypted">Poslat nešifrované</string>
+    <string name="send_unencrypted">Odeslat nešifrovaný text</string>
     <string name="decryption_failed">Zašifrování se nezdařilo. Možná nemáte správný privátní klíč.</string>
     <string name="openkeychain_required">OpenKeychain</string>
-    <string name="openkeychain_required_long">%1$s používá <b>OpenKeychain</b> k šifrování a dešifrování zpráv a ke správě Vašich veřejných klíčů.<br><br>OpenKeychain je vydána pod licencí GPLv3+ a dostupná na F-Droid nebo Google Play.<br><br><small>(Po instalaci, prosím, restartujte %1$s.)</small></string>
+    <string name="openkeychain_required_long"><![CDATA[%1$s používá <b>OpenKeychain</b> k šifrování a dešifrování zpráv a ke správě Vašich veřejných klíčů.<br><br>OpenKeychain je vydána pod licencí GPLv3+ a dostupná na F-Droid nebo Google Play.<br><br><small>(Po instalaci, prosím, restartujte %1$s.)</small>]]></string>
     <string name="restart">Restartovat</string>
     <string name="install">Instalovat</string>
     <string name="openkeychain_not_installed">Nainstalujte prosím OpenKeychain</string>
@@ -499,7 +499,7 @@
     <string name="shared_text_with_x">Text sdílen s %s</string>
     <string name="no_storage_permission">Povolit %1$s přístup k externímu úložišti</string>
     <string name="no_camera_permission">Povolit %1$s přístup ke kameře</string>
-    <string name="sync_with_contacts">Synchronizovat s kontakty</string>
+    <string name="sync_with_contacts">Integrace seznamu kontaktů</string>
     <string name="sync_with_contacts_long">%1$s zpracovává Váš seznam kontaktů místně na Vašem zařízení za účelem zobrazení jmen a profilových obrázků odpovídajících kontaktů na XMPP.
 \n
 \n
@@ -555,7 +555,7 @@
     <string name="gp_medium">Střední</string>
     <string name="gp_long">Dlouhý</string>
     <string name="pref_broadcast_last_activity">Naposledy spatřen</string>
-    <string name="pref_broadcast_last_activity_summary">Umožnit svým kontaktům vidět, kdy jste naposledy použili aplikaci</string>
+    <string name="pref_broadcast_last_activity_summary">Umožnit kontaktům vidět, kdy jste byli v aplikaci naposledy aktivní</string>
     <string name="pref_privacy">Soukromí</string>
     <string name="pref_theme_options">Vzhled</string>
     <string name="pref_theme_options_summary">Vybrat paletu barev</string>
@@ -1074,7 +1074,7 @@
     <string name="call_integration_not_available">Integrace hovorů není k dispozici!</string>
     <string name="pref_connection_summary_w_cd">Název hostitele a port, Tor, objevování kanálů</string>
     <string name="pref_connection_summary">Název hostitele a port, Tor</string>
-    <string name="pref_backup_summary">Vytvořit jednou, naplánovat další</string>
+    <string name="pref_backup_summary">Vytvořit jednorázově, naplánovat pravidelné zálohy</string>
     <string name="pref_up_long_summary">Pokud je funkce distribuce UnifiedPush povolena, spolehlivé a na baterii nenáročné XMPP spojení bude využíváno k probouzení jiných aplikací kompatibilních s UnifiedPush, jako jsou např. Tusky, Ltt.rs, FluffyChat a další.</string>
     <string name="pref_category_application">Aplikace</string>
     <string name="pref_category_interaction">Interakce</string>
@@ -1088,4 +1088,48 @@
     <string name="pref_accept_invites_from_strangers_summary">Přijímat pozvánky do skupinových chatů od neznámých kontaktů</string>
     <string name="pref_large_font">Velké písmo</string>
     <string name="pref_large_font_summary">Zvětšit písmo v chatových bublinách</string>
+    <string name="server_info_bind2">XEP-0386: Bind 2</string>
+    <string name="server_info_sasl2">XEP-0388: Extensible SASL Profile</string>
+    <string name="add_reaction_title">Přidat reakci</string>
+    <string name="more_reactions">Více reakcí</string>
+    <string name="rtp_state_retracted">Odvolaný hovor</string>
+    <string name="pref_category_receiving">Přijímané</string>
+    <string name="edit_nick">Upravit přezdívku</string>
+    <string name="pref_category_sending">Odesílané</string>
+    <string name="pref_category_server_connection">Připojení k serveru</string>
+    <string name="could_not_modify_call">Nebylo možné upravit hovor</string>
+    <string name="your_avatar_tap_to_select_new_avatar">Váš avatar. Klepnutím vyberte nový avatar z galerie.</string>
+    <string name="video_is_disabled_tap_to_enable">Video je vypnuto. Klepněte pro zapnutí.</string>
+    <string name="pref_category_operating_system">Operační systém</string>
+    <string name="pref_category_engagement_notifications">Oznámení o aktivitě</string>
+    <string name="allow_private_messages">Povolit soukromé zprávy</string>
+    <string name="delete_pgp_key">Smazat OpenPGP klíč</string>
+    <string name="edit_name_and_topic">Upravit název a téma</string>
+    <string name="edit_configuration">Změnit konfiguraci</string>
+    <string name="change_notification_settings">Změnit nastavení upozornění</string>
+    <string name="call_is_using_earpiece">Hovor probíhá přes sluchátko telefonu.</string>
+    <string name="call_is_using_wired_headset">Hovor probíhá přes drátová sluchátka s mikrofonem</string>
+    <string name="call_is_using_speaker_tap_to_switch_to_earpiece">Hovor probíhá přes spodní reproduktor. Klepnutím přepnete na sluchátko telefonu.</string>
+    <string name="call_is_using_speaker">Hovor probíhá přes spodní reproduktor.</string>
+    <string name="call_is_using_bluetooth">Hovor probíhá přes bluetooth.</string>
+    <string name="flip_camera">Změnit kameru</string>
+    <string name="video_is_enabled_tap_to_disable">Video je zapnuto. Klepněte pro vypnutí.</string>
+    <string name="clients_may_not_support_av">XMPP klient vašeho kontaktu nemusí podporovat hlasové a videohovory.</string>
+    <string name="could_not_add_reaction">Nebylo možné přidat reakci</string>
+    <string name="add_reaction">Přidat reakci…</string>
+    <string name="pref_align_start">Zprávy zarovnané doleva</string>
+    <string name="pref_align_start_summary">Zobrazit všechny zprávy, včetně odeslaných, na levé straně pro jednotné rozvržení chatu.</string>
+    <string name="custom_notifications">Vlastní oznámení</string>
+    <string name="custom_notifications_enable">Povolit vlastní oznámení (důležitost, zvuk, vibrace) pro tuto konverzaci?</string>
+    <string name="show_to_contacts_only">Zobrazit pouze kontaktům</string>
+    <string name="pref_show_avatars">Zobrazení avatarů</string>
+    <string name="pref_show_avatars_summary">Kromě skupinových chatů zobrazit avatary také u vašich zpráv a v chatech 1:1.</string>
+    <string name="pref_chat_bubbles">Bubliny chatu</string>
+    <string name="pref_chat_bubbles_summary">Barva pozadí, velikost písma, avatary</string>
+    <string name="pref_title_bubbles">Bubliny chatu</string>
+    <string name="could_not_disable_video">Nebylo možné zakázat video.</string>
+    <string name="call_is_using_earpiece_tap_to_switch_to_speaker">Hovor probíhá přes sluchátko telefonu. Klepnutím přepnete na spodní reproduktor.</string>
+    <string name="pref_call_integration">Integrace hovorů</string>
+    <string name="pref_call_integration_summary">Hovory z této aplikace interagují s běžnými telefonními hovory, například ukončení jednoho hovoru, když začne další.</string>
+    <string name="delete_avatar_message">Chcete smazat svůj avatar? Některé aplikace mohou i nadále zobrazovat uloženou kopii vašeho avataru.</string>
 </resources>
  
  
  
    
    @@ -403,7 +403,7 @@
     <string name="mark_as_read">Als gelesen markieren</string>
     <string name="pref_input_options">Eingabe</string>
     <string name="pref_enter_is_send">Eingabetaste sendet Nachricht</string>
-    <string name="pref_enter_is_send_summary">Nutze die Eingabetaste zum Versenden einer Nachricht. Strg+Eingabetaste sendet die Nachricht unabhängig von dieser Einstellung.</string>
+    <string name="pref_enter_is_send_summary">Eingabetaste zum Versenden einer Nachricht nutzen. Strg+Eingabetaste sendet die Nachricht unabhängig von dieser Einstellung.</string>
     <string name="pref_display_enter_key">Eingabetaste anzeigen</string>
     <string name="pref_display_enter_key_summary">Emoji-Taste durch Eingabetaste ersetzen</string>
     <string name="audio">Audio</string>
@@ -441,7 +441,7 @@
         <item quantity="one">%d Zertifikat gelöscht</item>
         <item quantity="other">%d Zertifikate gelöscht</item>
     </plurals>
-    <string name="pref_quick_action_summary">Ersetze \"Senden\"-Schaltfläche durch Schnell-Tasten</string>
+    <string name="pref_quick_action_summary">\"Senden\"-Schaltfläche durch Schnell-Tasten ersetzen</string>
     <string name="pref_quick_action">Schnell-Tasten</string>
     <string name="none">Keine</string>
     <string name="recently_used">Zuletzt verwendet</string>
@@ -459,7 +459,7 @@
     <string name="download_failed_invalid_file">Download fehlgeschlagen: Ungültige Datei</string>
     <string name="account_status_tor_unavailable">Tor-Netzwerk nicht verfügbar</string>
     <string name="account_status_bind_failure">Verbindungsfehler</string>
-    <string name="account_status_host_unknown">Der Server ist nicht für diese Domain verantwortlich</string>
+    <string name="account_status_host_unknown">Nicht für Domain verantwortlich</string>
     <string name="server_info_broken">Fehlerhaft</string>
     <string name="pref_presence_settings">Status</string>
     <string name="pref_away_when_screen_off">Abwesend bei gesperrtem Gerät</string>
@@ -753,7 +753,7 @@
     <string name="delivery_failed_channel_name">Fehlgeschlagene Zustellungen</string>
     <string name="pref_message_notification_settings">Benachrichtigungseinstellungen</string>
     <string name="pref_incoming_call_notification_settings">Einstellungen für die Benachrichtigung bei eingehenden Anrufen</string>
-    <string name="pref_more_notification_settings_summary">Wichtigkeit, Klang, Vibrationen</string>
+    <string name="pref_more_notification_settings_summary">Wichtigkeit, Ton, Vibration</string>
     <string name="video_compression_channel_name">Video komprimieren</string>
     <string name="view_media">Medien anzeigen</string>
     <string name="group_chat_members">Teilnehmer</string>
@@ -830,7 +830,7 @@
     <string name="backup_channel_name">Sicherung & Wiederherstellung</string>
     <string name="enter_jabber_id">XMPP-Adresse eingeben</string>
     <string name="create_group_chat">Gruppenchat erstellen</string>
-    <string name="join_public_channel">Öffentlichen Channel beitreten</string>
+    <string name="join_public_channel">Öffentlichem Channel beitreten</string>
     <string name="create_private_group_chat">Privaten Gruppenchat erstellen</string>
     <string name="create_public_channel">Öffentlichen Channel erstellen</string>
     <string name="create_dialog_channel_name">Channelname</string>
@@ -1012,7 +1012,7 @@
     <string name="remove_bookmark">Willst du das Lesezeichen für %s entfernen?</string>
     <string name="title_activity_share_with">Teilen mit…</string>
     <string name="pref_use_colorful_bubbles_summary">Unterschiedliche Hintergrundfarben für gesendete und empfangene Nachrichten</string>
-    <string name="pref_use_colorful_bubbles">Farbige Chatblasen</string>
+    <string name="pref_use_colorful_bubbles">Farbige Sprechblasen</string>
     <string name="pref_dynamic_colors">Dynamische Farben</string>
     <string name="pref_dynamic_colors_summary">Systemfarben (Material You)</string>
     <string name="start_chat">Chat starten</string>
@@ -1091,4 +1091,22 @@
     <string name="server_info_login_mechanism">Anmeldeverfahren</string>
     <string name="could_not_add_reaction">Reaktion konnte nicht hinzugefügt werden</string>
     <string name="add_reaction">Reaktion hinzufügen…</string>
+    <string name="add_reaction_title">Reaktion hinzufügen</string>
+    <string name="more_reactions">Weitere Reaktionen</string>
+    <string name="could_not_modify_call">Anruf konnte nicht geändert werden</string>
+    <string name="clients_may_not_support_av">Der XMPP-Client deines Kontakts unterstützt möglicherweise keine Audio-/Videoanrufe.</string>
+    <string name="pref_chat_bubbles">Sprechblasen</string>
+    <string name="pref_show_avatars">Profilbilder anzeigen</string>
+    <string name="pref_show_avatars_summary">Profilbilder in deinen Nachrichten und in 1:1 Chats statt nur in Gruppenchats anzeigen.</string>
+    <string name="pref_chat_bubbles_summary">Hintergrundfarbe, Schriftgröße, Profilbilder</string>
+    <string name="pref_title_bubbles">Sprechblasen</string>
+    <string name="pref_call_integration">Anrufintegration</string>
+    <string name="pref_align_start">Linksbündige Nachrichten</string>
+    <string name="pref_align_start_summary">Alle Nachrichten, einschließlich der gesendeten, auf der linken Seite anzeigen, um ein einheitliches Chatlayout zu erhalten.</string>
+    <string name="custom_notifications">Individuelle Benachrichtigungen</string>
+    <string name="pref_call_integration_summary">Anrufe aus dieser App interagieren mit normalen Telefonanrufen, wie beispielsweise das Beenden eines Anrufs, wenn ein anderer beginnt.</string>
+    <string name="custom_notifications_enable">Individuelle Benachrichtigungseinstellungen (Wichtigkeit, Ton, Vibration) für diese Unterhaltung aktivieren?</string>
+    <string name="delete_avatar_message">Möchtest du deinen Profilbild löschen? Einige Clients zeigen möglicherweise weiterhin eine zwischengespeicherte Kopie deines Profilbildes an.</string>
+    <string name="show_to_contacts_only">Nur für Kontakte anzeigen</string>
+    <string name="account_status_connection_timeout">Zeitüberschreitung beim Verbinden</string>
 </resources>
  
  
  
    
    @@ -461,7 +461,7 @@
     <string name="download_failed_invalid_file">Error al descargar: Archivo no válido</string>
     <string name="account_status_tor_unavailable">La red Tor no está disponible</string>
     <string name="account_status_bind_failure">Fallo de enlace</string>
-    <string name="account_status_host_unknown">El servidor no es responsable de este dominio</string>
+    <string name="account_status_host_unknown">No responsable del dominio</string>
     <string name="server_info_broken">Error</string>
     <string name="pref_presence_settings">Disponibilidad</string>
     <string name="pref_away_when_screen_off">Ausente cuando el dispositivo esté bloqueado</string>
@@ -853,7 +853,7 @@
     <string name="unable_to_set_channel_configuration">No se ha podido guardar la configuración del canal</string>
     <string name="allow_participants_to_edit_subject">Permitir a cualquiera editar el asunto</string>
     <string name="allow_participants_to_invite_others">Permitir a cualquiera invitar a otros contactos</string>
-    <string name="anyone_can_edit_subject">Cualquiera puede editar el tema.</string>
+    <string name="anyone_can_edit_subject">Cualquiera puede editar el asunto.</string>
     <string name="owners_can_edit_subject">Los propietarios pueden editar el asunto.</string>
     <string name="admins_can_edit_subject">Los administradores pueden editar el asunto.</string>
     <string name="owners_can_invite_others">Los propietarios pueden invitar a otros contactos.</string>
@@ -1084,4 +1084,43 @@
     <string name="pref_fullscreen_notification">Notificaciones a pantalla completa</string>
     <string name="pref_fullscreen_notification_summary">Permite que esta aplicación muestre notificaciones de llamadas entrantes que ocupan toda la pantalla cuando el dispositivo está bloqueado.</string>
     <string name="allow_private_messages">Permitir mensajes privados</string>
+    <string name="more_reactions">Más reacciones</string>
+    <string name="your_avatar_tap_to_select_new_avatar">Tu avatar. Toca para seleccionar un nuevo avatar de la galería.</string>
+    <string name="server_info_bind2">XEP-0386: Vinculación 2</string>
+    <string name="server_info_sasl2">XEP-0388: Perfil SASL Extensible</string>
+    <string name="could_not_disable_video">No se pudo desactivar el video.</string>
+    <string name="flip_camera">Voltear la cámara</string>
+    <string name="video_is_enabled_tap_to_disable">Video activado. Toca para desactivar.</string>
+    <string name="edit_nick">Editar apodo</string>
+    <string name="delete_pgp_key">Eliminar clave OpenPGP</string>
+    <string name="edit_name_and_topic">Editar nombre y tema</string>
+    <string name="edit_configuration">Cambiar configuración</string>
+    <string name="change_notification_settings">Cambiar la configuración de notificaciones</string>
+    <string name="call_is_using_earpiece_tap_to_switch_to_speaker">La llamada está utilizando el auricular. Toca para cambiar al altavoz.</string>
+    <string name="call_is_using_earpiece">La llamada está utilizando el auricular.</string>
+    <string name="call_is_using_wired_headset">La llamada está utilizando auriculares con cable</string>
+    <string name="call_is_using_speaker_tap_to_switch_to_earpiece">La llamada está utilizando altavoz. Toca para cambiar a auricular.</string>
+    <string name="call_is_using_speaker">La llamada está usando el altavoz.</string>
+    <string name="call_is_using_bluetooth">La llamada está usando bluetooth.</string>
+    <string name="video_is_disabled_tap_to_enable">Video desactivado. Toca para activar.</string>
+    <string name="server_info_login_mechanism">Apartado de inicio de sesión</string>
+    <string name="could_not_add_reaction">No se pudo agregar la reacción</string>
+    <string name="add_reaction">Agregar reacción…</string>
+    <string name="add_reaction_title">Agregar reacción</string>
+    <string name="clients_may_not_support_av">El cliente XMPP de tu contacto puede que no soporte llamadas de audio/video.</string>
+    <string name="could_not_modify_call">No se pudo modificar la llamada</string>
+    <string name="pref_chat_bubbles">Burbujas de chat</string>
+    <string name="pref_chat_bubbles_summary">Color, Tamaño de fuente, Imágenes de perfil</string>
+    <string name="pref_title_bubbles">Burbujas de Chat</string>
+    <string name="pref_call_integration">Integración de llamadas</string>
+    <string name="pref_show_avatars">Ver imágenes de perfil</string>
+    <string name="pref_show_avatars_summary">Mostrar imágenes de perfil para tus mensajes y conversaciones 1:1, aparte de las conversaciones en grupo.</string>
+    <string name="pref_call_integration_summary">Las llamadas desde esta app interactúan con las llamadas telefónicas regulares, como ser finalizar una llamada cuando recibimos otra.</string>
+    <string name="pref_align_start">Mensajes alineados a la izquierda</string>
+    <string name="pref_align_start_summary">Mostrar todos los mensajes, incluso los propios, sobre el margen izquierdo para una distribución uniforme del chat.</string>
+    <string name="custom_notifications">Notificaciones personalizadas</string>
+    <string name="custom_notifications_enable">¿Habilitar los ajustes de notificaciones personalizadas (importancia, sonido, vibración) para esta conversación?</string>
+    <string name="delete_avatar_message">¿Quieres eliminar tu imagen de perfil? Algunos clientes podrían seguir mostrando una copia en caché de tu avatar.</string>
+    <string name="show_to_contacts_only">Mostrar sólo a contactos</string>
+    <string name="account_status_connection_timeout">Se agotó el tiempo de espera de la conexión</string>
 </resources>
  
  
  
    
    @@ -6,7 +6,7 @@
     <string name="title_activity_choose_contacts">Vali kontaktid</string>
     <string name="minute_ago">1 minut tagasi</string>
     <string name="action_contact_details">Kontaktandmed</string>
-    <string name="action_muc_details">Rühmavestluse üksikasjad</string>
+    <string name="action_muc_details">Vestlusrühma üksikasjad</string>
     <string name="channel_details">Kanali teave</string>
     <string name="action_delete_contact">Kustuta serveri kontaktiloendist</string>
     <string name="action_block_contact">Blokeeri kontakt</string>
@@ -114,7 +114,7 @@
     <string name="attach_file">Lisa fail</string>
     <string name="block_contact_text">Kas sa soovid, et kasutaja %s oleks blokeeritud ega saaks sulle sõnumeid saata?</string>
     <string name="send_failed">kohaletoimetamine ei õnnestunud</string>
-    <string name="openkeychain_required_long">%1$s kasutab sõnumite krüptimiseks ja dekrüptimiseks ning sinu avalike võtmete haldamiseks rakendust <b>OpenKeychain</b>.<br><br>Ta on litsentseeritud GPLv3+ alusel ning leidub nii F-Droidi kui Google Play rakendustepoodides.<br><br><small>(Palun käivita %1$s hiljem uuesti.)</small></string>
+    <string name="openkeychain_required_long"><![CDATA[%1$s kasutab sõnumite krüptimiseks ja dekrüptimiseks ning sinu avalike võtmete haldamiseks rakendust <b>OpenKeychain</b>.<br><br>Ta on litsentseeritud GPLv3+ alusel ning leidub nii F-Droidi kui Google Play rakendustepoodides.<br><br><small>(Palun käivita %1$s hiljem uuesti.)</small>]]></string>
     <string name="restart">Käivita uuesti</string>
     <string name="install">Paigalda</string>
     <string name="openkeychain_not_installed">Palun paigalda rakendus OpenKeychain</string>
@@ -128,4 +128,1005 @@
     <string name="contacts_have_no_pgp_keys">Kuna sõnumi saajad pole avaldanud oma avalikke võtmeid, siis sinu sõnumi krüptimine ei õnnestu.
 \n
 \n<small>Palu, et nad seadistaks OpenPGP.</small></string>
+    <string name="pref_advanced_options">Täiendavad seadistused</string>
+    <string name="pref_send_crash_reports">Saada krahhiaruandeid</string>
+    <string name="pref_never_send_crash_summary">Saates rakenduse kokkujooksmise korral arendajatele teavet, aitad sa vigu parandada ja rakendust edasi arendada</string>
+    <string name="pref_confirm_messages">Lugemisteatised</string>
+    <string name="pref_confirm_messages_summary">Luba oma kontaktidel teada saada, kui oled saanud ja lugenud nende sõnumeid</string>
+    <string name="pref_prevent_screenshots">Keela ekraanitõmmised</string>
+    <string name="pref_prevent_screenshots_summary">Peida rakenduste vahetajast selle rakenduse sisu ja blokeeri ekraanitõmmiste tegemine</string>
+    <string name="pref_ui_options">Kasutajaliides</string>
+    <string name="openpgp_error">OpenKeychaini viga.</string>
+    <string name="bad_key_for_encryption">Võti ei sobi krüptimiseks.</string>
+    <string name="accept">Nõustu</string>
+    <string name="error">Tekkis viga</string>
+    <string name="recording_error">Viga</string>
+    <string name="your_account">Sinu kasutajakonto</string>
+    <string name="send_presence_updates">Saada olekuteateid</string>
+    <string name="receive_presence_updates">Luba vastu võtta teiste kasutajate olekuteateid</string>
+    <string name="ask_for_presence_updates">Küsi olekuteateid</string>
+    <string name="attach_choose_picture">Vali pilt</string>
+    <string name="attach_take_picture">Tee foto</string>
+    <string name="pref_notification_grace_period">Teavituste keelamise viiteaeg</string>
+    <string name="pref_notification_grace_period_summary">Kui mõnes muus sinu seadmes tuvastatakse mõni tegevus, siis selle aja möödumisel enam siin seadmes ei kuvata teavitusi.</string>
+    <string name="conference_unknown_error">Sa ei osale enam vestlusrühmas</string>
+    <string name="conference_technical_problems">Sa lahkusid sellest vestlusrühmast tehnilistel põhjustel</string>
+    <string name="contact_has_read_up_to_this_point">%s on siiamaani lugenud</string>
+    <string name="preemptively_grant">Ennetavalt luba tellimuste päringud</string>
+    <string name="error_not_an_image_file">Sinu valitud fail pole pilt</string>
+    <string name="error_compressing_image">Pildifaili konverteerimine ei õnnestunud</string>
+    <string name="error_file_not_found">Faili ei leidu</string>
+    <string name="error_io_exception">Üldine sisend/väljundviga. Võibolla on sinu nutiseadme andmeruum täis saanud?</string>
+    <string name="error_security_exception_during_image_copy">Rakendus, millega sa selle faili valisid, ei jaganud piisavalt õigusi faili lugemiseks.
+\n
+\n<small>Pildi valimiseks palun kasuta mõnda muud failihaldurit</small>.</string>
+    <string name="error_security_exception">Rakendus, mida sa kasutasid faili jagamisel, ei edastanud piisavalt õigusi faili lugemiseks.</string>
+    <string name="account_status_unknown">Teadmata</string>
+    <string name="account_status_disabled">Ajutiselt pole kasutusel</string>
+    <string name="account_state_logged_out">Väljalogitud</string>
+    <string name="account_status_online">Võrgus</string>
+    <string name="account_status_connecting">Ühendame…</string>
+    <string name="account_status_offline">Pole võrgus</string>
+    <string name="account_status_unauthorized">Pole lubatud</string>
+    <string name="account_status_not_found">Serverit ei leidu</string>
+    <string name="account_status_no_internet">Ühenduvust pole</string>
+    <string name="account_status_regis_fail">Registreerumine ei õnnestunud</string>
+    <string name="account_status_regis_conflict">Selline kasutajanimi on juba olemas</string>
+    <string name="account_status_regis_success">Registreerimine õnnestus</string>
+    <string name="account_status_regis_not_sup">Server ei toeta registreerumise võimalust</string>
+    <string name="account_status_regis_invalid_token">Vigane registreerimistunnus</string>
+    <string name="account_status_tls_error">TLS-ühenduse alustamine ei õnnestunud</string>
+    <string name="account_status_tls_error_domain">Domeen pole verifitseeritav</string>
+    <string name="account_status_policy_violation">Reeglite rikkumine</string>
+    <string name="account_status_incompatible_server">Mitteühilduv server</string>
+    <string name="account_status_incompatible_client">Mitteühilduv klient</string>
+    <string name="account_status_stream_error">Meediavoo viga</string>
+    <string name="account_status_stream_opening_error">Meediavoo avamise viga</string>
+    <string name="encryption_choice_unencrypted">Krüptimata</string>
+    <string name="encryption_choice_otr">OTR-krüptimine</string>
+    <string name="encryption_choice_pgp">OpenPGP-krüptimine</string>
+    <string name="encryption_choice_omemo">OMEMO-krüptimine</string>
+    <string name="mgmt_account_publish_avatar">Avalda profiilipilt</string>
+    <string name="mgmt_account_publish_pgp">Avalda oma OpenPGP avalik võti</string>
+    <string name="unpublish_pgp">Eemalda oma OpenPGP avalik võti</string>
+    <string name="unpublish_pgp_message">Kas sa oled kindel, et soovid eemaldada oma OpenPGP avaliku võtme oma olekuteatest?
+\nSeejärel sinu vestlusparterid enam ei saa sulle saata OpenPGP abil krüptitud sõnumeid.</string>
+    <string name="openpgp_has_been_published">OpenPGP avalik võti on avaldatud.</string>
+    <string name="mgmt_account_delete">Kustuta kasutajakonto</string>
+    <string name="mgmt_account_disable">Eemalda ajutiselt kasutusest</string>
+    <string name="mgmt_account_enable">Võta konto kasutusele</string>
+    <string name="mgmt_account_delete_confirm_text">Kas sa oled kindel, et soovid oma kasutajakonto kustutada? Sellega kustub ka kogu sinu vestluste ajalugu</string>
+    <string name="attach_record_voice">Salvesta häälsõnum</string>
+    <string name="account_settings_jabber_id">XMPP aadress</string>
+    <string name="block_jabber_id">Blokeeri XMPP aadress</string>
+    <string name="account_settings_example_jabber_id">kasutajanimi@toredomeen.ee</string>
+    <string name="password">Salasõna</string>
+    <string name="invalid_jid">See pole korrektne XMPP aadress</string>
+    <string name="error_out_of_memory">Mälu sa otsa. Pilt on liiga suur</string>
+    <string name="add_phone_book_text">Kas sa soovid lisada %s oma aadressiraamatusse?</string>
+    <string name="server_info_show_more">Serveri teave</string>
+    <string name="server_info_mam">XEP-0313: MAM (sõnumite arhiivi haldus)</string>
+    <string name="server_info_carbon_messages">XEP-0280: Message Carbons (sõnumite koopiad)</string>
+    <string name="server_info_csi">XEP-0352: Client State Indication (klientide olekuteated)</string>
+    <string name="server_info_blocking">XEP-0191: Blocking Command (blokeerimised)</string>
+    <string name="server_info_roster_version">XEP-0237: Roster Versioning (kontaktiloendite versioonid)</string>
+    <string name="server_info_stream_management">XEP-0198: Stream Management (andmevoog otspunktide vahel)</string>
+    <string name="server_info_external_service_discovery">XEP-0215: External Service Discovery (XMPP-väliste teenuste tuvastamine)</string>
+    <string name="server_info_pep">XEP-0163: PEP (kasutajate olekud, teated, andmed, profiilipildid, võtmed)</string>
+    <string name="server_info_http_upload">XEP-0363: HTTP File Upload (failide üleslaadimine http vahendusel)</string>
+    <string name="trust_omemo_fingerprints">Usalda OMEMO sõrmejälgi</string>
+    <string name="topic">Teema</string>
+    <string name="publish">Avalda</string>
+    <string name="error_publish_avatar_server_reject">Server keeldus sinu profiilipildi avaldamisest</string>
+    <string name="error_saving_avatar">Profiilipildi salvestamine andmekandjale ei õnenstunud</string>
+    <string name="error_publish_avatar_no_server_support">Sinu server ei toeta profiilipiltide avaldamist</string>
+    <string name="publishing">Avaldame…</string>
+    <string name="or_long_press_for_default">(vaikimisi väärtuse taastamiseks vajuta pikalt)</string>
+    <string name="error_publish_avatar_converting">Pildi konverteerimine ei õnnestunud</string>
+    <string name="private_message_to">kasutajale %s</string>
+    <string name="pref_autojoin">Sünkroniseeri järjehoidjad</string>
+    <string name="conference_creation_failed">Vestlusrühma loomine ei õnnestunud</string>
+    <string name="account_image_description">Kasutajakonto profiilipilt</string>
+    <string name="non_anonymous">Muuda XMPP-aadressid kõikidele nähtavaks</string>
+    <string name="your_full_jid_will_be">Sinu terviklik XMPP-aadress saab olema selline: %s</string>
+    <string name="report_jid_as_spammer">Anna sellest XMPP-aadressist teada kui spämmijast.</string>
+    <string name="error_publish_avatar_offline">Profiilipildi avaldamiseks peab sinu nutiseadmes olema võrguühendus.</string>
+    <string name="certificate_does_not_contain_jid">Sertifikaadis ei leidu XMPP-aadressi</string>
+    <string name="create_dialog_group_chat_name">Vestlusrühma nimi</string>
+    <string name="conference_destroyed">See vestlusrühm on kustutatud</string>
+    <string name="nickname">Hüüdnimi</string>
+    <string name="group_chat_name">Nimi</string>
+    <string name="channel_discover_opt_in_message">Kanalite leidmiseks on kasutusel kolmandate osapoole hallatav teenus <a href=https://search.jabber.network>search.jabber.network</a>.<br><br>Selle kasutamisel edastatakse teenusele sinu nutiseadme IP-aadress ja otsingusõnad. Lisateavet leiad teenuse <a href=https://search.jabber.network/privacy>privaatsustingimustest</a>.</string>
+    <string name="channel_discovery_opt_in_title">Võimalik privaatsuse rikkumine!</string>
+    <string name="i_already_have_an_account">Mul juba on kasutajakonto olemas</string>
+    <string name="delete_avatar">Kustuta profiilipilt</string>
+    <string name="no_xmpp_adddress_found">XMPP-aadressi ei leidu</string>
+    <string name="server_info_available">saadaval</string>
+    <string name="missing_public_keys">Avalikke võtmeid pole avaldatud</string>
+    <string name="last_seen_now">viimati nähtud just nüüd</string>
+    <string name="server_info_unavailable">pole saadaval</string>
+    <string name="last_seen_min">viimati nähtud ühe minuti eest</string>
+    <string name="last_seen_mins">viimati nähtud %d minutit tagasi</string>
+    <string name="last_seen_hour">viimati nähtud ühe tunni eest</string>
+    <string name="last_seen_hours">viimati nähtud %d tundi tagasi</string>
+    <string name="last_seen_day">viimati nähtud ühe päeva eest</string>
+    <string name="last_seen_days">viimati nähtud %d päeva tagasi</string>
+    <string name="install_openkeychain">Krüptitud sõnum. Dekrüptimiseks palun paigalda OpenKeychain.</string>
+    <string name="openpgp_messages_found">Leidub uus OpenPGPga krüptitud sõnum</string>
+    <string name="openpgp_key_id">OpenPGP võtme tunnus</string>
+    <string name="omemo_fingerprint">OMEMO sõrmejälg</string>
+    <string name="omemo_fingerprint_x509">v\\OMEMO sõrmejälg</string>
+    <string name="omemo_fingerprint_selected_message">OMEMO sõrmejälg (sõnumi allikas)</string>
+    <string name="server_info_bind2">XEP-0386: Bind 2 (tegevused käivitamisel)</string>
+    <string name="server_info_sasl2">XEP-0388: Extensible SASL Profile (SASL autentimis- ja andmeturbeprotokolli laiendused)</string>
+    <string name="server_info_push">XEP-0357: Push (tõuketeavitused)</string>
+    <string name="other_devices">Muud seadmed</string>
+    <string name="fetching_keys">Laadime võtmeid…</string>
+    <string name="done">Valmis</string>
+    <string name="decrypt">Dekrüpti</string>
+    <string name="group_chats">Vestlusrühmad</string>
+    <string name="search">Otsi</string>
+    <string name="enter_contact">Sisesta kontakt</string>
+    <string name="delete_contact">Kustuta kontakt</string>
+    <string name="view_contact_details">Vaata kontakti teavet</string>
+    <string name="block_contact">Blokeeri kontakt</string>
+    <string name="create">Loo</string>
+    <string name="select">Vali</string>
+    <string name="contact_already_exists">Selline kontakt on juba olemas</string>
+    <string name="join">Liitu</string>
+    <string name="channel_full_jid_example">kanalinimi@conference.toredomeen.ee/hüüdnimi</string>
+    <string name="channel_bare_jid_example">kanalinimi@conference.toredomeen.ee</string>
+    <string name="save_as_bookmark">Salvesta järjehoidjana</string>
+    <string name="delete_bookmark">Kustuta järjehoidja</string>
+    <string name="destroy_room">Kustuta vestlusrühm</string>
+    <string name="destroy_channel">Kustuta kanal</string>
+    <string name="destroy_room_dialog">Kas sa oled kindel, et soovid selle vestlusrühma kustutada?
+\n
+\n<b>Hoiatus:</b> Selle käigus vestlusrühm tervikuna kustutatakse ka serverist.</string>
+    <string name="destroy_channel_dialog">Kas sa oled kindel, et soovid selle kanali kustutada?
+\n
+\n<b>Hoiatus:</b> Selle käigus kanal tervikuna kustutatakse ka serverist.</string>
+    <string name="could_not_destroy_room">Vestlusrühma kustutamine ei õnnestunud</string>
+    <string name="could_not_destroy_channel">Kanali kustutamine ei õnnestunud</string>
+    <string name="action_edit_subject">Muuda vestlusrühma teemat</string>
+    <string name="joining_conference">Liitume vestlusrühmaga…</string>
+    <string name="leave">Lahku</string>
+    <string name="add_back">Lisa tagasi</string>
+    <string name="contacts_have_read_up_to_this_point">%s on siiamaani lugenud</string>
+    <string name="contacts_and_n_more_have_read_up_to_this_point">%1$s ja %2$d muud kasutajat on siiamaani lugenud</string>
+    <string name="everyone_has_read_up_to_this_point">Kõik on lugenud siiamaani</string>
+    <string name="enter_password">Sisesta salasõna</string>
+    <string name="conference_requires_password">Vestlusrühm eeldab salasõna</string>
+    <string name="conference_banned">Sulle on ligipääs siia vestlusrühma keelatud</string>
+    <string name="conference_members_only">See vestlusrühm on mõeldud vaid liikmetele</string>
+    <string name="conference_shutdown">Vestlusrühm on suletud</string>
+    <string name="touch_to_choose_picture">Pildi valimiseks galeriist klõpsi profiilipildil</string>
+    <string name="private_message">sosistas</string>
+    <string name="send_private_message_to">Saada privaatne sõnum kasutajale %s</string>
+    <string name="connect">Ühenda</string>
+    <string name="account_already_exists">Selline kasutajakonto on juba olemas</string>
+    <string name="next">Edasi</string>
+    <string name="server_info_session_established">Sessioon on loodud</string>
+    <string name="skip">Jäta vahele</string>
+    <string name="enable">Luba</string>
+    <string name="jabber_id_copied_to_clipboard">Kopeerisime XMPP-aadressi lõikelauale</string>
+    <string name="web_address">veebiaadress</string>
+    <string name="could_not_change_password">Salasõna muutmine ei õnnestunud</string>
+    <string name="password_changed">Salasõna on muudetud!</string>
+    <string name="change_password">Muuda salasõna</string>
+    <string name="current_password">Senine salasõna</string>
+    <string name="new_password">Uus salasõna</string>
+    <string name="password_should_not_be_empty">Salasõna ei saa tühi olla</string>
+    <string name="remove_from_room">Eemalda vestlusrühmast</string>
+    <string name="ban_from_conference">Keela ligipääs vestlusrühma</string>
+    <string name="conference_options">Privaatse vestlusrühma seadistused</string>
+    <string name="modified_conference_options">Vestlusrühma seadistused on muudetud!</string>
+    <string name="could_not_modify_conference_options">Vestlusrühma seadistuste muutmine ei õnnestunud</string>
+    <string name="avatar_has_been_published">Profiilipilt on avaldatud!</string>
+    <string name="title_undo_swipe_out_group_chat">Lahkusid privaatest vestlusrühmast</string>
+    <string name="search_group_chats">Otsi vestlusrühmi</string>
+    <string name="action_add_account_with_certificate">Logi sisse sertifikaadi alusel</string>
+    <string name="jid_does_not_match_certificate">XMPP-aadress ei vasta sertifikaadile</string>
+    <string name="hostname_or_onion">Serveri või .onion-teenuse aadress</string>
+    <string name="secure_password_generated">Tekitasime turvalise salasõna</string>
+    <string name="no_keys_just_confirm">Sa juba oled usaldanud selle kasutaja sõrmejälge. Valides „Valmis“ sa vaid kinnitad, et %s osaleb selles vestlusrühmas.</string>
+    <string name="registration_password_too_weak">Registreerimine ei õnnestunud: salasõna on liiga nõrk</string>
+    <string name="creating_conference">Loome vestlusrühma…</string>
+    <string name="group_chat_avatar">Vestlusrühma profiilipilt</string>
+    <string name="copy_link">Kopeeri veebiaadress</string>
+    <string name="copy_jabber_id">Kopeeri XMPP-aadress</string>
+    <string name="pref_omemo_setting_summary_always">OMEMO on alati kasutusel kahepoolsetes vestlustes ja privaatsetes vestlusrühmades.</string>
+    <string name="p1_s3_filetransfer">Http-põhine failijagamine teenuses S3</string>
+    <string name="pref_start_search">Otseotsing</string>
+    <string name="group_chat_members">Osalejad</string>
+    <string name="only_the_owner_can_change_group_chat_avatar">Vaid omanik võib muuta vestlusrühma profiilipilte</string>
+    <string name="contact_name">Kontakti nimi</string>
+    <string name="set_profile_picture">Conversationsi profiilipilt</string>
+    <string name="enter_password_to_restore">Varukoopiast taastamiseks sisesta %s kasutajakonto salasõna.</string>
+    <string name="enter_your_name_instructions">Selleks, et need, kellel pole sind oma aadressiraamatus, saaksid teada, kes sa oled, siis palun lisa oma nimi.</string>
+    <string name="group_chat_will_make_your_jabber_id_public">See kanal teeb sinu XMPP-aadressi avalikuks</string>
+    <string name="enter_jabber_id">Sisesta XMPP-aadress</string>
+    <string name="xmpp_address">XMPP-aadress</string>
+    <string name="please_enter_xmpp_address">Palun lisa XMPP-aadress</string>
+    <string name="this_is_an_xmpp_address">See on XMPP-aadress. Palun lisa nimi.</string>
+    <string name="jabber_ids_are_visible_to_anyone">XMPP-aadressid on nähtavad kõikidele.</string>
+    <string name="create_group_chat">Loo vestlusrühm</string>
+    <string name="create_private_group_chat">Loo privaatne vestlusrühm</string>
+    <string name="allow_participants_to_edit_subject">Luba kõigil muuta teemat</string>
+    <string name="anyone_can_edit_subject">Kõik võivad seda teemat muuta.</string>
+    <string name="owners_can_edit_subject">Vaid omanikud võivad seda teemat muuta.</string>
+    <string name="admins_can_edit_subject">Vaid peakasutajad võivad seda teemat muuta.</string>
+    <string name="please_enter_name">Palun sisesta kanali nimi</string>
+    <string name="creating_channel">Loome avalikku kanalit…</string>
+    <string name="channel_already_exists">Selline kanal on juba olemas</string>
+    <string name="joined_an_existing_channel">Sa oled liitunud olemasoleva kanaliga</string>
+    <string name="unable_to_set_channel_configuration">Kanali seadistuste salvestamine ei õnnestunud</string>
+    <string name="allow_participants_to_invite_others">Luba kõigil saata teistel kutseid</string>
+    <string name="owners_can_invite_others">Omanikud võivad saata teistele kasutajatele kutseid.</string>
+    <string name="file_too_large">Fail on liiga suur</string>
+    <string name="no_users_hint_group_chat">Selles privaatses vestlusrühmas pole osalejaid.</string>
+    <string name="manage_permission">Halda õigusi</string>
+    <string name="search_participants">Otsi osalejaid</string>
+    <string name="attach">Lisa manusena</string>
+    <string name="discover_channels">Leia kanaleid</string>
+    <string name="share_backup_files">Jaga varukoopia faile</string>
+    <string name="please_enter_password">Palun sisesta selle kasutajakonto salasõna</string>
+    <string name="add_existing_account">Lisa olemasolev kasutajakonto</string>
+    <string name="this_looks_like_a_domain">See tundub olema domeeni aadressi moodi</string>
+    <string name="add_anway">Lisa ikkagi</string>
+    <string name="this_looks_like_channel">See tundub olema kanali aadressi moodi</string>
+    <string name="rtp_state_content_add_video">Kas lülitad ümber videokõnele?</string>
+    <string name="group_chats_and_channels"><![CDATA[Vestlusrühmad ja kanalid]]></string>
+    <string name="search_channels">Otsi kanaleid</string>
+    <string name="register_new_account">Registreeri uus kasutajakonto</string>
+    <string name="conversations_backup">Conversationsi varukoopia</string>
+    <string name="event">Sündmus</string>
+    <string name="open_backup">Ava varukoopia</string>
+    <string name="not_a_backup_file">See fail ei ole Conversationsi varukoopia fail</string>
+    <string name="outdated_backup_file_format">Sa proovid importida aegunud vorminguga varukoopia failist</string>
+    <string name="account_already_setup">See kasutajakonto on juba seadistatud</string>
+    <string name="add_to_favorites">Tõsta ülal esile</string>
+    <string name="add_contact_or_create_or_join_group_chat">Lisa kontakte, loo mõni uus vestlusrühm või liitu olemasolevaga ning tutvu kanalitega</string>
+    <string name="your_avatar">Sinu profiilipilt</string>
+    <string name="your_avatar_tap_to_select_new_avatar">Sinu profiilipilt. Klõpsi siin uue pildi valimiseks galeriist.</string>
+    <string name="avatar_for_x">Kasutaja %s profiilipilt</string>
+    <string name="account_status_temporary_auth_failure">Ajutine viga autentimisel</string>
+    <string name="audio_video_disabled_tor">Tor-võrgu kasutamisel ei tööta kõnede funktsionaalsus</string>
+    <string name="switch_to_video">Lülitu ümber video kasutamisele</string>
+    <string name="reject_switch_to_video">Keeldu päringust videole ümberlülitumiseks</string>
+    <string name="encrypted_with_omemo">Krüptitud OMEMO abil</string>
+    <string name="encrypted_with_openpgp">Krüptitud OpenPGP abil</string>
+    <string name="not_encrypted">Pole krüptitud</string>
+    <string name="exit">Välju</string>
+    <string name="record_voice_mail">Salvesta kõneteade</string>
+    <string name="play_audio">Esita heli</string>
+    <string name="pause_audio">Peata heli esitamine</string>
+    <string name="log_out">Logi välja</string>
+    <string name="log_in">Logi sisse</string>
+    <string name="pref_allow_screenshots_summary">Näita rakenduse sisu rakenduste vahetajas ning luba teha ekraanitõmmiseid</string>
+    <string name="pref_category_e2ee">Läbiv krüptimine</string>
+    <string name="pref_title_trust_system_ca_store">Sertifitseerimiskeskused</string>
+    <string name="pref_title_trust_system_ca_store_summary">Usalda süsteemi sertifitseerimiskeskuseid</string>
+    <string name="pref_allow_screenshots">Luba ekraanitõmmiste tegemine</string>
+    <string name="pref_accept_invites_from_strangers_summary">Nõustu kutsetega võõrastelt vestlusrühmas osalemiseks</string>
+    <string name="edit_name_and_topic">Muuda nime ja teemat</string>
+    <string name="could_not_add_reaction">Reaktsiooni lisamine ei õnnestunud</string>
+    <string name="add_reaction">Lisa reaktsioon…</string>
+    <string name="jabber_ids_are_visible_to_admins">XMPP-aadressid on nähtavad serverite haldajatele.</string>
+    <string name="no_users_hint_channel">Selles avalikus kanalis pole ühtegi osalejat. Saad kutse oma kontaktidela või kasuta selle kanali XMPP-aadressi levitamiseks Jaga-nuppu.</string>
+    <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO sõrmejälg (sõnumi allikas)</string>
+    <string name="unblock_contact">Eemalda kontakti blokeering</string>
+    <string name="unable_to_decrypt_backup">Varukoopia dekrüptimine ei õnnestunud. Kas salasõna on ikka õige?</string>
+    <string name="contact_added_you">Vestluspartner lisas sind kontaktide loendisse</string>
+    <string name="no_conference_server_found">Vestlusrühma serverit ei leidu</string>
+    <string name="disable_notifications">Keela teavitused</string>
+    <string name="conference_kicked">Sa oled sellest vestlusrühmast välja müksatud</string>
+    <string name="user_has_left_conference">%1$s on lahkunud vestlusrühmast</string>
+    <string name="host_does_not_support_group_chat_avatars">Server ei toeta vestlusrühma profiilipilte</string>
+    <string name="anyone_can_invite_others">Kõik võivad saata teistele kasutajatele kutseid.</string>
+    <string name="scan_qr_code">Skaneeri QR-kood</string>
+    <string name="show_qr_code">Näita QR-koodi</string>
+    <string name="show_block_list">Näita blokeeringute loendit</string>
+    <string name="account_details">Kasutajakonto üksikasjad</string>
+    <string name="confirm">Kinnita</string>
+    <string name="regenerate_omemo_key">Loo OMEMO-võti uuesti</string>
+    <string name="copy_omemo_clipboard_description">Kopeeri OMEMO sõrmejälg lõikelauale</string>
+    <string name="using_account">kasutades kontot %s</string>
+    <string name="hosted_on">serveris %s</string>
+    <string name="checking_x">Kontrollime HTTP-serveris: %s</string>
+    <string name="not_connected_try_again">Sul hetkel puudub ühendus. Proovi hiljem uuesti</string>
+    <string name="url_copied_to_clipboard">Kopeerisime võrguaadressi lõikelauale</string>
+    <string name="error_message_copied_to_clipboard">Kopeerisime veateate lõikelauale</string>
+    <string name="try_again">Proovi uuesti</string>
+    <string name="pref_keep_foreground_service">Teenus esiplaanil</string>
+    <string name="pref_keep_foreground_service_summary">See eelistus takistab operatsioonisüsteemil sinu võrguühenduse sulgemist</string>
+    <string name="pref_create_backup">Tee varukoopia</string>
+    <string name="pref_create_backup_summary">Varukoopia failide salvestamise asukoht: %s</string>
+    <string name="notification_backup_created_title">Sinu varukoopia on tehtud</string>
+    <string name="notification_create_backup_title">Teeme varukoopiat</string>
+    <string name="notification_backup_created_subtitle">Varukoopia failid on salvestatud siin kaustas: %s</string>
+    <string name="restoring_backup">Taastame varukoopiast</string>
+    <string name="notification_restored_backup_title">Andmed sinu varukoopiast on taastatud</string>
+    <string name="notification_restored_backup_subtitle">Ära unusta seda kontot kasutusele võtta.</string>
+    <string name="choose_file">Vali fail</string>
+    <string name="receiving_x_file">Laadime alla %1$s (%2$d%% valmis)</string>
+    <string name="download_x_file">Laadi %s alla</string>
+    <string name="delete_x_file">Kustuta %s</string>
+    <string name="file">fail</string>
+    <string name="open_x_file">Ava %s</string>
+    <string name="sending_file">laadime üles (%1$d%% valmis)</string>
+    <string name="preparing_file">Valmistume faili jagamiseks</string>
+    <string name="x_file_offered_for_download">%s on allalaadimiseks saadaval</string>
+    <string name="cancel_transmission">Katkesta laadimine</string>
+    <string name="audiobook">Heliraamat</string>
+    <string name="vcard">Kontakt</string>
+    <string name="server_side_mam_prefs">Serveris arhiveerimise eelistused</string>
+    <string name="error_trustkey_bundle">Krüptimisvõtmete laadimine ei õnnestunud</string>
+    <string name="enter_your_name">Sisesta oma nimi</string>
+    <string name="your_name">Sinu nimi</string>
+    <string name="change_notification_settings">Muuda teavituse seadistusi</string>
+    <string name="pref_security_settings">Turvalisus</string>
+    <string name="fetching_history_from_server">Laadime serverist ajalugu</string>
+    <string name="moderated">Muuda kanal modereeritavaks</string>
+    <string name="you_are_not_participating">Sina ei osale</string>
+    <string name="channel_options">Avaliku kanali seadistused</string>
+    <string name="members_only">Privaatne, vaid liikmetele</string>
+    <string name="vector_graphic">vektorgraafika</string>
+    <string name="unable_to_start_recording">Ei õnnestunud alustada salvestamist</string>
+    <string name="start_orbot">Käivita Orbot</string>
+    <plurals name="toast_delete_certificates">
+        <item quantity="one">%d sertifikaat on kustutatud</item>
+        <item quantity="other">%d sertifikaati on kustutatud</item>
+    </plurals>
+    <string name="request_presence_updates">Esmalt palu oma kontakti, et ta lubaks olekuteadete saatmise.
+\n
+\n<small>Seda kasutatakse sinu kontakti XMPP-kliendi tuvastamiseks</small>.</string>
+    <string name="request_now">Palu nüüd</string>
+    <string name="ignore">Eira</string>
+    <string name="without_mutual_presence_updates"><b>Hoiatus:</b> Selle saatmine ilma selleta, et mõlemal poolel on olekuteated sisse lülitatud, võib tekitada ootamatuid probleeme.
+\n
+\n<small>Ava „Kontakti üksikasjad“ ja kontrolli oma olekuteadete staatust.</small></string>
+    <string name="pref_allow_message_correction">Sõnumite muutmine</string>
+    <string name="pref_allow_message_correction_summary">Luba, et sinu kontaktid saavad tagantjärgi sõnumeid muuta</string>
+    <string name="pref_expert_options">Seadistused asjatundjatele</string>
+    <string name="pref_expert_options_summary">Palun ole nendega ettevaatlik</string>
+    <string name="title_activity_about_x">Rakenduse teave: %s</string>
+    <string name="title_pref_quiet_hours">Aeg, mil sa ei soovi segamist</string>
+    <string name="title_pref_quiet_hours_start_time">Algus</string>
+    <string name="title_pref_quiet_hours_end_time">Lõpp</string>
+    <string name="title_pref_enable_quiet_hours">Kasuta aega, millal sa ei soovi segamist</string>
+    <string name="pref_quiet_hours_summary">Nendel vaiksetel tundidel on teavitused välja lülitatud</string>
+    <string name="pref_expert_options_other">Muud seadistused</string>
+    <string name="pref_autojoin_summary">Kui liitud või lahkud vestlusrühmast või reageerid teistes klientides tehtud muudatustele, siis lisa päringusse „autojoin“ tingimus.</string>
+    <string name="toast_message_omemo_fingerprint">OMEMO sõrmejälg on kopeeritud lõikelauale</string>
+    <string name="conference_resource_constraint">Ressursside piirangud</string>
+    <string name="check_x_filesize">Kontrolli %s faili suurust</string>
+    <string name="check_x_filesize_on_host">Kontrolli %1$s faili suurust serveris %2$s</string>
+    <string name="message_options">Sõnumi valikud</string>
+    <string name="quote">Tsiteeri</string>
+    <string name="paste_as_quote">Aseta tsitaadina</string>
+    <string name="copy_original_url">Kopeeri algne URL</string>
+    <string name="send_again">Saada uuesti</string>
+    <string name="file_url">Faili võrguaadress</string>
+    <string name="pref_show_dynamic_tags">Dünaamilised sildid</string>
+    <string name="pref_show_dynamic_tags_summary">Kuva mittemuudetavaid silte kontaktkirjete all</string>
+    <string name="enable_notifications">Võta teavitused kasutusele</string>
+    <string name="file_transmission_failed">faili jagamine ei õnnestunud</string>
+    <string name="file_transmission_cancelled">faili saatmine või vastuvõtmine on katkestatud</string>
+    <string name="file_deleted">Fail on kustutatud</string>
+    <string name="no_application_found_to_open_file">Faili avamiseks ei leidu sobivat rakendust</string>
+    <string name="no_application_found_to_open_link">Lingi avamiseks ei leidu sobivat rakendust</string>
+    <string name="no_application_found_to_view_contact">Kontaktkirje vaatamiseks ei leidu sobivat rakendust</string>
+    <string name="no_more_history_on_server">Serveris ei leidu rohkem ajalugu</string>
+    <string name="updating">Uuendame…</string>
+    <string name="enable_all_accounts">Võta kõik kontod kasutusele</string>
+    <string name="disable_all_accounts">Eemalda kõik kontod kasutuselt</string>
+    <string name="perform_action_with">Tee toiming...</string>
+    <string name="no_affiliation">Kuuluvusi pole</string>
+    <string name="could_not_change_affiliation">Kasutaja „%s“ kuuluvuse muutmine ei õnnestunud</string>
+    <string name="remove_owner_privileges">Võta omaniku õigused ära</string>
+    <string name="remove_from_channel">Eemalda kanalilt</string>
+    <string name="ban_from_channel">Keela ligipääs kanalile</string>
+    <string name="could_not_change_role">Kasutaja „%s“ rolli muutmine ei õnnestunud</string>
+    <string name="removing_from_public_conference">Sa üritad eemaldada kasutajat „%s“ avalikult kanalilt. Ainus viis seda teha on talle igavese suhtluskeelu seadmine.</string>
+    <string name="ban_now">Keela ligipääs nüüd</string>
+    <string name="sending_x_file">Saadame faili „%s“</string>
+    <string name="offering_x_file">Pakume faili „%s“</string>
+    <string name="pref_display_enter_key">Näita sisestusklahvi</string>
+    <string name="pref_display_enter_key_summary">Vaheta emotikonide klahv sisestusklahviks</string>
+    <string name="audio">heli</string>
+    <string name="video">video</string>
+    <string name="image">pilt</string>
+    <string name="multimedia_file">multimeediafail</string>
+    <string name="pdf_document">pdf-dokument</string>
+    <string name="apk">Androidi rakendus</string>
+    <string name="hide_offline">Peida võrgus mitteolevad kasutajad</string>
+    <string name="contact_is_typing">%s kirjutab…</string>
+    <string name="contact_has_stopped_typing">%s on lõpetanud kirjutamise</string>
+    <string name="contacts_are_typing">%s kirjutavad…</string>
+    <string name="contacts_have_stopped_typing">%s on lõpetanud kirjutamise</string>
+    <string name="pref_chat_states">Kirjutamise teavitused</string>
+    <string name="pref_chat_states_summary">Luba teistel osapooltel teada saada, kui neile kirjutad</string>
+    <string name="send_location">Saada oma asukoht</string>
+    <string name="show_location">Näita oma asukohta</string>
+    <string name="no_application_found_to_display_location">Asukoha näitamiseks ei leidu ühtegi rakendust</string>
+    <string name="location">Asukoht</string>
+    <string name="title_undo_swipe_out_chat">Vestlus on arhiveeritud</string>
+    <string name="title_undo_swipe_out_channel">Lahkusid avalikult kanalilt</string>
+    <string name="pref_dont_trust_system_cas_title">Ära usalda operatsioonisüsteemis kirjeldatud sertifitseerimiskeskuseid</string>
+    <string name="pref_dont_trust_system_cas_summary">Kõik sertifikaadid peavad olema käsitsi kinnitatud</string>
+    <string name="pref_remove_trusted_certificates_title">Eemalda sertifikaadid</string>
+    <string name="pref_remove_trusted_certificates_summary">Kustuta käsitsi kinnitatud sertifikaadid</string>
+    <string name="toast_no_trusted_certs">Käsitsi kinnitatud sertifikaate pole</string>
+    <string name="dialog_manage_certs_title">Eemalda sertifikaadid</string>
+    <string name="dialog_manage_certs_positivebutton">Kustuta valik</string>
+    <string name="pref_quick_action">Kiirtoiming</string>
+    <string name="send_private_message">Saada privaatne sõnum</string>
+    <string name="username">Kasutajanimi</string>
+    <string name="username_hint">Kasutajanimi</string>
+    <string name="invalid_username">See pole korrektne kasutajanimi</string>
+    <string name="download_failed_server_not_found">Allalaadimine ei õnnestunud: serverit ei leidu</string>
+    <string name="download_failed_file_not_found">Allalaadimine ei õnnestunud: faili ei leidu</string>
+    <string name="pref_start_search_summary">Vaates „Uus vestlus“ ava klahvistik ning aseta kursor otsinguväljale</string>
+    <string name="media_browser">Meediabrauser</string>
+    <string name="security_violation_not_attaching_file">Fail on vahele jäetud turvareeglite rikkumise tõttu.</string>
+    <string name="pref_video_compression">Video kvaliteet</string>
+    <string name="pref_video_compression_summary">Madalam kvaliteet tähendab väiksemaid faile</string>
+    <string name="video_360p">Keskmine (360p)</string>
+    <string name="video_720p">Kõrge (720p)</string>
+    <string name="cancelled">katkestatud</string>
+    <string name="already_drafting_message">Sa juba oled koostamas sõnumit.</string>
+    <string name="feature_not_implemented">Funktsionaalsus pole implementeeritud</string>
+    <string name="invalid_country_code">Vigane riigikood</string>
+    <string name="choose_a_country">Vali riik</string>
+    <string name="phone_number">telefoninumber</string>
+    <string name="backup_started_message">Varundamine on alanud. Saad teavituse, kui ta on lõppenud.</string>
+    <string name="start_chat">Alusta vestlust</string>
+    <string name="pref_quick_action_summary">Asenda „Saada“ nupp kiirtoimingutega</string>
+    <string name="download_failed_could_not_connect">Allalaadimine ei õnnestunud: ühendus serveriga ei õnnestunud</string>
+    <string name="download_failed_could_not_write_file">Allalaadimine ei õnnestunud: faili salvestamine ei õnnestunud</string>
+    <string name="download_failed_invalid_file">Allalaadimine ei õnnestunud: vigane fail</string>
+    <string name="clear_other_devices_desc">Kas sa oled kindel, et soovid eemaldada OMEMO-levitusest kõik muud seadmed? Kui sinu seadmed järgmisel korral loovad ühenduse, siis nad levitavad sama teavitust uuesti, aga nad ei pruugi saada vahepeal saadetud sõnumeid.</string>
+    <string name="clear_other_devices">Eemalda seadmed</string>
+    <string name="error_no_keys_to_trust_server_error">Selle kontakti jaoks ei leidu ühtegi kasutatavat võtit.
+\nServerist uute võtmete laadimine ei õnnestunud. Kas vestluse teise osapoole serveriga võib midagi valesti olla?</string>
+    <string name="error_no_keys_to_trust_presence">Selle kontakti jaoks ei leidu ühtegi kasutatavat võtit.
+\nPalun kontrolli, et teil mõlemal on olekuteavituste kontrollimine sisse lülitatud.</string>
+    <string name="member">Osaleja</string>
+    <string name="unable_to_parse_certificate">Sertifikaadi sisu töötlemine ei õnnestunud</string>
+    <plurals name="x_messages">
+        <item quantity="one">%d sõnum</item>
+        <item quantity="other">%d sõnumit</item>
+    </plurals>
+    <string name="not_a_valid_port">See pole korrektne pordi number</string>
+    <string name="not_valid_hostname">See pole korrektne hosti nimi</string>
+    <string name="shared_file_with_x">Fail jagatud kasutajaga %s</string>
+    <string name="shared_image_with_x">Pilt jagatud kasutajaga %s</string>
+    <string name="shared_images_with_x">Pildid jagatud kasutajaga %s</string>
+    <string name="shared_text_with_x">Tekst jagatud kasutajaga %s</string>
+    <string name="security_error_invalid_file_access">Turvaviga: vale ligipääs failile!</string>
+    <string name="create_account">Loo kasutajakonto</string>
+    <string name="reject_request">Lükka päring tagasi</string>
+    <string name="install_orbot">Paigalda Orbot</string>
+    <string name="no_name_set_instructions">Oma nime lisamiseks või muutmiseks klõpsi nuppu „Muuda“.</string>
+    <string name="ebook">e-raamat</string>
+    <string name="create_dialog_channel_name">Kanali nimi</string>
+    <string name="unable_to_perform_this_action">Seda toimingut ei õnnestunud teha</string>
+    <string name="open_join_dialog">Liitu avaliku kanaliga…</string>
+    <string name="error_trustkeys_title">Midagi läks valesti</string>
+    <string name="no_role">Pole võrgus</string>
+    <string name="outcast">Hüljatu</string>
+    <string name="advanced_mode">Üksikasjalik seadistus</string>
+    <string name="grant_membership">Liikmelisuse lubamine</string>
+    <string name="remove_membership">Liikmelisuse keelamine</string>
+    <string name="grant_admin_privileges">Peakasutaja õiguste lubamine</string>
+    <string name="remove_admin_privileges">Peakasutaja õiguste keelamine</string>
+    <string name="grant_owner_privileges">Omaniku õiguste lubamine</string>
+    <string name="never">Mitte kunagi</string>
+    <string name="until_further_notice">Järgmise teadaandmiseni</string>
+    <string name="snooze">Lükka edasi</string>
+    <string name="reply">Vasta</string>
+    <string name="mark_as_read">Märgi loetuks</string>
+    <string name="pref_input_options">Sisend</string>
+    <string name="pref_enter_is_send">Sisestusklahv saadab</string>
+    <string name="pref_enter_is_send_summary">Kasuta sisestusklahvi saatmiseks. Lisaks toimib saatmiseks ka Ctrl+Enter, ka siis kui see valik pole kasutusel.</string>
+    <string name="none">Määramata</string>
+    <string name="recently_used">Viimatikasutatu</string>
+    <string name="choose_quick_action">Vali kiirtoiming</string>
+    <string name="search_contacts">Otsi kontakte</string>
+    <string name="pref_presence_settings">Kättesaadavus</string>
+    <string name="account_status_tor_unavailable">Tori võrk pole saadaval</string>
+    <string name="account_status_bind_failure">Serveriga sidumine ei õnnestunud</string>
+    <string name="account_status_host_unknown">Pole vastutav antud domeeni eest</string>
+    <string name="server_info_broken">Katki</string>
+    <string name="pref_away_when_screen_off">Seadme lukustumisel, määra olekuks „Eemal“</string>
+    <string name="pref_away_when_screen_off_summary">Kui seade on lukus, siis määra oma olekuks „Eemal“</string>
+    <string name="pref_dnd_on_silent_mode">Vaikse seadme puhul näita olekut „Hõivatud“</string>
+    <string name="pref_dnd_on_silent_mode_summary">Kui nutiseade on vaikses režiimis, siis näita oma olekuks „Hõivatud“</string>
+    <string name="pref_treat_vibrate_as_silent">Tõlgenda värinaalarmi kasutamist vaikse režiimina</string>
+    <string name="pref_treat_vibrate_as_dnd_summary">Kui nutiseade on värinaalarmi režiimis, siis näita oma olekuks „Hõivatud“</string>
+    <string name="pref_show_connection_options">Serveri hosti nimi ja pordi number</string>
+    <string name="pref_show_connection_options_summary">Kasutajakonto lisamisel näita täiendavaid ühenduse seadistusi</string>
+    <string name="hostname_example">xmpp.toredomeen.ee</string>
+    <string name="mam_prefs">Arhiveerimise eelistused</string>
+    <string name="fetching_mam_prefs">Laadime arhiveerimise eelistusi. Palun oota…</string>
+    <string name="unable_to_fetch_mam_prefs">Arhiveerimise eelistuste laadimine ei õnnestunud</string>
+    <string name="captcha_required">Robotilõksu lahendamine on vajalik</string>
+    <string name="captcha_hint">Sisesta tekst, mida näed ülal asuval pildil</string>
+    <string name="certificate_chain_is_not_trusted">Ebausaldusväärne sertifikaadiahel</string>
+    <string name="action_renew_certificate">Uuenda sertifikaati</string>
+    <string name="error_fetching_omemo_key">Viga OMEMO-võtme laadimisel!</string>
+    <string name="load_more_messages">Laadi veel sõnumeid</string>
+    <string name="verified_omemo_key_with_certificate">Verifitseerisime OMEMO-võtme sertifikaadiga!</string>
+    <string name="device_does_not_support_certificates">Sinu nutiseade ei toeta kliendipoolsete sertifikaatide valimist!</string>
+    <string name="pref_connection_options">Ühendus</string>
+    <string name="pref_use_tor">Ühenda Tori võrgu kaudu</string>
+    <string name="pref_use_tor_summary">Kasuta kõikideks ühendusteks tunnelit läbi Tori võrgu. Eeldab Orbot-rakenduse kasutamist</string>
+    <string name="account_settings_hostname">Hosti nimi</string>
+    <string name="account_settings_port">Pordi number</string>
+    <string name="connected_accounts">Kasutajakontosid: %2$d, neist ühendus olemas %1$d</string>
+    <string name="status_message">Olekuteade</string>
+    <string name="presence_chat">Vestlemiseks vaba</string>
+    <string name="presence_away">Eemal</string>
+    <string name="presence_dnd">Hõivatud</string>
+    <string name="presence_online">Võrgus</string>
+    <string name="use_own_provider">Kasuta minu valitud teenusepakkujat</string>
+    <string name="pick_your_username">Vali oma kasutajanimi</string>
+    <string name="pref_manually_change_presence">Halda kättesaadavust käsitsi</string>
+    <string name="pref_manually_change_presence_summary">Olekuteate muutmisega seadista oma kättesaadavust.</string>
+    <string name="presence_xa">Pole saadaval</string>
+    <string name="this_account_is_disabled">Sa oled selle kasutajakonto välja lülitanud</string>
+    <string name="this_account_is_logged_out">Sa oled sellest kasutajakontost välja loginud</string>
+    <string name="choose_participants">Vali osalejad</string>
+    <string name="invite_again">Kutsu uuesti</string>
+    <string name="registration_please_wait">Registreerimine ei õnnestunud: palun proovi hiljem uuesti</string>
+    <string name="the_app_is_out_of_date">Sa kasutad rakenduse aegunud versiooni.</string>
+    <string name="update">Uuenda</string>
+    <string name="logged_in_with_another_device">See telefoninumber on hetkel sisselogitud ühes teises seadmes.</string>
+    <string name="video_original">Algne (pakkimata)</string>
+    <string name="open_with">Ava rakendusega…</string>
+    <string name="choose_account">Vali kasutajakonto</string>
+    <string name="restore_backup">Taasta varukoopiast</string>
+    <string name="restore">Taasta</string>
+    <string name="backup_channel_name">Varundus ja taastamine</string>
+    <string name="no_market_app_installed">Ühtegi rakendustepoodi pole paigaldatud.</string>
+    <string name="join_public_channel">Liitu avaliku kanaliga</string>
+    <string name="create_public_channel">Loo avalik kanal</string>
+    <string name="rtp_state_declined_or_busy">Hõivatud</string>
+    <string name="remove_from_favorites">Eemalda esiletõstetute hulgast</string>
+    <string name="gpx_track">GPX-rada</string>
+    <string name="could_not_correct_message">Sõnumi muutmine ei õnnestunud</string>
+    <string name="search_all_conversations">Kõik vestlused</string>
+    <string name="search_this_conversation">See vestlus</string>
+    <string name="pref_up_push_account_title">XMPP konto</string>
+    <string name="hide_notification">Peida teavitus</string>
+    <string name="no_storage_permission">Anna rakendusele %1$s õigus kasutada välist andmeruumi</string>
+    <string name="pref_picture_compression">Piltide pakkimine ja tihendamine</string>
+    <string name="pref_picture_compression_summary">Vihje: sõltumata selle eelistuse olekust, kui soovid saata pilte algsel kujul, siis kasuta valikut „Vali fail“, mitte „Vali pilt“.</string>
+    <string name="always">Alati</string>
+    <string name="notify_never">Teavitused pole kasutusel</string>
+    <string name="notify_paused">Teavitused on peatatud</string>
+    <string name="large_images_only">Vaid suurte piltide puhul</string>
+    <string name="battery_optimizations_enabled">Akukasutuse optimeerimine on sisselülitatud</string>
+    <string name="battery_optimizations_enabled_explained">Sinu nutiseade kasutab hetkel %1$s rakenduse jaoks tõhusat akukasutuse optimeerimist ning see võib põhjustada nii sõnumite hiljem saabumist, kui kadumist.
+\nMe soovitame, et lülitad akukasutuse optimeerimise välja.</string>
+    <string name="battery_optimizations_enabled_dialog">Sinu nutiseade kasutab hetkel %1$s rakenduse jaoks tõhusat akukasutuse optimeerimist ning see võib põhjustada nii sõnumite hiljem saabumist, kui kadumist.
+\n
+\nJärgnevas palume, et lülitad akukasutuse optimeerimise välja.</string>
+    <string name="welcome_header">Liitu Conversationi kasutajatega</string>
+    <string name="welcome_header_quicksy">Tere tulemast kasutama sõnumirakendust Quicksy!</string>
+    <string name="no_application_to_share_uri">Ei leidu ühtegi rakendust võrguaadressi ehk URI jagamiseks</string>
+    <string name="share_uri_with">Jaga võrguaadress rakendusega…</string>
+    <string name="no_camera_permission">Anna rakendusele %1$s õigus kasutada kaamerat</string>
+    <string name="notify_on_all_messages">Teavita kõikidest sõnumitest</string>
+    <string name="notify_only_when_highlighted">Teavita vaid mainimiste puhul</string>
+    <string name="disable">Lülita välja</string>
+    <string name="selection_too_large">Valitud ala on liiga suur</string>
+    <string name="no_accounts">(Aktiveeritud kasutajakontosid pole)</string>
+    <string name="this_field_is_required">Selle välja täitmine on kohustuslik</string>
+    <string name="correct_message">Muuda sõnumit</string>
+    <string name="send_corrected_message">Saada muudetud sõnum</string>
+    <string name="device_does_not_support_battery_op">Sinu nutiseade et toeta akukasutuse optimeerimise rakendusekohast väljalülitamist</string>
+    <string name="agree_and_continue">Nõustu ja jätka</string>
+    <string name="quicksy_wants_your_consent">Quicksy küsib nõusolekut sinu andmete kasutamiseks</string>
+    <string name="pref_theme_light">Hele kujundus</string>
+    <string name="pref_theme_dark">Tume kujundus</string>
+    <string name="pref_privacy">Privaatsus</string>
+    <string name="pref_theme_options">Kujundus</string>
+    <string name="pref_theme_options_summary">Vali värvipalett</string>
+    <string name="pref_theme_automatic">Automaatne</string>
+    <string name="pref_use_colorful_bubbles">Värvilised vestlusmullid</string>
+    <string name="pref_use_colorful_bubbles_summary">Saadetud ja saabunud sõnumite erksad taustavärvid</string>
+    <string name="unable_to_connect_to_keychain">Ühenduse loomine OpenKeychainiga ei õnnestunud</string>
+    <string name="this_device_is_no_longer_in_use">See seade pole enam kasutusel</string>
+    <string name="type_pc">Arvuti</string>
+    <string name="type_phone">Mobiiltelefon</string>
+    <string name="type_tablet">Tahvelarvuti</string>
+    <string name="type_web">Veebibrauser</string>
+    <string name="type_console">Konsool</string>
+    <string name="payment_required">Makse on vajalik</string>
+    <string name="mtm_trust_anchor">Serveri sertifikaat pole allkirjastatud ühegi tunnustaud sertifitseerimiskeskuse (CA) poolt.</string>
+    <string name="error_trustkey_general">%1$s ei suuda saata krüptitud sõnumit kasutajale %2$s. Võib-olla kasutab teine osapool aegunud serveritarkvara või tema vestlusrakendus ei oska OMEMO abil krüptimist kasutada.</string>
+    <string name="qr_code_scanner_needs_access_to_camera">QR-koodi skaneerimiseks on vajalik õigus kasutada kaamerat</string>
+    <string name="pref_validate_hostname_summary">Kui serveri sertifikaadis leidub kontrollitud hostinimi, siis loetakse ta verifitseerituks</string>
+    <string name="pref_delete_omemo_identities">Kustuta OMEMO identiteedid</string>
+    <string name="show_error_message">Näita veateadet</string>
+    <string name="error_message">Veateade</string>
+    <string name="data_saver_enabled">Andmemahu säästja on kasutusel</string>
+    <string name="server_info_partial">osaline</string>
+    <string name="video_is_enabled_tap_to_disable">Video on kasutusel. Väljalülitamiseks klõpsi.</string>
+    <string name="video_is_disabled_tap_to_enable">Video pole kasutusel. Sisselülitamiseks klõpsi.</string>
+    <string name="sync_with_contacts_long">%1$s töötleb sinu kontaktiloendit sinu nutiseadmes ning näitab sulle XMPP-aadressidele vastavaid nimesid ja profiilipilte.
+\n
+\nKontaktide andmed mitte kunagi ei lahku sinu nutiseadmest!</string>
+    <string name="sync_with_contacts">Kontaktiloendi lõimimine</string>
+    <string name="gp_disable">Eemalda kasutusest</string>
+    <string name="gp_short">Lühike</string>
+    <string name="gp_medium">Keskmine</string>
+    <string name="gp_long">Pikk</string>
+    <string name="pref_broadcast_last_activity">Viimati nähtud</string>
+    <string name="pref_broadcast_last_activity_summary">Luba oma kontaktidel näha, millal sa viimati vestlusrakendust kasutasid</string>
+    <string name="today">Täna</string>
+    <string name="yesterday">Eile</string>
+    <string name="pref_validate_hostname">Kontrolli hosti nime DNSSECi abil</string>
+    <string name="attach_record_video">Salvesta video</string>
+    <string name="copy_to_clipboard">Kopeeri lõikelauale</string>
+    <string name="message_copied_to_clipboard">Sõnum on kopeeritud lõikelauale</string>
+    <string name="message">Sõnum</string>
+    <string name="private_messages_are_disabled">Privaatsõnumid pole kasutusel</string>
+    <string name="mtm_accept_cert">Kas soovid nõustuda tundmatu sertifikaadiga?</string>
+    <string name="mtm_accept_servername">Kas nõustud, et serveri nimi ei klapi sertifikaadis leiduvaga?</string>
+    <string name="mtm_hostname_mismatch">Serveri tuvastamine kui „%s“ polnud võimalik. Sertifikaat kehtib vaid alljärgnevalt:</string>
+    <string name="mtm_connect_anyway">Kas sa ikkagi soovid ühenduse luua?</string>
+    <string name="mtm_cert_details">Sertifikaadi üksikasjad:</string>
+    <string name="once">Üks kord</string>
+    <string name="pref_scroll_to_bottom">Keri alla välja</string>
+    <string name="pref_scroll_to_bottom_summary">Peale sõnumi saatmist keri vaade alla</string>
+    <string name="edit_status_message_title">Muuda olekuteadet</string>
+    <string name="edit_status_message">Muuda olekuteadet</string>
+    <string name="disable_encryption">Lülita krüptimine välja</string>
+    <string name="error_trustkey_device_list">Seadmete loendi laadimine ei õnnestunud</string>
+    <string name="call_is_using_earpiece_tap_to_switch_to_speaker">Kõne kasutab kõrvaklappe. Valjuhääldile ümberlülitamiseks klõpsi.</string>
+    <string name="call_is_using_earpiece">Kõne kasutab kõrvaklappe.</string>
+    <string name="call_is_using_wired_headset">Kõne kasutab traadiga kõrvaklappe</string>
+    <string name="call_is_using_speaker_tap_to_switch_to_earpiece">Kõne kasutab valjuhääldit. Kõrvaklappidele ümberlülitamiseks klõpsi.</string>
+    <string name="call_is_using_speaker">Kõne kasutab valjuhääldit.</string>
+    <string name="call_is_using_bluetooth">Kõne kasutab bluetoothi ühendust.</string>
+    <string name="flip_camera">Pööra kaamera teistpidi</string>
+    <string name="server_info_login_mechanism">Sisselogimise meetod</string>
+    <string name="add_reaction_title">Lisa reaktsioon</string>
+    <string name="more_reactions">Veel reaktsioone</string>
+    <string name="pref_omemo_setting_summary_default_on">OMEMO on vaikimisi kasutusel kõikide uute vestluste puhul.</string>
+    <string name="action_unfix_from_location">Eemalda fikseeritud asukoht</string>
+    <string name="action_copy_location">Kopeeri asukoht</string>
+    <string name="action_share_location">Jaga asukohta</string>
+    <string name="action_fix_to_location">Fikseeri asukoht</string>
+    <string name="action_directions">Tee juhtamine</string>
+    <string name="share">Jaga</string>
+    <string name="pref_omemo_setting_summary_default_off">OMEMO vajab eraldi siselülitamist kõikide uute vestluste puhul.</string>
+    <string name="create_shortcut">Loo kiirlink</string>
+    <string name="default_on">Vaikimisi kasutusel</string>
+    <string name="default_off">Vaikimisi pole kasutusel</string>
+    <string name="pref_dynamic_colors">Dünaamilised värvid</string>
+    <string name="pref_dynamic_colors_summary">Süsteemi värvid (Material You)</string>
+    <string name="not_encrypted_for_this_device">Sõnum polnud selle seadme jaoks krüptitud.</string>
+    <string name="undo">võta tagasi</string>
+    <string name="location_disabled">Asukoha jagamine pole kasutusel</string>
+    <string name="title_activity_share_location">Jaga asukohta</string>
+    <string name="title_activity_show_location">Näita oma asukohta</string>
+    <string name="omemo_decryption_failed">OMEMO-põhise sõnumi dekrüptimine ei õnnestunud.</string>
+    <string name="no_permission_to_access_x">Puudub õigus %s kasutamiseks</string>
+    <string name="remote_server_not_found">Serverit ei leidu</string>
+    <string name="remote_server_timeout">Ühendus serveriga aegus</string>
+    <string name="unable_to_update_account">Kasutajakonto uuendamine ei õnnestunud</string>
+    <string name="pref_delete_omemo_identities_summary">Loo uuesti kõik oma OMEMO-võtmed. Kõik sinu kontaktid peavad sel juhul sind uuesti verifitseerima. Kasuta seda võimalust ainult viimase abinõuna.</string>
+    <string name="no_permission_to_place_call">Pole õigusi helistamiseks</string>
+    <string name="delete_selected_keys">Kustuta valitud võtmed</string>
+    <string name="gif">GIF</string>
+    <string name="magic_create_text">Me aitama sul luua kasutajakontot veebiteenuses conversations.im.
+\nKui conversations.im on sinu teenusepakkuja, siis saad suhelda kõikide teiste teenusepakkujate serverites asuvate kasutajatega - lihtsalt edasta neile oma XMPP aadress.</string>
+    <string name="missing_internet_permission">Anna õigused internetiühenduse kasutamiseks</string>
+    <string name="me">Mina</string>
+    <string name="contact_asks_for_presence_subscription">Sinu kontakt soovib kasutada olekuteadete tellimist</string>
+    <string name="allow">Luba</string>
+    <string name="reconnect_on_other_host">Loo ühendus teises hostis</string>
+    <string name="no_microphone_permission">Anna rakendusele %1$s õigus kasutada mikrofoni</string>
+    <string name="please_wait">Palun oota korraks…</string>
+    <string name="search_messages">Otsi sõnumeid</string>
+    <string name="view_conversation">Vaata vestlust</string>
+    <string name="pref_use_share_location_plugin">Asukoha jagamise lisamoodul</string>
+    <string name="pref_use_share_location_plugin_summary">Rakenduses leiduva kaardi asemel kasuta asukoha jagamise lisamoodulit</string>
+    <string name="share_as_uri">Jaga XMPP võrguaadressina</string>
+    <string name="error_unable_to_create_temporary_file">Ajutise faili loomine ei õnnestunud</string>
+    <string name="this_device_has_been_verified">See seade on verifitseeritud</string>
+    <string name="copy_fingerprint">Kopeeri sõrmejälg</string>
+    <string name="all_omemo_keys_have_been_verified">Sa oled verifitseerinud kõik sinu valduses olevad OMEMO-võtmed</string>
+    <string name="barcode_does_not_contain_fingerprints_for_this_chat">See triipkood ei sisalda selle vestluse sõrmejägi.</string>
+    <string name="verified_fingerprints">Verifitseeritud sõrmejäljed</string>
+    <string name="use_camera_icon_to_scan_barcode">Kasuta kaamerat kontakti triipkoodi skaneerimiseks</string>
+    <string name="please_wait_for_keys_to_be_fetched">Palun oota, kuni võtmed saavad laaditud</string>
+    <string name="share_as_barcode">Jaga triipkoodina</string>
+    <string name="share_as_http">Jaga HTTP lingina</string>
+    <string name="pref_blind_trust_before_verification">Enne verifitseerimist usalda pimesi</string>
+    <string name="pref_blind_trust_before_verification_summary">Usalda verifitseerimata kontaktide uusi seadmeid, aga küsi kinnitust verifitseeritud kontaktide uute seadmete puhul.</string>
+    <string name="blindly_trusted_omemo_keys">Pimesi usaldatud OMEMO-võtmed. See võib tähendada, et tegemist on kellegi teisega või keegi kolmas suudab sõnumiliiklust jälgida.</string>
+    <string name="not_trusted">Pole usaldatud</string>
+    <string name="invalid_barcode">Vigane QR-kood</string>
+    <string name="pref_clean_cache_summary">Tühjenda puhverdatud failide kaust (mida kasutab kaamerarakendus)</string>
+    <string name="pref_clean_cache">Tühjenda puhver</string>
+    <string name="pref_clean_private_storage">Tühjenda privaatne vahemälu</string>
+    <string name="pref_clean_private_storage_summary">Tühjenda failide hoidmiseks mõeldud privaatne vahemälu (faile saad serverist uuesti alla laadida)</string>
+    <string name="sharing_application_not_grant_permission">Jagav rakendus ei andnud õigusi selle faili lugemiseks.</string>
+    <string name="jabber_network">jabber.network</string>
+    <string name="reconnecting_call">Kõne on uuesti ühendamisel</string>
+    <string name="reconnecting_video_call">Videokõne on uuesti ühendamisel</string>
+    <string name="abort_registration_procedure">Kas sa oled kindel, et soovid katkestada registreerimise?</string>
+    <string name="yes">Jah</string>
+    <string name="no">Ei</string>
+    <string name="verify_your_phone_number">Verifitseeri oma telefoninumber</string>
+    <string name="enter_country_code_and_phone_number">Quicksy saadab sulle telefoninumbri verifitseerimiseks SMS-sõnumi (võivad lisanduda sidevõrkude tasud). Sisesta maakood ja oma telefoninumber:</string>
+    <string name="we_will_be_verifying"><![CDATA[Järgnevaga me verifitseerime telefoninumbri <br/><br/><b>%s</b><br/><br/>Kas kõik on õige või sooviksid numbrit muuta?]]></string>
+    <string name="not_a_valid_phone_number">%s pole korrektne telefoninumber.</string>
+    <string name="please_enter_your_phone_number">Palun sisesta oma telefoninumber.</string>
+    <string name="search_countries">Otsi riike</string>
+    <string name="verify_x">Verifitseeri %s</string>
+    <string name="we_have_sent_you_an_sms_to_x"><![CDATA[Me oleme sulle siia saatnud SMS-sõnumi: <b>%s</b>.]]></string>
+    <string name="please_enter_pin_below">Palun sisesta 6-numbriline PIN-kood alljärgnevalt.</string>
+    <string name="resend_sms">Saada SMS uuesti</string>
+    <string name="resend_sms_in">Saada SMS uuesti (%s)</string>
+    <string name="wait_x">Palun oota (%s)</string>
+    <string name="please_enter_pin">Palun sisesta oma 6-numbriline PIN-kood.</string>
+    <string name="verifying">Verifitseerime…</string>
+    <string name="requesting_sms">Teeme päringu SMS-sõnumi kohta…</string>
+    <string name="incorrect_pin">Sinu sisestatud PIN-kood pole õige.</string>
+    <string name="pin_expired">Sinu sisestatud PIN-kood on aegunud.</string>
+    <string name="unknown_api_error_network">Tundmatu võrguühenduse viga.</string>
+    <string name="unknown_api_error_response">Tundmatu vastus serverilt.</string>
+    <string name="unable_to_connect_to_server">Ühenduse loomine serveriga ei õnnestunud.</string>
+    <string name="invalid_user_input">Vigane sisend kasutajalt</string>
+    <string name="temporarily_unavailable">Teenus pole ajutiselt saadaval. Palun proovi hiljem uuesti.</string>
+    <string name="no_network_connection">Võrguühendus puudub.</string>
+    <string name="try_again_in_x">Palun oota %s ja proovi uuesti</string>
+    <string name="rate_limited">Sinu tehtavatele päringutele kehtib hetkel ajaühikuline piirang</string>
+    <string name="too_many_attempts">Liiga palju päringuid</string>
+    <string name="restore_warning">Ära kasuta varukoopiat olemasoleva paigalduse kloonimiseks (samaaegseks käivitamiseks). Varukoopiast taastamine on mõeldud vaid teise seadmesse kolimise jaoks ning juhuks, kui kaotad oma algse nutiseadme.</string>
+    <string name="restore_warning_continued">Palun ära kasuta varukoopiaid, mida sa pole ise teinud!</string>
+    <string name="unable_to_restore_backup">Varukoopiast taastamine ei õnnestunud.</string>
+    <string name="local_server">Kohalik server</string>
+    <string name="pref_channel_discovery_summary">Enamus kasutajaid peaksid eelistama valikut „jabber.network“. See tagab asjakohasemad soovitused kogu avalikust XMPP võrgustikust.</string>
+    <string name="pref_channel_discovery">Kanalite tuvastamise meetod</string>
+    <string name="backup">Varukoopia</string>
+    <string name="category_about">Rakenduse teave</string>
+    <string name="please_enable_an_account">Palun võta mõni kasutajakonto kasutusele</string>
+    <string name="make_call">Helista</string>
+    <string name="rtp_state_incoming_call">Saabuv kõne</string>
+    <string name="rtp_state_incoming_video_call">Saabuv videokõne</string>
+    <string name="rtp_state_content_add">Kas lisame veel meediavoogusid?</string>
+    <string name="rtp_state_connecting">Ühendame</string>
+    <string name="rtp_state_connected">Ühendatud</string>
+    <string name="rtp_state_reconnecting">Ühendame uuesti</string>
+    <string name="rtp_state_accepting_call">Võtame kõnet vastu</string>
+    <string name="rtp_state_ending_call">Lõpetame kõnet</string>
+    <string name="answer_call">Vasta</string>
+    <string name="dismiss_call">Keeldu</string>
+    <string name="rtp_state_finding_device">Tuvastame seadmeid</string>
+    <string name="rtp_state_ringing">Helistame</string>
+    <string name="rtp_state_contact_offline">Kontakt pole kättesaadav</string>
+    <string name="rtp_state_connectivity_error">Kõne ühendamine ei õnnestunud</string>
+    <string name="rtp_state_connectivity_lost_error">Ühendus katkes</string>
+    <string name="rtp_state_retracted">Kõne on katkestatud</string>
+    <string name="rtp_state_application_failure">Rakenduse viga</string>
+    <string name="rtp_state_security_error">Viga verifitseerimisel</string>
+    <string name="hang_up">Lõpeta kõne</string>
+    <string name="ongoing_call">Kõne on pooleli</string>
+    <string name="we_have_sent_you_another_sms">Me oleme saatnud sulle uue SMS-sõnumi 6-numbrilise koodiga.</string>
+    <string name="disable_tor_to_make_call">Helistamiseks lülita Tori võrguühendus välja</string>
+    <string name="back">Tagasi</string>
+    <string name="unable_to_establish_secure_connection">Turvalise ühenduse loomine ei õnnestunud.</string>
+    <string name="possible_pin">Automaatselt asetasime lõikelaualt võimaliku PIN-koodi.</string>
+    <string name="unable_to_find_server">Serveri leidmine ei õnnestunud.</string>
+    <string name="something_went_wrong_processing_your_request">Midagi läks valesti sinu päringu töötlemisel.</string>
+    <string name="ongoing_video_call">Videokõne on pooleli</string>
+    <string name="device_does_not_support_data_saver">Sinu nutiseadme operatsioonisüsteem ei toeta andmesäästja väljalülitamist %1$s jaoks.</string>
+    <string name="data_saver_enabled_explained">Sinu nutiseadme operatsioonisüsteem piirab taustal töötamise ajal %1$s ligipääsu internetiühendusele. Et sa saaksid teavitusi uute sõnumite kohta ka siis, kui „Andmesäästja“ on kasutusel, siis palun luba, et %1$s saaks piiramatult kasutada võrguühendust.
+\nKui vähegi võimalik, siis ka %1$s üritab säästa andmeliikluse mahtu.</string>
+    <string name="verifying_omemo_keys_trusted_source_account">Oled verifitseerimas oma kasutajakonto OMEMO-võtmeid. See tegevus on turvaline vaid siis, kui leidsid lingi usaldusväärsest allikast, sellisest, kuhu vaid sina said selle lisada.</string>
+    <string name="continue_btn">Jätka</string>
+    <string name="verify_omemo_keys">Verifitseeri OMEMO-võtmed</string>
+    <string name="i_followed_this_link_from_a_trusted_source">Ma klõpsisin seda linki allikas, mida usaldan</string>
+    <string name="verifying_omemo_keys_trusted_source">Oled verifitseerimas %1$s OMEMO-võtmeid niipea, kui linki klõpsid. See tegevus on turvaline vaid siis, kui leidsid lingi usaldusväärsest allikast, sellisest, kuhu vaid %2$s oleks võinud selle lisada.</string>
+    <plurals name="days">
+        <item quantity="one">%d päev</item>
+        <item quantity="other">%d päeva</item>
+    </plurals>
+    <string name="distrust_omemo_key">Lõpeta seadme usaldamine</string>
+    <string name="encrypting_message">Krüptime sõnumit</string>
+    <string name="not_fetching_history_retention_period">Ei laadi sõnumeid, kuna kohalik keeluperiood veel kehtib.</string>
+    <string name="pref_automatically_delete_messages">Kustuta sõnumid automaatselt</string>
+    <string name="pref_automatically_delete_messages_description">Kustuta sellest seadmest automaatselt sõnumid, mille vanus ületab seadistatud aja.</string>
+    <plurals name="seconds">
+        <item quantity="one">%d sekund</item>
+        <item quantity="other">%d sekundit</item>
+    </plurals>
+    <plurals name="months">
+        <item quantity="one">%d kuu</item>
+        <item quantity="other">%d kuud</item>
+    </plurals>
+    <string name="hide_inactive_devices">Peida mitteaktiivsed</string>
+    <plurals name="minutes">
+        <item quantity="one">%d minut</item>
+        <item quantity="other">%d minutit</item>
+    </plurals>
+    <plurals name="hours">
+        <item quantity="one">%d tund</item>
+        <item quantity="other">%d tundi</item>
+    </plurals>
+    <plurals name="weeks">
+        <item quantity="one">%d nädal</item>
+        <item quantity="other">%d nädalat</item>
+    </plurals>
+    <string name="show_inactive_devices">Näita mitteaktiivseid</string>
+    <string name="transcoding_video">Tihendame videot</string>
+    <string name="corresponding_chats_closed">Vastavad vestlused on arhiveeritud.</string>
+    <string name="contact_blocked_past_tense">Kontakt on blokeeritud.</string>
+    <string name="pref_notifications_from_strangers">Teavitused tundmatutelt isikutelt</string>
+    <string name="pref_notifications_from_strangers_summary">Teavita, kui tundmatud on saatnud sõnumeid või helistanud.</string>
+    <string name="received_message_from_stranger">Said sõnumi tundmatult isikult</string>
+    <string name="block_stranger">Blokeeri tundmatu saatja</string>
+    <string name="block_entire_domain">Blokeeri kogu domeen</string>
+    <string name="online_right_now">hetkel võrgus</string>
+    <string name="retry_decryption">Proovi dekrüptimist uuesti</string>
+    <string name="session_failure">Sessiooniviga</string>
+    <string name="sasl_downgrade">Nõrgendatud SASLi meetod</string>
+    <string name="account_status_regis_web">Server eeldab registreerimist veebisaidis</string>
+    <string name="open_website">Ava veebisait</string>
+    <string name="application_found_to_open_website">Veebisaidi avamiseks ei leidu rakendust</string>
+    <string name="pref_headsup_notifications">Teavitused väljaspool olekuriba</string>
+    <string name="pref_headsup_notifications_summary">Näita teavitusi väljaspool olekuriba (Heads-Up notifications)</string>
+    <string name="distrust_omemo_key_text">Kas sa oled kindel, et tahad sellelt seadmelt eemaldada verifitseerimise?
+\nSee seade ja temalt saabunud sõnumid on nüüd märgitud kui „Pole usaldatud“.</string>
+    <string name="providing_a_name_is_optional">Nime lisamine pole kohustuslik</string>
+    <string name="silent_messages_channel_description">See teavituste kategooria on kasutusel selliste teavituste kuvamiseks, mille puhul ei peaks helilist teavitust kasutama. Näiteks kui sa tegutsed mõnes muus nutiseadmes (teavituste keelamise viiteaeg).</string>
+    <string name="ongoing_calls_channel_name">Pooleliolevad kõned</string>
+    <string name="missed_calls_channel_name">Vastamata kõned</string>
+    <string name="silent_messages_channel_name">Vaiksed sõnumid</string>
+    <string name="incoming_call">Saabuv kõne</string>
+    <string name="incoming_call_duration_timestamp">Saabuv kõne (%s) · %s</string>
+    <string name="unable_to_parse_invite">Kutse töötlemine ei õnnestunud</string>
+    <string name="plain_text_document">Vormindamata tekst</string>
+    <string name="account_registrations_are_not_supported">Kasutajakontode registreerimine pole toetatud</string>
+    <string name="pref_notifications_summary">Teavituste keelamise viiteaeg, helin, värinaalarm, tundmatud saatjad</string>
+    <string name="pref_attachments_summary">Failide suurus, piltide pakkimine, videote kvaliteet</string>
+    <string name="notifications">Teavitused</string>
+    <string name="pref_category_sending">Saatmine</string>
+    <string name="pref_category_receiving">Vastuvõtmine</string>
+    <string name="pref_large_font">Suur font</string>
+    <string name="pref_large_font_summary">Näita jutumullides tekste suuremana</string>
+    <string name="pref_up_long_summary">Toimides UnifiedPushi levitajana, kasutatakse meie püsivat, usaldusväärset ja akusõbralikku XMPP-ühendust teiste UnifiedPushiga ühilduvate rakenduste (Tusky, Ltt.rs, FluffyChat ja paljud teised) äratamiseks.</string>
+    <string name="pref_accept_invites_from_strangers">Kutsed tundmatutelt saatjatelt</string>
+    <string name="error_trustkey_hint_mutual">Vihje: mõnel juhul õnnestub seda viga parandada, kui mõlemad osapooled üksteist oma kontaktiloendisse lisavad.</string>
+    <string name="disable_encryption_message">Kas sa oled kindel, et soovid OMEMO-põhise krüptimise selle vestluse jaoks välja lülitada?
+\nSee võimaldab sinu serveri haldajal seinu sõnumeid lugeda, kuid see võib olla ainus võimalus suhelda osapooltega, kes kasutavad aegunud sõnumikliente.</string>
+    <string name="disable_now">Lülita välja nüüd</string>
+    <string name="draft">Visand:</string>
+    <string name="pref_omemo_setting">OMEMO-põhine krüptimine</string>
+    <string name="foreground_service_channel_name">Teenus esiplaanil</string>
+    <string name="foreground_service_channel_description">See teavituste kategooria on kasutusel selliste püsiteavituste kuvamiseks, mis märgivad, et %1$s töötab.</string>
+    <string name="notification_group_status_information">Olekuteave</string>
+    <string name="error_channel_name">Võrguühenduse probleemid</string>
+    <string name="unable_to_save_recording">Salvestise salvestamine ei õnnestunud</string>
+    <string name="error_channel_description">See teavituste kategooria on kasutusel teavituse kuvamiseks olukorras, kus serveris asuva kasutajakontoga ühenduse loomine ei õnenstu.</string>
+    <string name="notification_group_messages">Sõnumid</string>
+    <string name="notification_group_calls">Kõned</string>
+    <string name="messages_channel_name">Sõnumid</string>
+    <string name="incoming_calls_channel_name">Saabuvad kõned</string>
+    <string name="delivery_failed_channel_name">Vead kohaletoimetamisel</string>
+    <string name="pref_message_notification_settings">Sõnumite teavituste seadistused</string>
+    <string name="pref_incoming_call_notification_settings">Saabuvate kõnede teavituste seadistused</string>
+    <string name="pref_more_notification_settings_summary">Tähtsus, helid, värinaalarm</string>
+    <string name="video_compression_channel_name">Video pakkimine ja tihendamine</string>
+    <string name="view_media">Näita meediat</string>
+    <string name="outgoing_call_duration_timestamp">Väljuv kõne (%s) · %s</string>
+    <string name="outgoing_call">Väljuv kõne</string>
+    <string name="outgoing_call_timestamp">Väljuv kõne · %s</string>
+    <string name="missed_call_timestamp">Vastamata kõne · %s</string>
+    <string name="missed_call">Vastamata kõne</string>
+    <plurals name="n_missed_calls_from_x">
+        <item quantity="one">%1$d vastamata kõne kasutajalt %2$s</item>
+        <item quantity="other">%1$d vastamata kõnet kasutajalt %2$s</item>
+    </plurals>
+    <string name="audio_call">Häälkõne</string>
+    <string name="video_call">Videokõne</string>
+    <string name="help">Abiteave</string>
+    <string name="switch_to_chat">Lülita ümber vestlusele</string>
+    <string name="microphone_unavailable">Sinu nutiseadme mikrofon pole saadaval</string>
+    <string name="only_one_call_at_a_time">Korraga saad osaleda vaid ühes kõnes.</string>
+    <string name="return_to_ongoing_call">Tagasi poolelioleva kõne juurde</string>
+    <string name="could_not_switch_camera">Kaamera vahetamine ei õnnestunud</string>
+    <string name="server_does_not_support_easy_onboarding_invites">Server ei toeta kutsete loomist</string>
+    <string name="no_active_accounts_support_this">Ükski aktiivne konto ei toeta seda funktsionaalsust</string>
+    <plurals name="view_users">
+        <item quantity="one">Näita %1$d osalejat</item>
+        <item quantity="other">Näita %1$d osalejat</item>
+    </plurals>
+    <plurals name="some_messages_could_not_be_delivered">
+        <item quantity="one">Sõnumi kohaletoimetamine ei õnnestunud</item>
+        <item quantity="other">Mõnede sõnumite kohaletoimetamine ei õnnestunud</item>
+    </plurals>
+    <string name="failed_deliveries">Vead kohaletoimetamisel</string>
+    <string name="more_options">Täiendavad valikud</string>
+    <string name="no_application_found">Rakendust ei leidunud</string>
+    <string name="invite_to_app">Kutsu kasutama Conversationsit</string>
+    <string name="unable_to_enable_video">Video kasutuselevõtmine ei õnnestu.</string>
+    <string name="could_not_disable_video">Video kasutuselt eemaldamine ei õnnestu.</string>
+    <string name="unified_push_distributor">UnifiedPushi levitaja</string>
+    <string name="pref_up_push_account_summary">Kasutajakonto, mille kaudu tõukesõnumeid saadakse.</string>
+    <string name="pref_up_push_server_title">Tõukesõnumite server</string>
+    <string name="no_account_deactivated">Puudub (pole kasutusel)</string>
+    <string name="decline">Keeldu</string>
+    <string name="delete_from_server">Eemalda kasutajakonto serverist</string>
+    <string name="could_not_delete_account_from_server">Kasutajakonto eemaldamine serverist ei õnnestunud</string>
+    <string name="contact_uses_unverified_keys">Vestluse teine osapool kasutab verifitseerimata nutiseadmeid. Verifitseerimiseks skaneeri tema QR-koodi ja takista võimalikke vahendusründeid.</string>
+    <string name="privacy_policy">Privaatsuspoliitika</string>
+    <string name="contact_list_integration_not_available">Kontaktiloendi lõimimine pole hetkel saadaval</string>
+    <string name="unverified_devices">Sina kasutad verifitseerimata nutiseadmeid. Verifitseerimiseks skaneeri oma muus sedames kuvatavat QR-koodi ja takista võimalikke vahendusründeid.</string>
+    <string name="report_spam">Teata spämmist</string>
+    <string name="report_spam_and_block">Teata spämmist ja blokeeri selle saatja</string>
+    <string name="call_integration_not_available">Kõnede lõimimine pole hetkel saadaval!</string>
+    <string name="delete_and_close">Kustuta ja arhiveeri vestlus</string>
+    <string name="no_certificate_selected">Kliendisertifikaati pole valitud!</string>
+    <string name="pref_title_interface">Liides</string>
+    <string name="pref_summary_appearance">Välimus, värvid, ekraanitõmmised, sisendid</string>
+    <string name="pref_title_security">Turvalisus</string>
+    <string name="pref_summary_security">Läbiv krüptimine, pimesi usaldamine enne verifitseerimist, vahendusründe tuvastamine</string>
+    <string name="unified_push_summary">Teavituste edastamine UnifiedPushiga ühilduvatele kolmandate osapoolte rakendustele</string>
+    <string name="pref_automatic_download">Automaatne allalaadimine</string>
+    <string name="appearance">Välimus</string>
+    <string name="pref_light_dark_mode">Hele või tume kujundus</string>
+    <string name="detect_mim">Nõua kanaliga sidumist</string>
+    <string name="detect_mim_summary">Edastuskanaliga sidumine võib aidata vahendusrünnete tuvastamisel</string>
+    <string name="pref_category_server_connection">Ühendus serveriga</string>
+    <string name="pref_category_operating_system">Operatsioonisüsteem</string>
+    <string name="pref_privacy_summary">Kirjutamisteatised, viimati nähtud, kättesaadavus</string>
+    <string name="pref_connection_summary">Hosti nimi ja port, ühendus Tori võrguga</string>
+    <string name="pref_connection_summary_w_cd">Hosti nimi ja port, ühendus Tori võrguga, kanalite tuvastamine</string>
+    <string name="pref_keyboard_options">Klahvistik</string>
+    <string name="unsupported_operation">Tegevus pole toetatud</string>
+    <string name="pref_category_engagement_notifications">Osalusest teavitamine</string>
+    <string name="pref_fullscreen_notification_summary">Luba sellel rakendusel näidata saabuvate kõnede teavitusi täisekraanivaates ka siis, kui seade on lukustatud.</string>
+    <string name="pref_category_application">Rakendus</string>
+    <string name="pref_category_interaction">Koostoime</string>
+    <string name="pref_category_on_this_device">Seadmes</string>
+    <string name="pref_backup_summary">Üks kord tehtav või korduv varundus</string>
+    <string name="pref_create_backup_one_off_summary">Tee varukoopia vaid nüüd</string>
+    <string name="pref_backup_recurring">Tee varukoopiaid korduvalt</string>
+    <string name="pref_fullscreen_notification">Täisekraaniteavitused</string>
+    <string name="delete_pgp_key">Kustuta OpenPGP võti</string>
+    <string name="edit_configuration">Muuda seadistusi</string>
+    <string name="allow_private_messages">Luba privaatsed sõnumid</string>
+    <string name="edit_nick">Muuda hüüdnime</string>
+    <plurals name="n_missed_calls">
+        <item quantity="one">%d vastamata kõne</item>
+        <item quantity="other">%d vastamata kõnet</item>
+    </plurals>
+    <plurals name="n_missed_calls_from_m_contacts">
+        <item quantity="one">%1$d vastamata kõne %2$dlt helistajalt</item>
+        <item quantity="other">%1$d vastamata kõnet %2$dlt helistajalt</item>
+    </plurals>
+    <string name="could_not_modify_call">Kõne muutmine ei õnnestunud</string>
+    <string name="clients_may_not_support_av">Vestluse teise osapoole XMPP-klient ilmselt ei toeta hääl- ja videokõnesid.</string>
+    <string name="pref_show_avatars_summary">Näita oma sõnumite puhul tunnuspilte otsevestlustes ja vestlusrühmades.</string>
+    <string name="pref_show_avatars">Näita tunnuspilte</string>
+    <string name="pref_chat_bubbles">Jutumullid</string>
+    <string name="pref_chat_bubbles_summary">Taustavärv, kirjatüübi suurus, tunnuspildid</string>
+    <string name="pref_title_bubbles">Jutumullid</string>
+    <string name="pref_align_start">Kõned joonduvad vasakule</string>
+    <string name="pref_align_start_summary">Kõik saadetud ja saadunud sõnumid joonduvad vasakule ning moodustavad ühtselt kujundatud sõnumivoo.</string>
+    <string name="custom_notifications">Sinu seadistatud teavitused</string>
+    <string name="pref_call_integration_summary">Kõned suudavad suhestuda nutiseadme muude kõnede loogikaga, näiteks uue kõne algamisel eelmine kõne lõppeb.</string>
+    <string name="custom_notifications_enable">Kas kasutad selle vestluse jaoks enda määratud teavituste seadistusi (olulisus, helimärguanded, värina kasutamine)?</string>
+    <string name="pref_call_integration">Kõnede lõimimine</string>
+    <string name="delete_avatar_message">Kas sa sooviksid oma tunnuspildi kustutada? Palun arvesta, et mitmed klientrakendused võivad jätkata vana puhverdatud pildi kasutamist.</string>
+    <string name="show_to_contacts_only">Näita vaid kontaktidele</string>
+    <string name="account_status_connection_timeout">Ühenduse on aegunud</string>
 </resources>
  
  
  
    
    @@ -679,7 +679,7 @@
     <string name="mtm_accept_cert">Accepter les certificats inconnus ?</string>
     <string name="mtm_trust_anchor">Le certificat du serveur n\'est pas signé par une Autorité de Certification connue.</string>
     <string name="mtm_accept_servername">Accepter un nom de serveur qui ne correspond pas ?</string>
-    <string name="mtm_hostname_mismatch">Le serveur n\'a pu s\'authentifier en tant que « %s ». Le certificat est valide uniquement pour :</string>
+    <string name="mtm_hostname_mismatch">Le serveur n\'a pu s\'authentifier en tant que « %s ». Le certificat est valide uniquement pour :</string>
     <string name="mtm_connect_anyway">Désirez-vous quand-même vous connecter ?</string>
     <string name="mtm_cert_details">Détails du certificat :</string>
     <string name="once">Une fois</string>
  
  
  
    
    @@ -70,7 +70,7 @@
     <string name="problem_connecting_to_accounts">Non puido conectarse a múltiples contas</string>
     <string name="touch_to_fix">Toca para xestionar as túas contas</string>
     <string name="attach_file">Adxuntar</string>
-    <string name="not_in_roster">¿Engadir este contacto faltante a lista de contactos?</string>
+    <string name="not_in_roster">Engadir este novo contacto á lista de contactos?</string>
     <string name="add_contact">Engadir contacto</string>
     <string name="send_failed">Erro ao enviar</string>
     <string name="preparing_image">Preparándose para enviar a imaxe</string>
@@ -78,9 +78,7 @@
     <string name="sharing_files_please_wait">Compartindo ficheiros. Agarda…</string>
     <string name="action_clear_history">Baleirar historial</string>
     <string name="clear_conversation_history">Limpar historial da charla</string>
-    <string name="clear_histor_msg">¿Queres eliminar tódalas mensaxes desta conversa? 
-\n 
-\n<b>Aviso:</b> Esto non lle afecta ás mensaxes gardadas noutros dispositivos ou servidores.</string>
+    <string name="clear_histor_msg">Queres eliminar todas as mensaxes desta conversa?\n\n<b>Aviso:</b> Isto non lle afecta ás mensaxes gardadas noutros dispositivos ou servidores.</string>
     <string name="delete_file_dialog">Eliminar ficheiro</string>
     <string name="delete_file_dialog_msg">Tes a certeza de querer eliminar este ficheiro\?
 \n
@@ -94,7 +92,7 @@
     <string name="send_unencrypted">Enviar texto sen cifrar</string>
     <string name="decryption_failed">Fallou o desencriptado. Quizais non teñas a chave privada apropiada.</string>
     <string name="openkeychain_required">OpenKeychain</string>
-    <string name="openkeychain_required_long"><![CDATA[%1$s utiliza <b>OpenKeychain</b> para cifrar e descifrar as mensaxes e xestionar a túas chaves públicas.<br><br>Está baixo licenza  GPLv3+ e dispoñible en F-Droid e Google Play.<br><br><small>(Reinicia %1$s após a instalación.)</small>]]></string>
+    <string name="openkeychain_required_long"><![CDATA[%1$s utiliza <b>OpenKeychain</b> para cifrar e descifrar as mensaxes e xestionar a túas chaves públicas.<br><br>Está baixo licenza GPLv3+ e dispoñible en F-Droid e Google Play.<br><br><small>(Reinicia %1$s após a instalación.)</small>]]></string>
     <string name="restart">Reiniciar</string>
     <string name="install">Instalar</string>
     <string name="openkeychain_not_installed">Instala OpenKeychain por favor</string>
@@ -236,9 +234,7 @@
     <string name="delete_bookmark">Eliminar marcador</string>
     <string name="destroy_room">Destruír a parola do grupo</string>
     <string name="destroy_channel">Eliminar canle</string>
-    <string name="destroy_room_dialog">Tes a certeza de querer destruír esta conversa en grupo? 
-\n 
-\n<b>Aviso:</b> O parola en grupo será totalmente eliminado do servidor.</string>
+    <string name="destroy_room_dialog">Tes a certeza de querer destruír esta conversa en grupo?\n\n<b>Aviso:</b> A parola en grupo será totalmente eliminada do servidor.</string>
     <string name="destroy_channel_dialog">Tes a certeza de querer eliminar a canle?\n\n<b>Aviso:</b> A canle será eliminada completamente do servidor.</string>
     <string name="could_not_destroy_room">Non se puido eliminar a parola</string>
     <string name="could_not_destroy_channel">Non se puido eliminar a canle</string>
@@ -283,7 +279,7 @@
     <string name="pref_allow_message_correction_summary">Permitir aos teus contactos editar as súas mensaxes de xeito retroactivo</string>
     <string name="pref_expert_options">Axustes de experta</string>
     <string name="pref_expert_options_summary">Por favor ten tino con estos axustes</string>
-    <string name="title_activity_about_x">Acerca de %s</string>
+    <string name="title_activity_about_x">Sobre %s</string>
     <string name="title_pref_quiet_hours">Non molestar</string>
     <string name="title_pref_quiet_hours_start_time">Hora de inicio</string>
     <string name="title_pref_quiet_hours_end_time">Hora de finalización</string>
@@ -463,7 +459,7 @@
     <string name="download_failed_invalid_file">Fallou a descarga: ficheiro non válido</string>
     <string name="account_status_tor_unavailable">Sen acceso a rede Tor</string>
     <string name="account_status_bind_failure">Fallou a ligazón</string>
-    <string name="account_status_host_unknown">O servidor non corresponde a este dominio</string>
+    <string name="account_status_host_unknown">Non é responsable do dominio</string>
     <string name="server_info_broken">Roto</string>
     <string name="pref_presence_settings">Dispoñibilidade</string>
     <string name="pref_away_when_screen_off">Ausente se bloqueado</string>
@@ -622,7 +618,7 @@
     <string name="show_inactive_devices">Mostrar inactivos</string>
     <string name="hide_inactive_devices">Agochar inactivos</string>
     <string name="distrust_omemo_key">Retirar confianza a dispositivo</string>
-    <string name="distrust_omemo_key_text">Tes a certeza de que queres eliminar a verificación deste dispositivo?\nEste dispositivo e as súas mensaxes serán marcados como \"Non confiable\".</string>
+    <string name="distrust_omemo_key_text">Tes a certeza de querer retirar a verificación a este dispositivo?\nEste dispositivo e as súas mensaxes serán marcados como «Non confiable».</string>
     <plurals name="seconds">
         <item quantity="one">%d segundo</item>
         <item quantity="other">%d segundos</item>
@@ -775,7 +771,7 @@
     <string name="phone_number">número de teléfono</string>
     <string name="verify_your_phone_number">Valida o teu número de teléfono</string>
     <string name="enter_country_code_and_phone_number">Quicksy vaiche enviar unha mensaxe SMS (podería ter custos) para validar o teu número de teléfono. Escribe o código de país e número de teléfono:</string>
-    <string name="we_will_be_verifying">Validaremos o número de teléfono<br/><br/><b>%s</b><br/><br/>É correcto, ou queres modificar o número?</string>
+    <string name="we_will_be_verifying"><![CDATA[Comprobaremos o número de teléfono<br/><br/><b>%s</b><br/><br/>É correcto, ou queres modificar o número?]]></string>
     <string name="not_a_valid_phone_number">%s non é un número de teléfono válido.</string>
     <string name="please_enter_your_phone_number">Por favor escribe o teu número de teléfono.</string>
     <string name="search_countries">Buscar países</string>
@@ -880,13 +876,13 @@
     <string name="unable_to_perform_this_action">Non se puido completar a acción</string>
     <string name="open_join_dialog">Unirse a canle pública…</string>
     <string name="sharing_application_not_grant_permission">A aplicación que comparte non proporciona permiso para acceder ao ficheiro.</string>
-    <string name="group_chats_and_channels">Parolas en grupo e Canles</string>
+    <string name="group_chats_and_channels"><![CDATA[Parolas en Grupo e Canles]]></string>
     <string name="jabber_network">jabber.network</string>
     <string name="local_server">Servidor local</string>
     <string name="pref_channel_discovery_summary">A maioría das usuarias debería escoller \'jabber.network\' para obter mellores suxestións desde o ecosistema público XMPP.</string>
     <string name="pref_channel_discovery">Método de descubrimento de canles</string>
     <string name="backup">Copia de apoio</string>
-    <string name="category_about">Acerca de</string>
+    <string name="category_about">Sobre</string>
     <string name="please_enable_an_account">Activa unha conta por favor</string>
     <string name="make_call">Facer unha chamada</string>
     <string name="rtp_state_incoming_call">Chamada entrante</string>
@@ -1093,4 +1089,22 @@
     <string name="server_info_bind2">XEP-0386: Bind 2</string>
     <string name="could_not_add_reaction">Non se puido engadir a reacción</string>
     <string name="add_reaction">Engadir reacción…</string>
+    <string name="more_reactions">Outras reaccións</string>
+    <string name="add_reaction_title">Engadir reacción</string>
+    <string name="could_not_modify_call">Non se puido modificar a chamada</string>
+    <string name="clients_may_not_support_av">O cliente do teu contacto XMPP pode que non sexa compatible con chamadas de audio/video.</string>
+    <string name="pref_show_avatars">Mostrar avatares</string>
+    <string name="pref_chat_bubbles">Burbullas da conversa</string>
+    <string name="pref_title_bubbles">Burbullas na conversa</string>
+    <string name="pref_show_avatars_summary">Mostrar avatares para as túas mensaxes en conversas 1:1, ademáis de nas parolas en grupo.</string>
+    <string name="pref_chat_bubbles_summary">Cor de fondo, Tamaño da letra, Avatares</string>
+    <string name="pref_call_integration">Integración das chamadas</string>
+    <string name="pref_call_integration_summary">As chamadas desde esta app interactúan coas chamadas de teléfono normais, por exemplo finalizando unha chamada cando outra comeza.</string>
+    <string name="pref_align_start">Mensaxes enfeitadas á esquerda</string>
+    <string name="pref_align_start_summary">Mostrar todas as mensaxes, incluíndo as propias, no lado esquerdo para unha aparencia uniforme.</string>
+    <string name="custom_notifications">Notificacións personalizadas</string>
+    <string name="custom_notifications_enable">Activar axustes personalizados para as notificacións (importancia, son, vibración) nesta conversa?</string>
+    <string name="delete_avatar_message">Queres eliminar o teu avatar? Algúns clientes poderían continuar mostrando unha copia almacenada do teu avatar.</string>
+    <string name="show_to_contacts_only">Mostrar só aos contactos</string>
+    <string name="account_status_connection_timeout">Caducidade da conexión</string>
 </resources>
  
  
  
    
    @@ -26,8 +26,8 @@
     <string name="minute_ago">1 perce</string>
     <string name="minutes_ago">%d perce</string>
     <plurals name="x_unread_conversations">
-        <item quantity="one">%d olvasatlan beszélgetés</item>
-        <item quantity="other">%d olvasatlan beszélgetés</item>
+        <item quantity="one">%d olvasatlan csevegés</item>
+        <item quantity="other">%d olvasatlan csevegés</item>
     </plurals>
     <string name="sending">küldés…</string>
     <string name="message_decrypting">Üzenet visszafejtése. Kérem várjon…</string>
@@ -39,7 +39,7 @@
     <string name="moderator">Moderátor</string>
     <string name="participant">Résztvevő</string>
     <string name="visitor">Látogató</string>
-    <string name="remove_contact_text">Szeretné eltávolítani %s partnerét a partnerlistájából? Beszélgetései ezzel a partnerrel nem lesznek eltávolítva.</string>
+    <string name="remove_contact_text">Szeretné eltávolítani %s partnerét a partnerlistájából? A csevegése ezzel a partnerrel nem lesz eltávolítva.</string>
     <string name="block_contact_text">Szeretné tiltani %s partnert, hogy ne tudjon üzeneteket küldeni?</string>
     <string name="unblock_contact_text">Szeretné feloldani %s partner tiltását és lehetővé tenni számára az üzenetek küldését?</string>
     <string name="block_domain_text">%s összes partnerét tiltja?</string>
@@ -77,7 +77,7 @@
     <string name="preparing_images">Képek előkészítése az átvitelhez</string>
     <string name="sharing_files_please_wait">Fájlok megosztása. Kérem várjon…</string>
     <string name="action_clear_history">Előzmények törlése</string>
-    <string name="clear_conversation_history">Beszélgetés előzményeinek törlése</string>
+    <string name="clear_conversation_history">Csevegés előzményeinek törlése</string>
     <string name="clear_histor_msg">Biztosan törölni szeretné az összes üzenetet a beszélgetésen belül?
 \n
 \n<b>Figyelmeztetés:</b> Ez nem fogja érinteni a más eszközökön vagy kiszolgálókon tárolt üzeneteket.</string>
@@ -173,7 +173,7 @@
     <string name="unpublish_pgp_message">Biztosan el szeretné távolítani az OpenPGP nyilvános kulcsát a jelenléti közleményéből?\nA partnerei többé nem lesznek képesek OpenPGP titkosítású üzeneteket küldeni Önnek.</string>
     <string name="openpgp_has_been_published">Az OpenPGP nyilvános kulcs közzé lett téve.</string>
     <string name="mgmt_account_enable">Fiók engedélyezése</string>
-    <string name="mgmt_account_delete_confirm_text">A fiók törlésével az összes beszélgetési előzményei is eltávolításra kerülnek</string>
+    <string name="mgmt_account_delete_confirm_text">A fiók törlésével az összes csevegési előzménye is eltávolításra kerül</string>
     <string name="attach_record_voice">Hang rögzítése</string>
     <string name="account_settings_jabber_id">XMPP-cím</string>
     <string name="block_jabber_id">XMPP-cím tiltása</string>
@@ -240,7 +240,7 @@
     <string name="contact_added_you">A partner hozzáadta Önt a partnerlistához</string>
     <string name="add_back">Én is hozzáadom</string>
     <string name="contact_has_read_up_to_this_point">%s elolvasta eddig a pontig</string>
-    <string name="contacts_have_read_up_to_this_point">%selolvasta eddig a pontig</string>
+    <string name="contacts_have_read_up_to_this_point">%s elolvasta eddig a pontig</string>
     <string name="everyone_has_read_up_to_this_point">Mindenki elolvasta eddig a pontig</string>
     <string name="publish">Közzététel</string>
     <string name="touch_to_choose_picture">Érintse meg a profilképet egy fénykép kiválasztásához a galériából</string>
@@ -265,7 +265,7 @@
     <string name="request_now">Kérés most</string>
     <string name="ignore">Mellőzés</string>
     <string name="pref_security_settings">Biztonság</string>
-    <string name="pref_allow_message_correction">Üzenetjavítás engedélyezése</string>
+    <string name="pref_allow_message_correction">Üzenetek javítása</string>
     <string name="pref_allow_message_correction_summary">Engedélyezés a partnereknek, hogy visszamenőlegesen szerkesszék az üzeneteiket</string>
     <string name="pref_expert_options">Szakértő beállítások</string>
     <string name="pref_expert_options_summary">Legyen óvatos ezekkel</string>
@@ -299,8 +299,8 @@
     <string name="jabber_id_copied_to_clipboard">Az XMPP-cím a vágólapra másolva</string>
     <string name="error_message_copied_to_clipboard">A hibaüzenet a vágólapra másolva</string>
     <string name="web_address">webcím</string>
-    <string name="scan_qr_code">2D vonalkód beolvasása</string>
-    <string name="show_qr_code">2D vonalkód megjelenítése</string>
+    <string name="scan_qr_code">QR kód beolvasása</string>
+    <string name="show_qr_code">QR kód megjelenítése</string>
     <string name="show_block_list">Tiltólista megjelenítése</string>
     <string name="account_details">Fiók részletei</string>
     <string name="confirm">Megerősítés</string>
@@ -446,7 +446,7 @@
     <string name="pref_away_when_screen_off">Távol, ha az eszköz le van zárva</string>
     <string name="pref_away_when_screen_off_summary">Mutasson „Távoli”-ként, ha az eszköz le van zárva</string>
     <string name="pref_treat_vibrate_as_silent">Rezgés kezelése csendes módként</string>
-    <string name="pref_show_connection_options">Kiterjesztett kapcsolati beállítások</string>
+    <string name="pref_show_connection_options">Gépnév és port</string>
     <string name="pref_show_connection_options_summary">Gépnév és port beállításainak megjelenítése egy fiók beállításakor</string>
     <string name="hostname_example">xmpp.example.com</string>
     <string name="action_add_account_with_certificate">Bejelentkezés tanúsítvánnyal</string>
@@ -524,7 +524,7 @@
     <string name="gp_short">Rövid</string>
     <string name="gp_medium">Közepes</string>
     <string name="gp_long">Hosszú</string>
-    <string name="pref_broadcast_last_activity">Üzenetszórás használata</string>
+    <string name="pref_broadcast_last_activity">Utoljára elérhető</string>
     <string name="pref_broadcast_last_activity_summary">Tudassa partnereivel, hogy mikor használja a Conversations-t</string>
     <string name="pref_privacy">Adatvédelem</string>
     <string name="pref_theme_options">Téma</string>
@@ -565,7 +565,7 @@
     <string name="share_as_http">Megosztás HTTP hivatkozásként</string>
     <string name="pref_blind_trust_before_verification">Vak bizalom ellenőrzés előtt</string>
     <string name="not_trusted">Nem megbízható</string>
-    <string name="invalid_barcode">Érvénytelen 2D vonalkód</string>
+    <string name="invalid_barcode">Érvénytelen QR kód</string>
     <string name="pref_clean_cache_summary">Gyorsítótármappa törlése (Kamera alkalmazás által használt)</string>
     <string name="pref_clean_cache">Gyorsítótár törlése</string>
     <string name="pref_clean_private_storage">Személyes tárhely törlése</string>
@@ -647,14 +647,13 @@
     <string name="error_trustkey_device_list">Nem sikerült lekérni az eszközlistát</string>
     <string name="error_trustkey_bundle">Nem sikerült lekérni a titkosítási kulcsokat</string>
     <string name="error_trustkey_hint_mutual">Tipp: bizonyos esetekben ez megoldható azzal, hogy hozzáadják egymást a partnerlistákhoz.</string>
-    <string name="disable_encryption_message">Biztosan le szeretné tiltani az OMEMO titkosítást ennél a beszélgetésnél?
-\nEz lehetővé fogja tenni a kiszolgáló rendszergazdájának, hogy elolvassa az üzeneteket, de ez lehet az egyetlen módja annak, hogy kommunikáljon az elavult programokat használó emberekkel.</string>
+    <string name="disable_encryption_message">Biztosan le szeretné tiltani az OMEMO titkosítást ennél a csevegésnél?\nEz lehetővé fogja tenni a kiszolgáló rendszergazdájának, hogy elolvassa az üzeneteket, de ez lehet az egyetlen módja annak, hogy kommunikáljon az elavult programokat használó emberekkel.</string>
     <string name="disable_now">Letiltás most</string>
     <string name="draft">Vázlat:</string>
     <string name="pref_omemo_setting">OMEMO titkosítás</string>
     <string name="pref_omemo_setting_summary_always">Mindig az OMEMO lesz használva az egymással történő üzenetváltásokhoz és a személyes csoportos csevegésekhez.</string>
-    <string name="pref_omemo_setting_summary_default_on">Az OMEMO lesz alapértelmezetten használva az új beszélgetésekhez.</string>
-    <string name="pref_omemo_setting_summary_default_off">Az OMEMO-t kifejezetten be kell majd kapcsolni az új beszélgetésekhez.</string>
+    <string name="pref_omemo_setting_summary_default_on">Az OMEMO lesz alapértelmezetten használva az új csevegésekhez.</string>
+    <string name="pref_omemo_setting_summary_default_off">Az OMEMO-t kifejezetten be kell majd kapcsolni az új csevegésekhez.</string>
     <string name="create_shortcut">Gyorsbillentyű létrehozása</string>
     <string name="default_on">Alapértelmezetten be</string>
     <string name="default_off">Alapértelmezetten ki</string>
@@ -674,14 +673,14 @@
     <string name="please_wait">Kérem várjon…</string>
     <string name="search_messages">Üzenetek keresése</string>
     <string name="gif">GIF</string>
-    <string name="view_conversation">Beszélgetés megtekintése</string>
+    <string name="view_conversation">Csevegés megtekintése</string>
     <string name="pref_use_share_location_plugin">Helymegosztás bővítmény</string>
     <string name="pref_use_share_location_plugin_summary">A helymegosztás bővítmény használata a beépített térkép helyett</string>
     <string name="copy_link">Webcím másolása</string>
     <string name="copy_jabber_id">XMPP-cím másolása</string>
     <string name="p1_s3_filetransfer">HTTP fájlmegosztás S3-hoz</string>
     <string name="pref_start_search">Közvetlen keresés</string>
-    <string name="pref_start_search_summary">A „Beszélgetés indítása” képernyőn nyissa meg a billentyűzetet, és helyezze a kurzort a keresőmezőbe</string>
+    <string name="pref_start_search_summary">A „Csevegés indítása” képernyőn nyissa meg a billentyűzetet, és helyezze a kurzort a keresőmezőbe</string>
     <string name="group_chat_avatar">Csoportos csevegés profilképe</string>
     <string name="host_does_not_support_group_chat_avatars">A gép nem támogatja a csoportos csevegés profilképeket</string>
     <string name="only_the_owner_can_change_group_chat_avatar">Csak a tulajdonos tudja megváltoztatni a csoportos csevegés profilképét</string>
@@ -734,7 +733,7 @@
     <string name="resend_sms">SMS újraküldése</string>
     <string name="resend_sms_in">SMS újraküldése (%s)</string>
     <string name="wait_x">Kérem várjon (%s)</string>
-    <string name="back">vissza</string>
+    <string name="back">Vissza</string>
     <string name="possible_pin">Automatikusan beillesztett lehetséges PIN-kód a vágólapról.</string>
     <string name="please_enter_pin">Adja meg a 6 számjegyű PIN-kódot.</string>
     <string name="abort_registration_procedure">Biztosan meg szeretné szakítani a regisztrációs folyamatot?</string>
@@ -868,4 +867,234 @@
         <item quantity="one">%1$d résztvevő megtekintése</item>
         <item quantity="other">%1$d résztvevő megtekintése</item>
     </plurals>
+    <string name="welcome_header">Csatlakozzon a Beszélgetéshez</string>
+    <string name="pref_enter_is_send_summary">Az Enter gomb használata az üzenetküldéshez. A Ctrl+Entert mindig használhatja üzenetküldésre, akkor is, ha ez ki van kapcsolva.</string>
+    <string name="conference_technical_problems">Technikai okokból elhagyta ezt a csoportos csevegést</string>
+    <string name="pref_dnd_on_silent_mode">Elfoglalt csendes módban</string>
+    <string name="remove_bookmark_and_close">Szeretné eltávolítani a könyvjelzőt erről: %s és archiválni a csevegést?</string>
+    <string name="audiobook">Hangoskönyv</string>
+    <string name="pref_send_crash_reports">Hibajelentések küldése</string>
+    <string name="pref_treat_vibrate_as_dnd_summary">Mutasson Elfoglaltként, ha az eszköz rezgő módban van</string>
+    <string name="pref_dnd_on_silent_mode_summary">Mutasson Elfoglaltként, ha az eszköz csendes módban van</string>
+    <string name="hostname_or_onion">Szerver- vagy .onion-cím</string>
+    <string name="no_storage_permission">%1$s hozzáférést igényel a külső tárhelyhez</string>
+    <string name="no_camera_permission">%1$s hozzáférést igényel a kamerához</string>
+    <string name="quicksy_wants_your_consent">Quicksy a hozzájárulását kéri az adatai felhasználásához</string>
+    <string name="battery_optimizations_enabled_explained">Az eszköze jelentős akkumulátor-optimalizálást alkalmaz a %1$s esetében, ami késleltetett értesítésekhez vagy akár üzenetvesztéshez vezethet.\nJavasolt kikapcsolni az optimalizálást.</string>
+    <string name="battery_optimizations_enabled_dialog">Az eszköze jelentős akkumulátor-optimalizálást alkalmaz a %1$s esetében, ami késleltetett értesítésekhez vagy akár üzenetvesztéshez vezethet.\n\nMost az eszköz meg fogja kérni, hogy kapcsolja ki.</string>
+    <string name="welcome_header_quicksy">Üdvözöljük a Quicksyben!</string>
+    <string name="unable_to_connect_to_keychain">Nem sikerült csatlakozni az OpenKeychainhez</string>
+    <string name="reconnect_on_other_host">Újracsatlakozás másik kiszolgálón</string>
+    <string name="no_permission_to_place_call">Nincs jogosultság hívást indítani</string>
+    <string name="pref_delete_omemo_identities_summary">Az OMEMO kulcsok újragenerálása. Az összes partnerének újra ellenőriznie kell majd. Csak végső esetben használja.</string>
+    <string name="data_saver_enabled_explained">%1$s nem tud hozzáférni az internethez a háttérben az operációs rendszer korlátozásai miatt. Engedélyezze a teljes hozzáférést %1$s számára, amikor az</string>
+    <string name="no_keys_just_confirm">Már megbízott ezen személy ujjlenyomatában. A \"kész\" kiválasztásával csak megerősíti, hogy %s része a csoportos csevegésnek.</string>
+    <string name="sync_with_contacts_long">%1$s feldolgozza a névjegyzékét helyben, az eszközén, hogy megmutassa az egyező partnerek neveit és profilképeit az XMPP-n.\n\nSoha nem hagyja el névjegyzék-adat az eszközét!</string>
+    <string name="barcode_does_not_contain_fingerprints_for_this_chat">A vonalkód nem tartalmaz ujjlenyomatot ehhez a csevegéshez.</string>
+    <string name="title_activity_share_with">Megosztás ezzel…</string>
+    <string name="pref_use_colorful_bubbles">Színes csevegőbuborékok</string>
+    <string name="pref_use_colorful_bubbles_summary">Különböző háttérszínek a küldött és a fogadott üzeneteknél</string>
+    <string name="action_archive_chat">Csevegés archiválása</string>
+    <string name="title_activity_new_chat">Új csevegés</string>
+    <string name="remove_bookmark">Szeretné eltávolítani a könyvjezőlt erről: %s?</string>
+    <string name="archive_this_chat">Csevegés törlése ezután</string>
+    <string name="download_failed_invalid_file">Letöltés sikertelen: Érvénytelen fájl</string>
+    <string name="send_encrypted_message">Titkosított üzenet küldése</string>
+    <string name="account_state_logged_out">Kijelentkezve</string>
+    <string name="account_status_incompatible_client">Nem kompatibilis kliens</string>
+    <string name="title_undo_swipe_out_chat">Csevegés archiválva</string>
+    <string name="group_chats">Csoportos csevegések</string>
+    <string name="contacts_and_n_more_have_read_up_to_this_point">%1$s+%2$d másik elolvasta eddig a pontig</string>
+    <string name="pref_autojoin">Könyvjelzők szinkronizálása</string>
+    <string name="error_no_keys_to_trust_server_error">Nincs használható kulcs ehhez partnerhez.\nNem sikerült lekérdezni újabb kulcsokat a kiszolgálóról. Lehetséges, hogy probléma adódott a partnere kiszolgálójával?</string>
+    <string name="error_no_keys_to_trust_presence">Nincs használható kulcs ehhez a partnerhez.\nGyőződjön meg, hogy mindkettejüknek van</string>
+    <string name="multimedia_file">multimédia fájl</string>
+    <string name="search_group_chats">Csoportos csevegés keresése</string>
+    <string name="this_account_is_logged_out">Kijelentkezett ebből a fiókból</string>
+    <string name="continue_btn">Folytatás</string>
+    <string name="delivery_failed_channel_name">Sikertelen kézbesítések</string>
+    <plurals name="n_missed_calls_from_x">
+        <item quantity="one">%1$d nem fogadott hívás tőle: %2$s</item>
+        <item quantity="other">%1$d nem fogadott hívás tőle: %2$s</item>
+    </plurals>
+    <plurals name="n_missed_calls">
+        <item quantity="one">%d nem fogadott hívás</item>
+        <item quantity="other">%d nem fogadott hívás</item>
+    </plurals>
+    <string name="magic_create_text">A conversations.im oldalon talál egy útmutatót a fiók létrehozásához.\nHa a conversations.im-et választja kiszolgálóként, akkor más kiszolgálóként felhasználóival is kommunikálhat, ha megadja nekik a teljes XMPP-címét.</string>
+    <string name="device_does_not_support_data_saver">Az Ön készüléke nem támogatja az Adatkímélő kikapcsolását %1$s esetében.</string>
+    <string name="verifying_omemo_keys_trusted_source_account">Ön most fogja ellenőrizni a saját fiókjának OMEMO kulcsait. Ez csak akkor biztonságos, ha megbízható forrásból származó linket követett és ha maga tette azt közzé.</string>
+    <string name="distrust_omemo_key_text">Biztos, hogy el akarja távolítani az eszköz hitelesítését?\nEz az eszköz és az általa küldött üzenetek nem megbízható jelölést kapnak.</string>
+    <string name="corresponding_chats_closed">A megfelelő beszélgetések archiválva.</string>
+    <string name="foreground_service_channel_description">Ez az értesítési kategória egy állandó értesítés megjelenítésére szolgál, hogy %1$s fut továbbra is.</string>
+    <string name="outdated_backup_file_format">Ön egy elavult biztonsági mentési fájlformátumot próbál importálni</string>
+    <string name="rtp_state_content_add">Hozzáad további sávokat?</string>
+    <string name="rtp_state_content_add_video">Át szeretne váltani videóhívásra?</string>
+    <string name="rtp_state_security_error">Ellenőrzési probléma</string>
+    <string name="reconnecting_call">Újracsatlakozás a híváshoz</string>
+    <string name="incoming_call_duration_timestamp">Bejövő hívás (%s) · %s</string>
+    <string name="outgoing_call_duration_timestamp">Kimenő hívás (%s) · %s</string>
+    <string name="missed_call_timestamp">Nem fogadott hívás · %s</string>
+    <string name="switch_to_chat">Váltás a csevegésre</string>
+    <string name="add_to_favorites">Kitűzés felülre</string>
+    <string name="remove_from_favorites">Levétel felülről</string>
+    <string name="gpx_track">GPX nyomvonal</string>
+    <string name="could_not_correct_message">Nem sikerült kijavítani az üzenetet</string>
+    <string name="search_all_conversations">Minden csevegés</string>
+    <string name="search_this_conversation">Ez a csevegés</string>
+    <string name="your_avatar">Saját avatar</string>
+    <string name="avatar_for_x">Avatár %s profiljához</string>
+    <string name="encrypted_with_omemo">OMEMO-val titkosítva</string>
+    <string name="more_options">További lehetőségek</string>
+    <string name="failed_deliveries">Meghiúsult küldések</string>
+    <string name="unable_to_enable_video">A videó engedélyezése nem lehetséges.</string>
+    <string name="reject_switch_to_video">Videóra váltás iránti kérelem elutasítása</string>
+    <string name="pref_up_push_account_title">XMPP fiók</string>
+    <string name="pref_up_push_account_summary">Az a fiók, amelyen keresztül a push-üzenetek érkeznek.</string>
+    <string name="delete_from_server">Fiók eltávolítása a szerverről</string>
+    <string name="could_not_delete_account_from_server">Nem sikerült törölni a fiókot a szerverről</string>
+    <string name="unified_push_distributor">UnifiedPush terjesztő</string>
+    <string name="log_in">Bejelentkezés</string>
+    <string name="contact_uses_unverified_keys">Az Ön partnere nem ellenőrzött eszközt használ. Szkennelje be a QR-kódját, hogy elvégezzük az ellenőrzését a MITM-támadások megakadályozásához.</string>
+    <string name="unverified_devices">Ön nem ellenőrzött eszközöket használ. Az ellenőrzés elvégzéséhez és az aktív MITM-támadások megakadályozásához olvassa be a QR-kódot a többi eszközén.</string>
+    <string name="log_out">Kijelentkezés</string>
+    <string name="hide_notification">Értesítés elrejtése</string>
+    <string name="report_spam_and_block">Spam bejelentése és a feladó blokkolása</string>
+    <string name="delete_and_close">Beszélgetés törlése és archiválása</string>
+    <string name="call_integration_not_available">Hívásintegráció nem elérhető!</string>
+    <string name="no_certificate_selected">Nincs kiválasztva ügyféltanúsítvány!</string>
+    <string name="start_chat">Beszélgetés indítása</string>
+    <string name="pref_title_interface">Interfész</string>
+    <string name="pref_summary_appearance">Téma, színek, képernyőképek, bevitel</string>
+    <string name="pref_title_security">Biztonság</string>
+    <string name="unified_push_summary">Értesítések továbbítása az UnifiedPush kompatibilis harmadik féltől származó alkalmazások számára</string>
+    <string name="pref_notifications_summary">Időtúllépés, hang, rezgés, ismeretlen felhasználók</string>
+    <string name="notifications">Értesítések</string>
+    <string name="pref_attachments_summary">Fájlméret, képtömörítés, videó minőség</string>
+    <string name="pref_category_sending">Küldés</string>
+    <string name="pref_category_receiving">Fogadás</string>
+    <string name="pref_automatic_download">Automatikus letöltés</string>
+    <string name="appearance">Megjelenés</string>
+    <string name="pref_light_dark_mode">Világos/sötét mód</string>
+    <string name="pref_allow_screenshots">Képernyőképek engedélyezése</string>
+    <string name="pref_allow_screenshots_summary">Alkalmazás tartalmának megjelenítése az alkalmazásváltóban és a képernyőképek készítésének engedélyezése</string>
+    <string name="detect_mim">Csatorna összekapcsolás szükséges</string>
+    <string name="pref_title_trust_system_ca_store">Tanúsítványkiadó</string>
+    <string name="pref_title_trust_system_ca_store_summary">Bízzon a rendszer hitelesítésszolgáltatói tanúsítványaiban</string>
+    <string name="detect_mim_summary">A csatorna összekapcsolás képes felismerni néhány machine-in-the-middle támadást</string>
+    <string name="pref_category_server_connection">Szerver kapcsolat</string>
+    <string name="pref_category_operating_system">Operációs rendszer</string>
+    <string name="pref_connection_summary">Hostnév és port, Tor</string>
+    <plurals name="n_missed_calls_from_m_contacts">
+        <item quantity="one">%1$d nem fogadott hívás %2$d partnertől</item>
+        <item quantity="other">%1$d nem fogadott hívás %2$d partnertől</item>
+    </plurals>
+    <plurals name="some_messages_could_not_be_delivered">
+        <item quantity="one">Az üzenet elküldése sikertelen</item>
+        <item quantity="other">Több üzenetet nem sikerült elküldeni</item>
+    </plurals>
+    <string name="pref_summary_security">E2E titkosítás, vak bizalom az ellenőrzés előtt, MITM felderítés</string>
+    <string name="privacy_policy">Adatvédelmi szabályzat</string>
+    <string name="contact_list_integration_not_available">A partnerek integrációja nem elérhető</string>
+    <string name="help">Segítség</string>
+    <string name="could_not_modify_call">Nem sikerült módosítani a hívást</string>
+    <string name="allow_private_messages">Privát üzenetek engedélyezése</string>
+    <string name="pref_accept_invites_from_strangers_summary">Idegenek meghívásainak elfogadása csoportos csevegésekre</string>
+    <string name="pref_accept_invites_from_strangers">Meghívók idegenektől</string>
+    <string name="rtp_state_contact_offline">A partner nem elérhető</string>
+    <string name="rtp_state_connectivity_lost_error">A kapcsolat megszakadt</string>
+    <string name="pref_large_font">Nagy betűtípus</string>
+    <string name="pref_large_font_summary">A betűméret növelése az üzenetbuborékokban</string>
+    <string name="encrypted_with_openpgp">OpenPGP-vel titkosítva</string>
+    <string name="not_encrypted">Titkosítatlan</string>
+    <string name="exit">Kilépés</string>
+    <string name="record_voice_mail">Hangposta rögzítése</string>
+    <string name="play_audio">Audió lejátszása</string>
+    <string name="pause_audio">Audió szüneteltetése</string>
+    <string name="no_application_found">Nem található alkalmazás</string>
+    <string name="invite_to_app">Meghívás a Conversations-be</string>
+    <string name="unable_to_parse_invite">Nem lehet elemezni a meghívót</string>
+    <string name="server_does_not_support_easy_onboarding_invites">A szerver nem támogatja a meghívók generálását</string>
+    <string name="pref_backup_recurring">Ismétlődő biztonsági mentés</string>
+    <string name="pref_fullscreen_notification">Teljes képernyős értesítések</string>
+    <string name="pref_create_backup_one_off_summary">Egyszeri biztonsági mentés létrehozása</string>
+    <string name="unsupported_operation">Nem támogatott művelet</string>
+    <string name="edit_nick">Nick szerkesztése</string>
+    <string name="delete_pgp_key">OpenPGP kulcs törlése</string>
+    <string name="edit_name_and_topic">Név és téma szerkesztése</string>
+    <string name="edit_configuration">Konfiguráció módosítása</string>
+    <string name="change_notification_settings">Értesítési beállítások módosítása</string>
+    <string name="call_is_using_earpiece">A hívás a fülhallgatót használja.</string>
+    <string name="server_info_bind2">XEP-0386: Bind 2</string>
+    <string name="server_info_sasl2">XEP-0388: Bővíthető SASL profil</string>
+    <string name="omemo_fingerprint_selected_message">OMEMO ujjlenyomat (üzenet eredete)</string>
+    <string name="clear_other_devices_desc">Biztos, hogy törölni akarja az összes többi eszközt az OMEMO bejelentésből? A következő csatlakozáskor az eszközök újra bejelentik magukat, de előfordulhat, hogy nem kapják meg az időközben küldött üzeneteket.</string>
+    <string name="pref_blind_trust_before_verification_summary">Megbízik a nem ellenőrzött kapcsolatok új eszközeiben, de az ellenőrzött kapcsolatok esetén az új eszközök kézi megerősítése szükséges.</string>
+    <string name="pref_dynamic_colors">Dinamikus színek</string>
+    <string name="pref_dynamic_colors_summary">Rendszerszínek (Material You)</string>
+    <string name="no_microphone_permission">%1$s hozzáférése a mikrofonhoz</string>
+    <string name="missed_calls_channel_name">Nem fogadott hívások</string>
+    <string name="restore_warning_continued">Ne próbálkozzon olyan biztonsági mentések visszaállításával, amelyeket nem saját maga készített!</string>
+    <string name="rtp_state_reconnecting">Újracsatlakozás</string>
+    <string name="reconnecting_video_call">Újracsatlakozás a videó híváshoz</string>
+    <string name="outgoing_call_timestamp">Kimenő hívás · %s</string>
+    <string name="no_active_accounts_support_this">Egyetlen aktív fiók sem támogatja ezt a funkciót</string>
+    <string name="could_not_disable_video">Nem sikerült letiltani a videót.</string>
+    <string name="plain_text_document">Egyszerű szöveges dokumentum</string>
+    <string name="account_registrations_are_not_supported">A fiókregisztráció nem támogatott</string>
+    <string name="no_xmpp_adddress_found">Nem található XMPP cím</string>
+    <string name="account_status_temporary_auth_failure">Ideiglenes hitelesítési hiba</string>
+    <string name="delete_avatar">Avatár törlése</string>
+    <string name="audio_video_disabled_tor">A hívások letiltva Tor használatakor</string>
+    <string name="switch_to_video">Videóra váltás</string>
+    <string name="pref_up_push_server_title">Push kiszolgáló</string>
+    <string name="no_account_deactivated">Nincs (kikapcsolva)</string>
+    <string name="decline">Visszautasít</string>
+    <string name="report_spam">Spam jelentése</string>
+    <string name="pref_category_e2ee">Végponttól-végpontig titkosítás</string>
+    <string name="pref_privacy_summary">Írás, olvasás és elérhetőség kijelzése</string>
+    <string name="pref_connection_summary_w_cd">Hostnév és port, Tor, Channel Discovery</string>
+    <string name="pref_keyboard_options">Billentyűzet</string>
+    <string name="pref_category_engagement_notifications">Interakciós értesítések</string>
+    <string name="pref_category_application">Alkalmazás</string>
+    <string name="pref_category_interaction">Interakciók</string>
+    <string name="pref_category_on_this_device">Az eszközön</string>
+    <string name="pref_up_long_summary">Ha az UnifiedPush funkció engedélyezve van, akkor a rendszer egy megbízható és akkumulátorbarát XMPP kapcsolatot használ a többi UnifiedPush kompatibilis alkalmazás indításához, mint pl. Tusky, Ltt.rs, FluffyChat és mások.</string>
+    <string name="call_is_using_earpiece_tap_to_switch_to_speaker">A hívás a fülhallgatót használja. Koppintson a hangszóróra váltáshoz.</string>
+    <string name="call_is_using_wired_headset">A hívás vezetékes headsetet használ</string>
+    <string name="call_is_using_speaker_tap_to_switch_to_earpiece">A hívás a hangszórót használja. Koppintson a gombra a fülhallgatóra való váltáshoz.</string>
+    <string name="call_is_using_speaker">A hívás a hangszórót használja.</string>
+    <string name="call_is_using_bluetooth">A hívás bluetooth-t használ.</string>
+    <string name="flip_camera">Kamera váltás</string>
+    <string name="video_is_enabled_tap_to_disable">A videó engedélyezve. Koppintson a letiltásához.</string>
+    <string name="video_is_disabled_tap_to_enable">A videó le van tiltva. Érintse meg az engedélyezéséhez.</string>
+    <string name="server_info_login_mechanism">Bejelentkezési folyamat</string>
+    <string name="clients_may_not_support_av">Előfordulhat, hogy a partner XMPP alkalmazása nem támogatja a hanghívást/videóhívásokat.</string>
+    <string name="pref_call_integration">Hívás integráció</string>
+    <string name="pref_call_integration_summary">Az alkalmazásból indított hívások együttműködnek a hagyományos telefonhívásokkal, például az egyik hívás befejeződik, amikor egy másik kezdődik.</string>
+    <string name="pref_align_start">Balra igazított üzenetek</string>
+    <string name="pref_align_start_summary">Az összes üzenet (beleértve az elküldötteket is) a bal oldalon jeleníthető meg az egységes csevegési elrendezés érdekében.</string>
+    <string name="custom_notifications">Egyéni értesítések</string>
+    <string name="custom_notifications_enable">Engedélyez egyéni értesítési beállítások (fontosság, hang, rezgés) ehhez a beszélgetéshez?</string>
+    <string name="show_to_contacts_only">Csak a partnereknek mutassa</string>
+    <string name="add_reaction">Reakció hozzáadása…</string>
+    <string name="add_reaction_title">Reakció hozzáadása</string>
+    <string name="more_reactions">Több reakció</string>
+    <string name="add_contact_or_create_or_join_group_chat">Partner hozzáadása, csoport létrehozása, csoporthoz csatlakozás vagy csatornák felfedezése</string>
+    <string name="your_avatar_tap_to_select_new_avatar">Az avatárja. Érintse meg az új avatár kiválasztásához a galériából.</string>
+    <string name="backup_started_message">A biztonsági mentés elindult. A mentés befejezése után értesítést fog kapni.</string>
+    <string name="pref_backup_summary">Egyszeri, ismétlődő ütemezés létrehozása</string>
+    <string name="pref_fullscreen_notification_summary">Engedélyezze, hogy ez az alkalmazás a bejövő hívásokról teljes képernyős értesítéseket jelenítse meg amikor a készülék le van zárva.</string>
+    <string name="omemo_fingerprint_x509_selected_message">vOMEMO ujjlenyomat (üzenet eredete)</string>
+    <string name="request_presence_updates">Először kérjen a partnertől jelenlétfrissítéseket.\n\n<small>Ez alapján határozzuk meg, hogy a partnere milyen csevegőalkalmazást használ.</small>.</string>
+    <string name="without_mutual_presence_updates"><b>Figyelem:</b> Ha kölcsönös jelenlétfrissítések nélküli küldi ezt el, akkor az váratlan problémákat okozhat.\n\n<small>Menjen a „Partner részletei” menüpontba, hogy ellenőrizze a jelenlétfrissítéseket.</small></string>
+    <string name="all_omemo_keys_have_been_verified">Az összes birtokában lévő OMEMO-kulcs ellenőrizve</string>
+    <string name="blindly_trusted_omemo_keys">Vakon bizalomba fogadott OMEMO kulcsok. Ez azt a veszélyt is magába foglalja, hogy lehet valaki más kulcsa vagy hamisított a kulcs.</string>
+    <string name="could_not_add_reaction">Nem sikerült reakciót hozzáadni</string>
+    <string name="delete_avatar_message">Szeretné törölni az avatárját? Egyes alkalmazások továbbra is megjeleníthetik az avatárjának gyorsítótárazott másolatát.</string>
+    <string name="pref_show_avatars">Avatárok mutatása</string>
+    <string name="pref_show_avatars_summary">A csoportos csevegések mellett az üzenetekben és az 1:1-es csevegésekben is megjelennek az avatárok.</string>
+    <string name="pref_chat_bubbles">Csevegőbuborékok</string>
+    <string name="pref_chat_bubbles_summary">Háttérszín, betűméret, avatárok</string>
+    <string name="pref_title_bubbles">Csevegőbuborékok</string>
 </resources>
  
  
  
    
    @@ -1103,4 +1103,8 @@
     <string name="server_info_login_mechanism">Meccanismo di accesso</string>
     <string name="server_info_bind2">XEP-0386: Bind 2</string>
     <string name="server_info_sasl2">XEP-0388: Profilo SASL estensibile</string>
+    <string name="could_not_add_reaction">Impossibile aggiungere una reazione</string>
+    <string name="add_reaction">Aggiungi reazione…</string>
+    <string name="add_reaction_title">Aggiungi reazione</string>
+    <string name="more_reactions">Altre reazioni</string>
 </resources>
  
  
  
    
    @@ -169,7 +169,7 @@
     <string name="encryption_choice_omemo">OMEMO</string>
     <string name="mgmt_account_delete">アカウントを削除</string>
     <string name="mgmt_account_disable">一時的に無効化</string>
-    <string name="mgmt_account_publish_avatar">アバターを公開</string>
+    <string name="mgmt_account_publish_avatar">プロフィール画像を公開</string>
     <string name="mgmt_account_publish_pgp">OpenPGP 公開鍵を公開</string>
     <string name="unpublish_pgp">OpenPGP 公開鍵を削除</string>
     <string name="unpublish_pgp_message">出席情報告知から OpenPGP 公開鍵を削除してもよろしいですか?\n連絡先はあなたに OpenPGP 暗号化メッセージを送信できなくなります。</string>
@@ -759,7 +759,7 @@
     <string name="phone_number">電話番号</string>
     <string name="verify_your_phone_number">電話番号を検証</string>
     <string name="enter_country_code_and_phone_number">Quicksy から電話番号を検証するための SMS メッセージ(キャリア料金がかかる場合があります)が送信されます。国番号と電話番号を入力してください:</string>
-    <string name="we_will_be_verifying">電話番号の検証を行います<br/><br/><b>%s</b><br/><br/>これでよろしいでしょうか、それとも番号を編集しますか?</string>
+    <string name="we_will_be_verifying"><![CDATA[この電話番号の検証を行います<br/><br/><b>%s</b><br/><br/>これでよろしいでしょうか、それとも番号を編集しますか?]]></string>
     <string name="not_a_valid_phone_number">%s は有効な電話番号ではありません。</string>
     <string name="please_enter_your_phone_number">電話番号を入力してください。</string>
     <string name="search_countries">国を検索</string>
@@ -856,7 +856,7 @@
     <string name="this_looks_like_channel">これは談話室アドレスのようです</string>
     <string name="share_backup_files">バックアップファイルを共有</string>
     <string name="conversations_backup">Conversations のバックアップ</string>
-    <string name="event">出来事</string>
+    <string name="event">予定</string>
     <string name="open_backup">バックアップを開く</string>
     <string name="not_a_backup_file">選択したファイルは、 Conversations のバックアップファイルではありません</string>
     <string name="account_already_setup">このアカウントは既に設定されています</string>
@@ -1048,4 +1048,45 @@
     <string name="pref_backup_recurring">繰り返し作成</string>
     <string name="pref_backup_summary">一回限りもしくは繰り返し計画を作成</string>
     <string name="pref_create_backup_one_off_summary">一回限りのバックアップを作成</string>
+    <string name="your_avatar_tap_to_select_new_avatar">アバター。新しいアバターをギャラリーから選択するにはタップしてください。</string>
+    <string name="could_not_disable_video">ビデオを無効にできませんでした。</string>
+    <string name="edit_nick">ニックネームを編集</string>
+    <string name="delete_pgp_key">OpenPGP 鍵を削除</string>
+    <string name="edit_configuration">設定を変更</string>
+    <string name="change_notification_settings">通知設定を変更</string>
+    <string name="call_is_using_earpiece">通話はイヤホンを使用します。</string>
+    <string name="call_is_using_earpiece_tap_to_switch_to_speaker">通話はイヤホンを使用します。スピーカーに切り替えるにはタップしてください。</string>
+    <string name="call_is_using_speaker">通話はスピーカーを使用します。</string>
+    <string name="call_is_using_bluetooth">通話は bluetooth を使用します。</string>
+    <string name="video_is_enabled_tap_to_disable">ビデオが有効になっています。無効にするにはタップしてください。</string>
+    <string name="server_info_login_mechanism">ログインメカニズム</string>
+    <string name="call_is_using_wired_headset">通話は有線ヘッドセットを使用します</string>
+    <string name="call_is_using_speaker_tap_to_switch_to_earpiece">通話はスピーカーを使用します。イヤホンに切り替えるにはタップしてください。</string>
+    <string name="video_is_disabled_tap_to_enable">ビデオが無効になっています。有効にするにはタップしてください。</string>
+    <string name="server_info_bind2">XEP-0386: Bind 2</string>
+    <string name="channel_discover_opt_in_message">談話室の探索には <a href=https://search.jabber.network>search.jabber.network</a> というサードパーティのサービスを利用します。<br><br>この機能を使用すると、IP アドレスと検索語がこのサービスに送信されます。詳細は、<a href=https://search.jabber.network/privacy>プライバシーポリシー</a>をご覧ください。</string>
+    <string name="server_info_sasl2">XEP-0388: 拡張可能な SASL プロファイル</string>
+    <string name="could_not_add_reaction">リアクションを追加できませんでした</string>
+    <string name="add_reaction">リアクションを追加…</string>
+    <string name="add_reaction_title">リアクションを追加</string>
+    <string name="more_reactions">さらに多くのリアクション</string>
+    <string name="welcome_header">会話に参加しよう</string>
+    <string name="flip_camera">表裏反転カメラ</string>
+    <string name="edit_name_and_topic">名前と題を編集</string>
+    <string name="pref_category_engagement_notifications">活動の知らせ</string>
+    <string name="pref_category_interaction">インターアクション</string>
+    <string name="pref_up_long_summary">UnifiedPushディストリビューターとして動作する場合、永続的で信頼性が高い、バッテリーに優しいXMPP接続は、Tusky、Ltt.rs、FluffyChatなどの他のUnifiedPush互換アプリを起動するために利用されます。</string>
+    <string name="clients_may_not_support_av">連絡先のXMPPクライアントは音声/ビデオ通話を対応していない可能性があります。</string>
+    <string name="pref_align_start">メッセージを左揃えにする</string>
+    <string name="pref_align_start_summary">自分のメッセージを含むすべてのメッセージを左側に表示し、統一されたチャットレイアウトになります。</string>
+    <string name="custom_notifications">独自通知</string>
+    <string name="delete_avatar_message">プロフィール画像を削除しますか?連絡先のクライアントによっては、更新しないままプロフィール画像を表示し続ける場合があります。</string>
+    <string name="show_to_contacts_only">画像の受信を連絡先だけに許可する</string>
+    <string name="could_not_modify_call">変更できませんでした</string>
+    <string name="custom_notifications_enable">このチャットには独自の通知設定(優先度、通知音、バイブレーションなど)を使用しますか?</string>
+    <string name="pref_show_avatars">プロフィール画像を表示</string>
+    <string name="pref_show_avatars_summary">グループチャットと個人チャットでプロフィール画像を表示する。</string>
+    <string name="pref_chat_bubbles">ふきだし</string>
+    <string name="pref_chat_bubbles_summary">背景色、文字サイズ、プロフィール画像など</string>
+    <string name="pref_title_bubbles">ふきだし</string>
 </resources>
  
  
  
    
    @@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <bool name="show_avatar_incoming_call">false</bool>
+    <bool name="is_portrait_mode">false</bool>
 </resources>
  
  
  
    
    @@ -63,7 +63,7 @@
     <string name="save">Opslaan</string>
     <string name="ok">Oké</string>
     <string name="crash_report_title">%1$s is gecrasht</string>
-    <string name="crash_report_message">Door crashrapportages via uw XMPP account te sturen help je de ontwikkeling van %1$s.</string>
+    <string name="crash_report_message">Door crashrapportages via jouw XMPP-account te sturen help je de ontwikkeling van %1$s.</string>
     <string name="send_now">Nu versturen</string>
     <string name="send_never">Niet opnieuw vragen</string>
     <string name="problem_connecting_to_account">Verbinding met account mislukt</string>
@@ -822,7 +822,7 @@
     <string name="title_activity_share_with">Delen met…</string>
     <string name="archive_this_chat">Chat daarna verwijderen</string>
     <string name="remove_bookmark">Wil je de bladwijzer voor %s verwijderen?</string>
-    <string name="openkeychain_required_long">%1$s gebruikt <b>OpenKeychain</b> om berichten te versleutelen en te decoderen en het beheer van je openbare sleutels. <br>< br>Het is gelicentieerd onder GPLv3+ en beschikbaar op F-Droid en Google Play<br><br><small>(Start %1$s hierna opnieuw.)</small></string>
+    <string name="openkeychain_required_long"><![CDATA[%1$s gebruikt <b>OpenKeychain</b> om berichten te versleutelen en te decoderen en het beheer van je openbare sleutels. <br>< br>Het is gelicentieerd onder GPLv3+ en beschikbaar op F-Droid en Google Play<br><br><small>(Start %1$s hierna opnieuw.)</small>]]></string>
     <string name="contact_has_no_pgp_key">Kan het bericht niet versleutelen omdat de contactpersoon hun openbare sleutel niet aankondigt.
 \n
 \n<small>Vraag de contactpersoon om OpenPGP in te stellen. </small></string>
@@ -893,7 +893,7 @@
     <string name="pref_enter_is_send_summary">Gebruik de Enter-toets om een bericht te verzenden. Je kunt altijd Ctrl + Enter gebruiken om een bericht te verzenden, zelfs als deze optie is uitgeschakeld.</string>
     <string name="pref_treat_vibrate_as_dnd_summary">Bezet melden als het apparaat op trillen staat</string>
     <string name="clear_other_devices_desc">Weet je zeker dat je alle andere apparaten uit de OMEMO-aankondiging wilt verwijderen? De volgende keer dat je apparaten verbinding maken, zullen ze zichzelf opnieuw aankondigen, maar ontvangen ze mogelijk geen berichten die ondertussen zijn verzonden.</string>
-    <string name="account_status_host_unknown">De server is niet verantwoordelijk voor dit domein</string>
+    <string name="account_status_host_unknown">Niet verantwoordelijk voor domein</string>
     <string name="title_undo_swipe_out_chat">Chat gearchiveerd</string>
     <string name="no_permission_to_place_call">Geen toestemming om te bellen</string>
     <string name="no_keys_just_confirm">Je hebt de vingerafdruk van deze persoon al vertrouwd. Door \"Gereed\" te selecteren, bevestig je alleen dat %s deel uitmaakt van deze groepschat.</string>
@@ -1022,7 +1022,7 @@
     <string name="server_does_not_support_easy_onboarding_invites">Server biedt geen ondersteuning voor het aanmaken van uitnodigingen</string>
     <string name="outdated_backup_file_format">Je probeert een verouderd back-upbestandsformaat te importeren</string>
     <string name="sharing_application_not_grant_permission">De app voor delen heeft geen toestemming gegeven voor toegang tot dit bestand.</string>
-    <string name="group_chats_and_channels">Groepschats en kanalen</string>
+    <string name="group_chats_and_channels"><![CDATA[Groepschats en kanalen]]></string>
     <string name="jabber_network">jabber.network</string>
     <string name="pref_channel_discovery">Channel discovery-methode</string>
     <string name="rtp_state_incoming_call">Inkomende oproep</string>
@@ -1107,4 +1107,22 @@
     <string name="server_info_bind2">XEP-0386: Bind 2</string>
     <string name="add_reaction">Reactie toevoegen…</string>
     <string name="could_not_add_reaction">Kan reactie niet toevoegen</string>
+    <string name="add_reaction_title">Reactie toevoegen</string>
+    <string name="more_reactions">Meer reacties</string>
+    <string name="could_not_modify_call">Kan de aanroep niet wijzigen</string>
+    <string name="clients_may_not_support_av">De XMPP-client van je contactpersoon ondersteunt mogelijk geen audio-/videogesprekken.</string>
+    <string name="pref_chat_bubbles">Chat-bubbels</string>
+    <string name="pref_chat_bubbles_summary">Achtergrondkleur, Lettergrootte, Avatars</string>
+    <string name="pref_title_bubbles">Chat-bubbels</string>
+    <string name="pref_show_avatars_summary">Avatars weergeven in jouw berichten en in 1:1-chats, naast groepschats.</string>
+    <string name="pref_show_avatars">Avatars weergeven</string>
+    <string name="pref_call_integration">Integratie van gesprekken</string>
+    <string name="pref_align_start">Links uitgelijnde berichten</string>
+    <string name="pref_align_start_summary">Toon alle berichten, inclusief verzonden berichten, aan de linkerkant voor een uniforme chatlay-out.</string>
+    <string name="custom_notifications">Aangepaste meldingen</string>
+    <string name="custom_notifications_enable">Aangepaste meldingsinstellingen (belangrijkheid, geluid, trillingen) inschakelen voor dit gesprek?</string>
+    <string name="pref_call_integration_summary">Oproepen van deze app werken samen met gewone telefoongesprekken, zoals het beëindigen van een gesprek wanneer een ander begint.</string>
+    <string name="show_to_contacts_only">Alleen weergeven aan contacten</string>
+    <string name="delete_avatar_message">Wil je je avatar verwijderen? Sommige clients kunnen nog steeds een kopie van jouw avatar in de cache weergeven.</string>
+    <string name="account_status_connection_timeout">Time-out voor verbinding</string>
 </resources>
  
  
  
    
    @@ -465,7 +465,7 @@
     <string name="download_failed_invalid_file">Pobieranie nieudane: Nieprawidłowy plik</string>
     <string name="account_status_tor_unavailable">Sieć TOR jest niedostepna</string>
     <string name="account_status_bind_failure">Błąd połączenia (zasób)</string>
-    <string name="account_status_host_unknown">Serwer nie odpowiada domenie</string>
+    <string name="account_status_host_unknown">Nie odpowiada za domenę</string>
     <string name="server_info_broken">Zepsute</string>
     <string name="pref_presence_settings">Dostępność</string>
     <string name="pref_away_when_screen_off">Niedostępny kiedy urządzenie jest zablokowane</string>
@@ -1119,4 +1119,24 @@
     <string name="server_info_sasl2">XEP-0388: rozszerzalny profil SASL</string>
     <string name="server_info_bind2">XEP-0386: uproszczone nawiązywanie połączenia</string>
     <string name="server_info_login_mechanism">Mechanizm logowania</string>
+    <string name="could_not_add_reaction">Nie można dodać reakcji</string>
+    <string name="add_reaction">Dodaj reakcję…</string>
+    <string name="add_reaction_title">Dodaj reakcję</string>
+    <string name="more_reactions">Więcej reakcji</string>
+    <string name="could_not_modify_call">Nie można zmodyfikować rozmowy</string>
+    <string name="clients_may_not_support_av">Klient XMPP Twojego kontaktu może nie obsługiwać rozmów/wideorozmów.</string>
+    <string name="pref_show_avatars">Pokaż awatary</string>
+    <string name="pref_show_avatars_summary">Pokazuj awatary dla Twoich wiadomości i w rozmowach 1:1, a nie tylko w rozmowach grupowych.</string>
+    <string name="pref_chat_bubbles">Dymki rozmowy</string>
+    <string name="pref_chat_bubbles_summary">Kolor tła, rozmiar czcionki, awatary</string>
+    <string name="pref_title_bubbles">Dymki rozmowy</string>
+    <string name="pref_call_integration">Integracja rozmów</string>
+    <string name="pref_call_integration_summary">Rozmowy w tej aplikacji wchodzą w interakcję ze zwykłymi rozmowami telefonicznymi, np. kończenie jednej rozmowy gdy zaczyna się inna.</string>
+    <string name="pref_align_start">Wiadomości wyrównane do lewej</string>
+    <string name="pref_align_start_summary">Wyświetlaj wszystkie wiadomości, również wysłane, po lewej stronie, dla jednolitego układu rozmowy.</string>
+    <string name="custom_notifications">Własne powiadomienia</string>
+    <string name="custom_notifications_enable">Włączyć dedykowane ustawienia powiadomień (ważność, dźwięk, wibracje) dla tej rozmowy?</string>
+    <string name="delete_avatar_message">Czy chcesz usunąć swój awatar? Niektórzy klienci mogą nadal pokazywać zachowaną kopię.</string>
+    <string name="show_to_contacts_only">Pokazuj wyłącznie kontaktom</string>
+    <string name="account_status_connection_timeout">Limit czasu połączenia</string>
 </resources>
  
  
  
    
    @@ -119,7 +119,7 @@
     <string name="pref_notification_sound_summary">Som de notificação para novas mensagens</string>
     <string name="pref_call_ringtone_summary">Toque para chamadas recebidas</string>
     <string name="pref_notification_grace_period">Período de espera</string>
-    <string name="pref_notification_grace_period_summary">Espaço de tempo em que as notificações serão silenciadas, após detectar atividade em algum dos seus outros dispositivos.</string>
+    <string name="pref_notification_grace_period_summary">Por quanto tempo os avisos serão silenciados, após detectar atividade em algum dos seus outros dispositivos.</string>
     <string name="pref_advanced_options">Avançado</string>
     <string name="pref_never_send_crash_summary">Ao enviar os stack traces você está colaborando com o desenvolvimento</string>
     <string name="pref_confirm_messages">Confirmação de mensagens</string>
@@ -269,7 +269,7 @@
     <string name="next">Próximo</string>
     <string name="server_info_session_established">Sessão estabelecida</string>
     <string name="skip">Pular</string>
-    <string name="disable_notifications">Desabilitar notificações</string>
+    <string name="disable_notifications">Desativar avisos</string>
     <string name="enable">Habilitar</string>
     <string name="conference_requires_password">Esta conversa em grupo exige uma senha</string>
     <string name="enter_password">Digite a senha</string>
@@ -289,7 +289,7 @@
     <string name="title_pref_quiet_hours_start_time">Início</string>
     <string name="title_pref_quiet_hours_end_time">Fim</string>
     <string name="title_pref_enable_quiet_hours">Habilitar horário de sossego</string>
-    <string name="pref_quiet_hours_summary">As notificações serão silenciadas no horário de sossego</string>
+    <string name="pref_quiet_hours_summary">Avisos serão silenciadas no horário de sossego</string>
     <string name="pref_expert_options_other">Outras</string>
     <string name="pref_autojoin">Sincronizar favoritos</string>
     <string name="pref_autojoin_summary">Define a flag \"autojoin\" ao entrar ou sair de uma sala e reage a modificações feitas por outros clientes.</string>
@@ -351,7 +351,7 @@
     <string name="no_application_found_to_view_contact">Não foi encontrado nenhum aplicativo para visualizar contatos</string>
     <string name="pref_show_dynamic_tags">Rótulos dinâmicos</string>
     <string name="pref_show_dynamic_tags_summary">Exibe rótulos de somente-leitura abaixo dos contatos</string>
-    <string name="enable_notifications">Habilitar notificações</string>
+    <string name="enable_notifications">Ativar avisos</string>
     <string name="no_conference_server_found">Nenhum servidor de conversas em grupo encontrado</string>
     <string name="conference_creation_failed">Não foi possível criar a conversa em grupo</string>
     <string name="account_image_description">Avatar da conta</string>
@@ -427,14 +427,14 @@
     <string name="contact_has_stopped_typing">%s parou de digitar</string>
     <string name="contacts_are_typing">%s estão digitando…</string>
     <string name="contacts_have_stopped_typing">%s pararam de digitar</string>
-    <string name="pref_chat_states">Notificações de digitação</string>
+    <string name="pref_chat_states">Aviso de digitação</string>
     <string name="pref_chat_states_summary">Permita que os seus contatos saibam quando você está escrevendo mensagens para eles</string>
     <string name="send_location">Enviar localização</string>
     <string name="show_location">Exibir localização</string>
     <string name="no_application_found_to_display_location">Não foi encontrado nenhum aplicativo para exibir a localização</string>
     <string name="location">Localização</string>
     <string name="title_undo_swipe_out_group_chat">Sair da conversa em grupo privada</string>
-    <string name="title_undo_swipe_out_channel">Deixar o canal público</string>
+    <string name="title_undo_swipe_out_channel">Saiu do canal público</string>
     <string name="pref_dont_trust_system_cas_title">Não confiar nas CAs do sistema</string>
     <string name="pref_dont_trust_system_cas_summary">Todos os certificados devem ser aprovados manualmente</string>
     <string name="pref_remove_trusted_certificates_title">Remover certificados</string>
@@ -465,7 +465,7 @@
     <string name="download_failed_invalid_file">Falha no download: arquivo inválido</string>
     <string name="account_status_tor_unavailable">Rede Tor não disponível</string>
     <string name="account_status_bind_failure">Falha na associação</string>
-    <string name="account_status_host_unknown">O servidor não responde por esse domínio</string>
+    <string name="account_status_host_unknown">Não é responsável por este domínio</string>
     <string name="server_info_broken">Quebrado</string>
     <string name="pref_presence_settings">Disponibilidade</string>
     <string name="pref_away_when_screen_off">\"Afastado\" quando o dispositivo estiver travado</string>
@@ -518,17 +518,15 @@
 \nNenhum dado da sua lista de contato sai do seu dispositivo!</string>
     <string name="notify_on_all_messages">Notificar em todas as mensagens</string>
     <string name="notify_only_when_highlighted">Notificar somente quando for mencionado</string>
-    <string name="notify_never">Notificações desabilitadas</string>
-    <string name="notify_paused">Notificações pausadas</string>
+    <string name="notify_never">Avisos desativados</string>
+    <string name="notify_paused">Avisos pausados</string>
     <string name="pref_picture_compression">Compressão de imagem</string>
     <string name="pref_picture_compression_summary">Dica: Use \'Selecionar o arquivo\' ao invés de \'Selecionar a imagem\' para enviar uma cópia da imagem original, sem redução de qualidade.</string>
     <string name="always">Sempre</string>
     <string name="large_images_only">Apenas imagens grandes</string>
     <string name="battery_optimizations_enabled">Otimizações de bateria habilitadas</string>
-    <string name="battery_optimizations_enabled_explained">O seu dispositivo está empregando uma otimização intensa de bateria para o %1$s, o que pode levar a atrasos nas notificações ou mesmo perda de mensagens.\nÉ recomendável desabilitar essa otimização.</string>
-    <string name="battery_optimizations_enabled_dialog">O seu dispositivo está empregando uma otimização intensa de bateria para o %1$s, o que pode levar a atrasos nas notificações ou mesmo perda de mensagens.
-\n
-\nVocê será pedido agora para desabilitar essa otimização.</string>
+    <string name="battery_optimizations_enabled_explained">O seu dispositivo está usando uma otimização intensa de bateria para o %1$s, o que pode atrasar avisos ou mesmo perder de mensagens.\nÉ recomendável desativar essa otimização.</string>
+    <string name="battery_optimizations_enabled_dialog">O seu dispositivo está usando uma otimização intensa de bateria para o %1$s, o que pode levar a atrasos nos avisos ou mesmo perda de mensagens.\n\nVocê será pedido agora para desativar essa otimização.</string>
     <string name="disable">Desabilitar</string>
     <string name="selection_too_large">A área selecionada é muito grande</string>
     <string name="no_accounts">(Nenhuma conta ativa)</string>
@@ -598,7 +596,7 @@
     <string name="show_error_message">Exibir a mensagem de erro</string>
     <string name="error_message">Mensagem de erro</string>
     <string name="data_saver_enabled">Economia de dados habilitada</string>
-    <string name="data_saver_enabled_explained">O seu sistema operacional está restringindo o acesso à internet ao %1$s, quando ele está em segundo plano. Para receber notificações de novas mensagens você deve permitir que o %1$s tenha acesso irrestrito quando a economia de dados estiver ativada.\nO %1$s fará um esforço para economizar dados sempre que possível.</string>
+    <string name="data_saver_enabled_explained">O seu sistema operacional está limitando o acesso à internet ao %1$s, quando ele está em segundo plano. Para receber avisos de novas mensagens você deve permitir que o %1$s tenha acesso irrestrito quando a economia de dados estiver ativada.\nO %1$s se um esforçará para economizar dados sempre que possível.</string>
     <string name="device_does_not_support_data_saver">O seu dispositivo não permite que a economia de dados seja desativada para o %1$s.</string>
     <string name="error_unable_to_create_temporary_file">Não foi possível criar o arquivo temporário</string>
     <string name="this_device_has_been_verified">Este dispositivo foi verificado</string>
@@ -608,8 +606,8 @@
     <string name="use_camera_icon_to_scan_barcode">Use a câmera para capturar o código de barras de um contato</string>
     <string name="please_wait_for_keys_to_be_fetched">Por favor, aguarde a obtenção das chaves</string>
     <string name="share_as_barcode">Compartilhar como código de barras</string>
-    <string name="share_as_uri">Compartilhar como uma URI XMPP</string>
-    <string name="share_as_http">Compartilhar como um link HTTP</string>
+    <string name="share_as_uri">Compartilhar como URI XMPP</string>
+    <string name="share_as_http">Compartilhar como link HTTP</string>
     <string name="pref_blind_trust_before_verification">Confiar cegamente antes de verificar</string>
     <string name="pref_blind_trust_before_verification_summary">Confiar em novos dispositivos de contatos não verificados, mas solicitar confirmação manual de novos dispositivos para contatos verificados.</string>
     <string name="blindly_trusted_omemo_keys">Chaves OMEMO confiadas cegamente, o que significa que poderiam ser outras pessoas ou alguém poderia tê-las aproveitado.</string>
@@ -664,7 +662,7 @@
     <string name="not_fetching_history_retention_period">As mensagens não estão sendo obtidas devido ao período de retenção local.</string>
     <string name="transcoding_video">Comprimindo vídeo</string>
     <string name="contact_blocked_past_tense">O contato foi bloqueado.</string>
-    <string name="pref_notifications_from_strangers">Notificações de desconhecidos</string>
+    <string name="pref_notifications_from_strangers">Avisos de desconhecidos</string>
     <string name="pref_notifications_from_strangers_summary">Notificar ao receber mensagens e chamadas de desconhecidos.</string>
     <string name="received_message_from_stranger">Foi recebida uma mensagem de um desconhecido</string>
     <string name="block_stranger">Bloquear os desconhecidos</string>
@@ -676,8 +674,8 @@
     <string name="account_status_regis_web">O servidor exige um registro no site web</string>
     <string name="open_website">Abrir site web</string>
     <string name="application_found_to_open_website">Não foi encontrado nenhum aplicativo para abrir o site web</string>
-    <string name="pref_headsup_notifications">Notificações Heads-up</string>
-    <string name="pref_headsup_notifications_summary">Exibe as notificações Heads-up</string>
+    <string name="pref_headsup_notifications">Avisos emergentes</string>
+    <string name="pref_headsup_notifications_summary">Exibir avisos emergentes</string>
     <string name="today">Hoje</string>
     <string name="yesterday">Ontem</string>
     <string name="pref_validate_hostname">Validar o nome do host com o DNSSEC</string>
@@ -764,10 +762,10 @@
     <string name="ongoing_calls_channel_name">Chamadas em andamento</string>
     <string name="missed_calls_channel_name">Chamadas perdidas</string>
     <string name="silent_messages_channel_name">Silenciar mensagens</string>
-    <string name="silent_messages_channel_description">Essa categoria de notificação é utilizada para exibir notificações que não deveriam gerar nenhum som. Por exemplo, quando estiver ativo em outro dispositivo (Período de Espera).</string>
+    <string name="silent_messages_channel_description">Essa categoria de aviso é utilizada para exibir avisos que não deveriam gerar nenhum som. Por exemplo, quando estiver ativo em outro dispositivo (Período de Espera).</string>
     <string name="delivery_failed_channel_name">Entregas não efetuadas</string>
-    <string name="pref_message_notification_settings">Configurações das notificações de mensagens</string>
-    <string name="pref_incoming_call_notification_settings">configurações das notificações de chamadas recebidas</string>
+    <string name="pref_message_notification_settings">Configurações dos avisos de mensagens</string>
+    <string name="pref_incoming_call_notification_settings">configurações dos avisos de chamadas recebidas</string>
     <string name="pref_more_notification_settings_summary">Importância, Som, Vibrar</string>
     <string name="video_compression_channel_name">Compressão de vídeo</string>
     <string name="view_media">Ver mídia</string>
@@ -1013,7 +1011,7 @@
     <string name="pref_summary_appearance">Tema, Cores, Capturas de tela, Entrada</string>
     <string name="pref_title_security">Segurança</string>
     <string name="pref_summary_security">Criptografia de ponta-a-ponta, confiança cega antes da verificação, detecção de MITM</string>
-    <string name="notifications">Notificações</string>
+    <string name="notifications">Avisos</string>
     <string name="pref_attachments_summary">Tamanho do arquivo, Compressão de imagem, Qualidade de vídeo</string>
     <string name="pref_title_trust_system_ca_store_summary">Confiar nos certificados CA do sistema</string>
     <string name="privacy_policy">Política de privacidade</string>
@@ -1038,7 +1036,7 @@
     <string name="decline">Rejeitar</string>
     <string name="delete_from_server">Remover conta do servidor</string>
     <string name="could_not_delete_account_from_server">Não pôde excluir conta do servidor</string>
-    <string name="unified_push_summary">Transmissor de notificações para apps de terceiros compatíveis com o UnifiedPush</string>
+    <string name="unified_push_summary">Transmissor de aviso para apps de terceiros compatíveis com o UnifiedPush</string>
     <string name="pref_category_e2ee">Criptografia de ponta-a-ponta</string>
     <string name="pref_title_trust_system_ca_store">Autoridades de certificação</string>
     <string name="hide_notification">Esconder notificação</string>
@@ -1088,7 +1086,7 @@
     <string name="pref_category_on_this_device">No dispositivo</string>
     <string name="pref_backup_recurring">Backup recorrente</string>
     <string name="pref_fullscreen_notification">Notificações em tela cheia</string>
-    <string name="pref_fullscreen_notification_summary">Permitir que este app mostre notificações para chamadas recebidas que utilizam a tela inteira quando o dispositivo está bloqueado.</string>
+    <string name="pref_fullscreen_notification_summary">Permitir que este app mostre avisos para chamadas recebidas que utilizam a tela cheia quando o dispositivo está bloqueado.</string>
     <string name="edit_nick">Editar nome de usuário</string>
     <string name="delete_pgp_key">Excluir chave OpenPGP</string>
     <string name="edit_name_and_topic">Editar nome e tópico</string>
@@ -1103,10 +1101,30 @@
     <string name="pref_allow_screenshots_summary">Mostrar o conteúdo do app no alternador de apps e permitir que a tela seja capturada</string>
     <string name="detect_mim">Exigir vínculo do canal</string>
     <string name="detect_mim_summary">A vinculação de canal pode detectar alguns ataques de máquina-no-meio</string>
-    <string name="pref_privacy_summary">Notificações de digitação, Visto por Último, Disponibilidade</string>
+    <string name="pref_privacy_summary">Avisos de digitação, Visto por Último, Disponibilidade</string>
     <string name="pref_connection_summary">Nome do host e porta, Tor</string>
     <string name="pref_connection_summary_w_cd">Nome do host e porta, Tor, Descoberta de Canais</string>
     <string name="pref_keyboard_options">Teclado</string>
-    <string name="pref_category_engagement_notifications">Notificações de engajamento</string>
+    <string name="pref_category_engagement_notifications">Avisos de engajamento</string>
     <string name="pref_category_application">Aplicação</string>
+    <string name="could_not_add_reaction">Não pôde adicionar reação</string>
+    <string name="add_reaction">Adicionar reação…</string>
+    <string name="add_reaction_title">Adicionar reação</string>
+    <string name="more_reactions">Mais reações</string>
+    <string name="could_not_modify_call">Não foi possível modificar a chamada</string>
+    <string name="clients_may_not_support_av">O cliente XMPP do seu contato não suporta chamadas de áudio ou vídeo.</string>
+    <string name="pref_show_avatars">Mostrar avatares</string>
+    <string name="pref_show_avatars_summary">Mostrar avatares para suas mensagens e em chats 1:1, em adição à chats em grupo.</string>
+    <string name="pref_chat_bubbles">Bolhas de conversa</string>
+    <string name="pref_chat_bubbles_summary">Cor do fundo, Tamanho da fonte, Avatares</string>
+    <string name="pref_title_bubbles">Bolhas de Conversa</string>
+    <string name="pref_align_start">Mensagens alinhadas à esquerda</string>
+    <string name="custom_notifications">Notificações customizadas</string>
+    <string name="pref_align_start_summary">Mostrar todas as mensagens, incluindo as enviadas por você, no lado esquerdo, para um layout de conversa uniforme.</string>
+    <string name="custom_notifications_enable">Ativar as configurações de notificações customizadas (importância, som, vibração) para esta conversa?</string>
+    <string name="pref_call_integration">Integração de chamadas</string>
+    <string name="pref_call_integration_summary">Chamadas deste app interagem com as chamadas comuns do sistema, como quando uma chamada é terminada quando outra começa.</string>
+    <string name="show_to_contacts_only">Mostrar somente para contatos</string>
+    <string name="delete_avatar_message">Você gostaria de excluir seu avatar? Alguns clientes podem continuar mostrando uma cópia em cache do seu avatar.</string>
+    <string name="account_status_connection_timeout">Conexão demorou muito</string>
 </resources>
  
  
  
    
    @@ -37,7 +37,7 @@
     <string name="blocked">Bloqueado</string>
     <string name="register_account">Registe uma nova conta no servidor</string>
     <string name="change_password_on_server">Altere a senha no servidor</string>
-    <string name="share_with">Compartilhar com...</string>
+    <string name="share_with">Mostrar a…</string>
     <string name="invite_contact">Convidar contacto</string>
     <string name="contacts">Contactos</string>
     <string name="contact">Contacto</string>
@@ -90,8 +90,8 @@
     <string name="error">Ocorreu um erro</string>
     <string name="recording_error">Erro</string>
     <string name="your_account">A sua conta</string>
-    <string name="send_presence_updates">Enviar atualizações de presença</string>
-    <string name="receive_presence_updates">Receber atualizações de presença</string>
+    <string name="send_presence_updates">Enviar presença</string>
+    <string name="receive_presence_updates">Receber presença</string>
     <string name="ask_for_presence_updates">Pedir atualizações de presença</string>
     <string name="attach_choose_picture">Escolher imagem</string>
     <string name="attach_take_picture">Tirar foto</string>
@@ -400,4 +400,21 @@
     <string name="message_copied_to_clipboard">Mensagem copiada para a área de transferência</string>
     <string name="title_activity_show_location">Exibir localização</string>
     <string name="rtp_state_declined_or_busy">Ocupado</string>
+    <string name="action_account">Gerir conta</string>
+    <string name="action_muc_details">Detalhes do grupo</string>
+    <string name="channel_details">Detalhes do canal</string>
+    <string name="title_activity_share_with">Mostrar a…</string>
+    <string name="action_unblock_participant">Perdoar membro</string>
+    <string name="title_activity_share_via_account">Mostrar via conta</string>
+    <string name="title_activity_new_chat">Nova conversa</string>
+    <string name="action_archive_chat">Arquivar conversa</string>
+    <string name="action_block_participant">Ignorar membro</string>
+    <plurals name="x_unread_conversations">
+        <item quantity="one">%d conversa não lida</item>
+        <item quantity="many">%d muitas conversas não lidas</item>
+        <item quantity="other">%d conversas não lidas</item>
+    </plurals>
+    <string name="remove_bookmark">Gostaria de remover %s como favorito?</string>
+    <string name="remove_bookmark_and_close">Gostaria de remover o favorito de %s e arquivar a conversa?</string>
+    <string name="remove_contact_text">Gostaria de remover %s da sua lista de contatos? A conversa não será removida.</string>
 </resources>
  
  
  
    
    @@ -95,7 +95,7 @@
     <string name="send_unencrypted">Trimite necriptat</string>
     <string name="decryption_failed">Decriptarea a eșuat. Poate nu aveți cheia privată corectă.</string>
     <string name="openkeychain_required">OpenKeychain</string>
-    <string name="openkeychain_required_long">%1$s utilizează <b>OpenKeychain</b> pentru a cripta și decripta mesaje și a administra cheile publice.<br><br>OpenKeychain este licențiat sub GPLv3+ și este disponibil în F-Droid și Google Play.<br><br><small>(Vă rugăm să reporniți %1$s după instalare.)</small></string>
+    <string name="openkeychain_required_long"><![CDATA[%1$s utilizează <b>OpenKeychain</b> pentru a cripta și decripta mesaje și a administra cheile publice.<br><br>OpenKeychain este licențiat sub GPLv3+ și este disponibil în F-Droid și Google Play.<br><br><small>(Vă rugăm să reporniți %1$s după instalare.)</small>]]></string>
     <string name="restart">Repornește</string>
     <string name="install">Instalare</string>
     <string name="openkeychain_not_installed">Va rugăm să instalați OpenKeychain</string>
@@ -463,7 +463,7 @@
     <string name="download_failed_invalid_file">Descărcarea a eșuat: Fișier invalid</string>
     <string name="account_status_tor_unavailable">Rețeaua Tor nu este disponibilă</string>
     <string name="account_status_bind_failure">Eroare de conexiune</string>
-    <string name="account_status_host_unknown">Serverul nu este responsabil pentru acest domeniu</string>
+    <string name="account_status_host_unknown">Nu este responsabil pentru acest domeniu</string>
     <string name="server_info_broken">Deteriorat</string>
     <string name="pref_presence_settings">Setări de disponibilitate</string>
     <string name="pref_away_when_screen_off">Plecat când dispozitivul este blocat</string>
@@ -1108,4 +1108,22 @@
     <string name="server_info_login_mechanism">Mecanism autentificare</string>
     <string name="add_reaction">Adaugă reacție…</string>
     <string name="could_not_add_reaction">Nu s-a putut adăuga o reacție</string>
+    <string name="add_reaction_title">Adaugă reacție</string>
+    <string name="more_reactions">Mai multe reacții</string>
+    <string name="could_not_modify_call">Nu s-a putut modifica apelul</string>
+    <string name="clients_may_not_support_av">Clientul XMPP al contactului dvs. este posibil să nu accepte apeluri audio/video.</string>
+    <string name="pref_chat_bubbles_summary">Culoare de fundal, mărime font, avatare</string>
+    <string name="pref_show_avatars_summary">Afișați avatare pentru mesajele dvs. și în discuțiile 1:1, în plus față de cele de grup.</string>
+    <string name="pref_title_bubbles">Bule de mesaj</string>
+    <string name="pref_chat_bubbles">Bule de mesaj</string>
+    <string name="pref_show_avatars">Arată avatare</string>
+    <string name="custom_notifications_enable">Activați setările de notificare personalizate (importanță, sunet, vibrații) pentru această conversație?</string>
+    <string name="custom_notifications">Notificări personalizate</string>
+    <string name="pref_align_start_summary">Afișați toate mesajele, inclusiv cele trimise, în partea stângă pentru un aspect uniform al discuției.</string>
+    <string name="pref_align_start">Mesaje aliniate la stânga</string>
+    <string name="pref_call_integration_summary">Apelurile din această aplicație interacționează cu apelurile telefonice obișnuite, cum ar fi terminarea unui apel atunci când începe altul.</string>
+    <string name="pref_call_integration">Integrarea apelurilor</string>
+    <string name="delete_avatar_message">Ați dori să vă ștergeți avatarul? Unii clienți ar putea să continue să arate o copie a avatarului.</string>
+    <string name="show_to_contacts_only">Arată doar contactelor</string>
+    <string name="account_status_connection_timeout">Timp limită de conectare expirat</string>
 </resources>
  
  
  
    
    @@ -41,7 +41,7 @@
     <string name="moderator">Модератор</string>
     <string name="participant">Участник</string>
     <string name="visitor">Посетитель</string>
-    <string name="remove_contact_text">Удалить %s из своего списка контактов? Чат с этим контактом удалён не будет.</string>
+    <string name="remove_contact_text">Удалить %s из своего списка контактов? Беседа с этим контактом удалена не будет.</string>
     <string name="block_contact_text">Заблокировать дальнейшие сообщения от %s?</string>
     <string name="unblock_contact_text">Разблокировать пользователя %s?</string>
     <string name="block_domain_text">Заблокировать всех пользователей домена %s?</string>
@@ -50,7 +50,7 @@
     <string name="blocked">Заблокирован</string>
     <string name="register_account">Создать новый аккаунт на сервере</string>
     <string name="change_password_on_server">Изменить пароль на сервере</string>
-    <string name="share_with">Поделиться с…</string>
+    <string name="share_with">Поделиться…</string>
     <string name="invite_contact">Пригласить контакт</string>
     <string name="invite">Пригласить</string>
     <string name="contacts">Контакты</string>
@@ -79,7 +79,7 @@
     <string name="preparing_images">Подготовка к передаче изображений</string>
     <string name="sharing_files_please_wait">Обмен файлами. Подождите…</string>
     <string name="action_clear_history">Очистить историю</string>
-    <string name="clear_conversation_history">Очистить историю чата</string>
+    <string name="clear_conversation_history">Очистить историю</string>
     <string name="clear_histor_msg">Удалить все сообщения в этой беседе? 
 \n 
 \n<b>Внимание:</b> данная операция не повлияет на сообщения, хранящиеся на других устройствах или серверах.</string>
@@ -91,9 +91,9 @@
     <string name="send_unencrypted_message">Отправить сообщение без шифрования</string>
     <string name="send_message">Сообщение</string>
     <string name="send_message_to_x">Сообщение для %s</string>
-    <string name="send_omemo_x509_message">v\\Зашифрованное OMEMO сообщение</string>
+    <string name="send_omemo_x509_message">Зашифрованное vOMEMO сообщение</string>
     <string name="your_nick_has_been_changed">Используется новое имя</string>
-    <string name="send_unencrypted">Отправить чистый текст</string>
+    <string name="send_unencrypted">Отправить в незашифрованном виде</string>
     <string name="decryption_failed">Расшифровка невозможна. Вероятно, у вас нет надлежащего ключа.</string>
     <string name="openkeychain_required">Установите OpenKeychain</string>
     <string name="openkeychain_required_long"><![CDATA[%1$s использует <b>OpenKeychain</b> для шифрования и дешифрования сообщений и управления открытыми ключами.<br><br>OpenKeychain распространяется под лицензией GPLv3+ и доступна для загрузки через F-Droid или Google Play.<br><br><small>(Потребуется перезапуск %1$s после установки.)</small>]]></string>
@@ -124,7 +124,7 @@
     <string name="pref_notification_sound_summary">Звук уведомления о новых сообщениях</string>
     <string name="pref_call_ringtone_summary">Мелодия входящего звонка</string>
     <string name="pref_notification_grace_period">Период отсрочки</string>
-    <string name="pref_notification_grace_period_summary">Время, на которое уведомления будут отключены, когда вы пользуетесь аккаунтом на другом устройстве.</string>
+    <string name="pref_notification_grace_period_summary">Время, на которое уведомления будут отключены, когда вы пользуетесь аккаунтом на другом устройстве</string>
     <string name="pref_advanced_options">Дополнительно</string>
     <string name="pref_never_send_crash_summary">Отправляя отчёты об ошибках, вы помогаете разработке</string>
     <string name="pref_confirm_messages">Отчёты о получении</string>
@@ -179,13 +179,13 @@
     <string name="mgmt_account_disable">Временно отключить</string>
     <string name="mgmt_account_publish_avatar">Разместить аватар</string>
     <string name="mgmt_account_publish_pgp">Анонсировать ключ OpenPGP</string>
-    <string name="unpublish_pgp">Удалить открытый ключ OpenPGP</string>
+    <string name="unpublish_pgp">Удалить публичный ключ OpenPGP</string>
     <string name="unpublish_pgp_message">Удалить ваш публичный ключ OpenPGP из опубликованных?
 \nВаши собеседники не смогут больше отправлять вам зашифрованные OpenPGP сообщения.</string>
     <string name="openpgp_has_been_published">Публичный ключ OpenPGP опубликован.</string>
     <string name="mgmt_account_enable">Включить аккаунт</string>
-    <string name="mgmt_account_delete_confirm_text">Удалить свой аккаунт? Удаление аккаунта также сотрёт все истории диалогов.</string>
-    <string name="attach_record_voice">Запись голоса</string>
+    <string name="mgmt_account_delete_confirm_text">Удалить свой аккаунт? Удаление аккаунта также сотрёт все беседы.</string>
+    <string name="attach_record_voice">Записать голос</string>
     <string name="account_settings_jabber_id">XMPP-адрес</string>
     <string name="block_jabber_id">Заблокировать XMPP-адрес</string>
     <string name="account_settings_example_jabber_id">username@example.com</string>
@@ -206,7 +206,7 @@
     <string name="server_info_push">XEP-0357: push-уведомления</string>
     <string name="server_info_available">доступно</string>
     <string name="server_info_unavailable">недоступно</string>
-    <string name="missing_public_keys">Отсутствие анонсирования открытых ключей</string>
+    <string name="missing_public_keys">Отсутствие анонсирования публичных ключей</string>
     <string name="last_seen_now">Присутствие: только что</string>
     <string name="last_seen_min">Присутствие: минуту назад</string>
     <string name="last_seen_mins">Присутствие: %d мин. назад</string>
@@ -218,9 +218,9 @@
     <string name="openpgp_messages_found">Найдены новые зашифрованные OpenPGP сообщения</string>
     <string name="openpgp_key_id">ID ключа OpenPGP</string>
     <string name="omemo_fingerprint">Отпечаток OMEMO</string>
-    <string name="omemo_fingerprint_x509">v\\OMEMO-отпечаток</string>
-    <string name="omemo_fingerprint_selected_message">OMEMO-отпечаток (выбранного сообщения)</string>
-    <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO-отпечаток (выбранного сообщения)</string>
+    <string name="omemo_fingerprint_x509">Отпечаток vOMEMO</string>
+    <string name="omemo_fingerprint_selected_message">Отпечаток OMEMO (выбранного сообщения)</string>
+    <string name="omemo_fingerprint_x509_selected_message">Отпечаток vOMEMO (выбранного сообщения)</string>
     <string name="other_devices">Другие устройства</string>
     <string name="trust_omemo_fingerprints">Доверенные отпечатки OMEMO</string>
     <string name="fetching_keys">Получение ключей…</string>
@@ -256,17 +256,17 @@
     <string name="leave">Покинуть</string>
     <string name="contact_added_you">Собеседник добавил вас в список контактов</string>
     <string name="add_back">Добавить в ответ</string>
-    <string name="contact_has_read_up_to_this_point">%s прочитал до этого момента</string>
-    <string name="contacts_have_read_up_to_this_point">%s прочитали до этого момента</string>
-    <string name="contacts_and_n_more_have_read_up_to_this_point">%1$s + ещё %2$d прочитали до этого момента</string>
-    <string name="everyone_has_read_up_to_this_point">Все прочитали сообщения до этого момента</string>
+    <string name="contact_has_read_up_to_this_point">%s прочитал(а) досюда</string>
+    <string name="contacts_have_read_up_to_this_point">%s прочитали досюда</string>
+    <string name="contacts_and_n_more_have_read_up_to_this_point">%1$s + ещё %2$d прочитали досюда</string>
+    <string name="everyone_has_read_up_to_this_point">Все прочитали сообщения досюда</string>
     <string name="publish">Опубликовать</string>
-    <string name="touch_to_choose_picture">Нажмите на аватар, чтобы выбрать новую фотографию из галереи</string>
+    <string name="touch_to_choose_picture">Нажмите на аватар, чтобы выбрать изображение из галереи</string>
     <string name="publishing">Установка…</string>
     <string name="error_publish_avatar_server_reject">Сервер отклонил размещение аватара</string>
     <string name="error_publish_avatar_converting">Невозможно преобразовать вашу фотографию</string>
     <string name="error_saving_avatar">Невозможно сохранить аватар</string>
-    <string name="or_long_press_for_default">(Или долгое прикосновение, чтобы вернуть значения по умолчанию)</string>
+    <string name="or_long_press_for_default">(Или долгое нажатие, чтобы вернуть значения по умолчанию)</string>
     <string name="error_publish_avatar_no_server_support">Ваш сервер не поддерживает публикацию аватаров</string>
     <string name="private_message">шёпотом</string>
     <string name="private_message_to">отправить %s</string>
@@ -300,7 +300,7 @@
     <string name="title_pref_enable_quiet_hours">Включить режим \"тихих часов\"</string>
     <string name="pref_quiet_hours_summary">Уведомления будут отключены во время \"тихих часов\"</string>
     <string name="pref_expert_options_other">Другие</string>
-    <string name="toast_message_omemo_fingerprint">OMEMO-отпечаток скопирован в буфер обмена</string>
+    <string name="toast_message_omemo_fingerprint">Отпечаток OMEMO скопирован в буфер обмена</string>
     <string name="conference_banned">Вы заблокированы в этой конференции</string>
     <string name="conference_members_only">Эта конференция — только для участников</string>
     <string name="conference_resource_constraint">Ресурсное ограничение</string>
@@ -351,7 +351,7 @@
     <string name="cancel_transmission">Отменить передачу</string>
     <string name="file_transmission_failed">передача файла не выполнена</string>
     <string name="file_transmission_cancelled">передача файла отменена</string>
-    <string name="file_deleted">Файл был удалён</string>
+    <string name="file_deleted">Файл удалён</string>
     <string name="no_application_found_to_open_file">Не найдено приложения для открытия файла</string>
     <string name="no_application_found_to_open_link">Не найдено приложения, способного открыть эту ссылку</string>
     <string name="no_application_found_to_view_contact">Не найдено приложения для просмотра контакта</string>
@@ -361,7 +361,7 @@
     <string name="no_conference_server_found">Сервер конференции не найден</string>
     <string name="conference_creation_failed">Невозможно создать конференцию</string>
     <string name="account_image_description">Аватар аккаунта</string>
-    <string name="copy_omemo_clipboard_description">Скопировать OMEMO-отпечаток в буфер обмена</string>
+    <string name="copy_omemo_clipboard_description">Копировать отпечаток OMEMO в буфер обмена</string>
     <string name="regenerate_omemo_key">Создать ключ OMEMO заново</string>
     <string name="clear_other_devices">Очистить устройства</string>
     <string name="clear_other_devices_desc">Очистить все остальные устройства из анонса ключей OMEMO? При соединении устройств в следующий раз новые ключи анонсируются автоматически, но устройства могут не получить сообщения, посланные до этого.</string>
@@ -390,13 +390,13 @@
     <string name="remove_membership">Снять права участника</string>
     <string name="grant_admin_privileges">Назначить администратором</string>
     <string name="remove_admin_privileges">Снять административные права</string>
-    <string name="grant_owner_privileges">Назначить администратором</string>
-    <string name="remove_owner_privileges">Снять права администратора</string>
-    <string name="remove_from_room">Убрать из конференции</string>
-    <string name="remove_from_channel">Исключить</string>
+    <string name="grant_owner_privileges">Назначить владельцем</string>
+    <string name="remove_owner_privileges">Снять права владельца</string>
+    <string name="remove_from_room">Исключить из конференции</string>
+    <string name="remove_from_channel">Исключить из канала</string>
     <string name="could_not_change_affiliation">Невозможно изменить принадлежность %s</string>
     <string name="ban_from_conference">Заблокировать в конференции</string>
-    <string name="ban_from_channel">Заблокировать</string>
+    <string name="ban_from_channel">Заблокировать в канале</string>
     <string name="removing_from_public_conference">Вы пытаетесь исключить %s из публичного канала. Единственный способ это сделать — навсегда заблокировать этого пользователя. </string>
     <string name="ban_now">Заблокировать</string>
     <string name="could_not_change_role">Невозможно сменить роль %s</string>
@@ -430,7 +430,7 @@
     <string name="offering_x_file">Предложен %s</string>
     <string name="hide_offline">Скрыть не в сети</string>
     <string name="contact_is_typing">%s печатает…</string>
-    <string name="contact_has_stopped_typing">%s перестал печатать</string>
+    <string name="contact_has_stopped_typing">%s перестал(а) печатать</string>
     <string name="contacts_are_typing">%s печатают…</string>
     <string name="contacts_have_stopped_typing">%s перестали печатать</string>
     <string name="pref_chat_states">Оповещения о наборе</string>
@@ -439,8 +439,8 @@
     <string name="show_location">Показать местоположение</string>
     <string name="no_application_found_to_display_location">Не найдено приложения для отображения местоположения</string>
     <string name="location">Местоположение</string>
-    <string name="title_undo_swipe_out_group_chat">Покинул приватную конференцию</string>
-    <string name="title_undo_swipe_out_channel">Покинул публичный канал</string>
+    <string name="title_undo_swipe_out_group_chat">Покинул(а) приватную конференцию</string>
+    <string name="title_undo_swipe_out_channel">Покинул(а) публичный канал</string>
     <string name="pref_dont_trust_system_cas_title">Не доверять системным УЦ</string>
     <string name="pref_dont_trust_system_cas_summary">Все сертификаты должны быть подтверждены вручную</string>
     <string name="pref_remove_trusted_certificates_title">Удалить сертификаты</string>
@@ -461,7 +461,7 @@
     <string name="choose_quick_action">Выбрать быстрое действие</string>
     <string name="search_contacts">Поиск контактов</string>
     <string name="send_private_message">Отправить личное сообщение</string>
-    <string name="user_has_left_conference">%1$s покинул конференцию</string>
+    <string name="user_has_left_conference">%1$s покинул(а) конференцию</string>
     <string name="username">Имя пользователя</string>
     <string name="username_hint">Имя пользователя</string>
     <string name="invalid_username">Недопустимое имя пользователя</string>
@@ -471,7 +471,7 @@
     <string name="download_failed_could_not_write_file">Загрузка не выполнена: ошибка записи файла</string>
     <string name="account_status_tor_unavailable">Сеть Tor недоступна</string>
     <string name="account_status_bind_failure">Ошибка связывания</string>
-    <string name="account_status_host_unknown">Сервер не ответственен за этот домен</string>
+    <string name="account_status_host_unknown">Не ответственен за домен</string>
     <string name="server_info_broken">Повреждено</string>
     <string name="pref_presence_settings">Доступность</string>
     <string name="pref_away_when_screen_off">\"Отошёл\" при блокировке экрана</string>
@@ -494,7 +494,7 @@
     <string name="certificate_chain_is_not_trusted">Ненадёжная цепь сертификатов</string>
     <string name="jid_does_not_match_certificate">XMPP-адрес не соответствует сертификату</string>
     <string name="action_renew_certificate">Обновить сертификат</string>
-    <string name="error_fetching_omemo_key">Ошибка при получении OMEMO-ключа!</string>
+    <string name="error_fetching_omemo_key">Ошибка при получении ключа OMEMO!</string>
     <string name="verified_omemo_key_with_certificate">Ключ OMEMO проверен с сертификатом!</string>
     <string name="device_does_not_support_certificates">Ваше устройство не поддерживает выбор клиентских сертификатов!</string>
     <string name="pref_connection_options">Подключение</string>
@@ -531,7 +531,7 @@
     <string name="pref_picture_compression_summary">Подсказка: используйте \"Выбрать файл\" вместо \"Выбрать изображение\", чтобы отправлять изображения в несжатом виде, независимо от этой настройки.</string>
     <string name="always">Всегда</string>
     <string name="large_images_only">Только большие изображения</string>
-    <string name="battery_optimizations_enabled">Оптимизации энергопотребления разрешены</string>
+    <string name="battery_optimizations_enabled">Включена оптимизация энергопотребления</string>
     <string name="battery_optimizations_enabled_explained">Ваше устройство использует агрессивную оптимизацию энергопотребления %1$s, что может привести к задержке уведомлений и даже потере сообщений.
 \nРекомендуется её отключить.</string>
     <string name="battery_optimizations_enabled_dialog">Ваше устройство использует агрессивную оптимизацию энергопотребления %1$s, что может привести к задержке уведомлений и даже потере сообщений.
@@ -556,7 +556,7 @@
     <string name="use_own_provider">Использовать своего провайдера</string>
     <string name="pick_your_username">Выберите имя пользователя</string>
     <string name="pref_manually_change_presence">Управлять доступностью вручную</string>
-    <string name="pref_manually_change_presence_summary">Устанавливать свою доступность при редактировании статусного сообщения.</string>
+    <string name="pref_manually_change_presence_summary">Устанавливать свою доступность при редактировании статусного сообщения</string>
     <string name="status_message">Статусное сообщение</string>
     <string name="presence_chat">Свободен для общения</string>
     <string name="presence_online">В сети</string>
@@ -620,7 +620,7 @@
     <string name="share_as_uri">Отправить XMPP URI</string>
     <string name="share_as_http">Отправить HTTP-ссылку</string>
     <string name="pref_blind_trust_before_verification">Слепое доверие до подтверждения</string>
-    <string name="pref_blind_trust_before_verification_summary">Автоматически доверять всем новым устройствам контактов, которые не были подтверждены ранее, но запрашивать ручное подтверждение каждый раз, когда подтверждённый контакт добавляет новое устройство.</string>
+    <string name="pref_blind_trust_before_verification_summary">Автоматически доверять всем новым устройствам контактов, которые не были подтверждены ранее, но запрашивать ручное подтверждение каждый раз, когда подтверждённый контакт добавляет новое устройство</string>
     <string name="blindly_trusted_omemo_keys">Принятие OMEMO-ключей вслепую. Это означает, что собеседник может оказаться недоверенным лицом.</string>
     <string name="not_trusted">Недоверенный</string>
     <string name="invalid_barcode">Некорректный QR-код</string>
@@ -631,7 +631,7 @@
     <string name="i_followed_this_link_from_a_trusted_source">Открывать ссылки из надёжного источника</string>
     <string name="verifying_omemo_keys_trusted_source">Вы подтверждаете OMEMO-ключи %1$s после нажатия на ссылку. Это безопасно только если вы перешли по ссылке из доверенного источника, где только %2$s мог разместить эту ссылку.</string>
     <string name="verify_omemo_keys">Проверить OMEMO-ключи</string>
-    <string name="show_inactive_devices">Показывать неактивные</string>
+    <string name="show_inactive_devices">Показать неактивные</string>
     <string name="hide_inactive_devices">Скрыть неактивные</string>
     <string name="distrust_omemo_key">Прекратить доверять устройству</string>
     <string name="distrust_omemo_key_text">Удалить устройство из доверенных?
@@ -679,7 +679,7 @@
     <string name="transcoding_video">Сжимание видео</string>
     <string name="contact_blocked_past_tense">Контакт заблокирован.</string>
     <string name="pref_notifications_from_strangers">Уведомления от неизвестных контактов</string>
-    <string name="pref_notifications_from_strangers_summary">Уведомлять о сообщениях и звонках от неизвестных контактов.</string>
+    <string name="pref_notifications_from_strangers_summary">Уведомлять о сообщениях и звонках от неизвестных контактов</string>
     <string name="received_message_from_stranger">Получено сообщение от неизвестного контакта</string>
     <string name="block_stranger">Заблокировать неизвестный контакт</string>
     <string name="block_entire_domain">Заблокировать весь домен</string>
@@ -699,14 +699,14 @@
     <string name="certificate_does_not_contain_jid">Сертификат не содержит XMPP-адрес</string>
     <string name="server_info_partial">частичный</string>
     <string name="attach_record_video">Записать видео</string>
-    <string name="copy_to_clipboard">Скопировать в буфер обмена</string>
+    <string name="copy_to_clipboard">Копировать в буфер обмена</string>
     <string name="message_copied_to_clipboard">Сообщение скопировано в буфер обмена</string>
     <string name="message">Сообщение</string>
     <string name="private_messages_are_disabled">Личные сообщения выключены</string>
     <string name="mtm_accept_cert">Принять неизвестный сертификат?</string>
     <string name="mtm_trust_anchor">Этот сертификат сервера не подписан ни одним из известных центров сертификации.</string>
     <string name="mtm_accept_servername">Принять несовпадающее имя сервера?</string>
-    <string name="mtm_hostname_mismatch">Сервер может аутентифицироваться в качестве \"%s\". Сертификат подходит только для:</string>
+    <string name="mtm_hostname_mismatch">Сервер не может аутентифицироваться в качестве \"%s\". Сертификат подходит только для:</string>
     <string name="mtm_connect_anyway">Всё равно подключиться?</string>
     <string name="mtm_cert_details">Детали сертификата:</string>
     <string name="once">Один раз</string>
@@ -716,18 +716,18 @@
     <string name="edit_status_message_title">Изменить статусное сообщение</string>
     <string name="edit_status_message">Изменить статусное сообщение</string>
     <string name="disable_encryption">Отключить шифрование</string>
-    <string name="error_trustkey_general">%1$s может отправить зашифрованные сообщения для %2$s. Причиной этому может быть использование получателем устаревшего сервера или клиента, которые не поддерживают OMEMO.</string>
+    <string name="error_trustkey_general">%1$s не может отправить зашифрованные сообщения для %2$s. Причиной этому может быть использование получателем устаревшего сервера или клиента, которые не поддерживают OMEMO.</string>
     <string name="error_trustkey_device_list">Невозможно получить список устройств</string>
     <string name="error_trustkey_bundle">Невозможно получить ключи шифрования</string>
     <string name="error_trustkey_hint_mutual">Подсказка: в некоторых случаях это может исправлено добавлением друг друга в список контактов.</string>
-    <string name="disable_encryption_message">Выключить OMEMO-шифрование для этого чата?
+    <string name="disable_encryption_message">Выключить OMEMO-шифрование для этой беседы?
 \nЭто позволит администратору сервера читать ваши сообщения, но также это может быть единственным способом связи с людьми, использующими устаревшие клиенты.</string>
     <string name="disable_now">Отключить сейчас</string>
     <string name="draft">Черновик:</string>
     <string name="pref_omemo_setting">OMEMO-шифрование</string>
-    <string name="pref_omemo_setting_summary_always">OMEMO будет всегда использоваться для одиночных бесед и закрытых конференций.</string>
-    <string name="pref_omemo_setting_summary_default_on">OMEMO будет использоваться по умолчанию для новых чатов.</string>
-    <string name="pref_omemo_setting_summary_default_off">OMEMO нужно будет явно включать для новых чатов.</string>
+    <string name="pref_omemo_setting_summary_always">OMEMO будет всегда использоваться для одиночных бесед и приватных конференций</string>
+    <string name="pref_omemo_setting_summary_default_on">OMEMO будет использоваться по умолчанию для новых бесед</string>
+    <string name="pref_omemo_setting_summary_default_off">OMEMO нужно будет явно включать для новых бесед</string>
     <string name="create_shortcut">Создать ярлык</string>
     <string name="default_on">Включено по умолчанию</string>
     <string name="default_off">Выключено по умолчанию</string>
@@ -748,7 +748,7 @@
     <string name="no_microphone_permission">Предоставить %1$s разрешение на использование микрофона</string>
     <string name="search_messages">Поиск сообщений</string>
     <string name="gif">GIF</string>
-    <string name="view_conversation">Посмотреть чат</string>
+    <string name="view_conversation">Посмотреть беседу</string>
     <string name="pref_use_share_location_plugin">Расширение для обмена информацией о местонахождении</string>
     <string name="pref_use_share_location_plugin_summary">Использовать расширение для обмена информацией о местонахождении вместо встроенной карты</string>
     <string name="copy_link">Копировать ссылку</string>
@@ -757,7 +757,7 @@
     <string name="pref_start_search">Быстрый поиск</string>
     <string name="pref_start_search_summary">На экране новой беседы открывать клавиатуру и ставить курсор в поле поиска</string>
     <string name="group_chat_avatar">Аватар конференции</string>
-    <string name="host_does_not_support_group_chat_avatars">Сервер не поддерживает наличие аватар у конференций</string>
+    <string name="host_does_not_support_group_chat_avatars">Сервер не поддерживает наличие аватаров у конференций</string>
     <string name="only_the_owner_can_change_group_chat_avatar">Только владелец может менять аватар конференции</string>
     <string name="contact_name">Имя контакта</string>
     <string name="nickname">Имя</string>
@@ -788,7 +788,7 @@
     <string name="media_browser">Просмотр медиафайлов</string>
     <string name="security_violation_not_attaching_file">Файл не прикреплён из соображений безопасности.</string>
     <string name="pref_video_compression">Качество видео</string>
-    <string name="pref_video_compression_summary">Чем ниже качество, тем меньше объем файлов</string>
+    <string name="pref_video_compression_summary">Чем ниже качество, тем меньше размер файлов</string>
     <string name="video_360p">Среднее (360p)</string>
     <string name="video_720p">Высокое (720р)</string>
     <string name="cancelled">отменено</string>
@@ -847,7 +847,7 @@
     <string name="ebook">Электронная книга</string>
     <string name="video_original">Оригинал (без сжатия)</string>
     <string name="open_with">Открыть с помощью…</string>
-    <string name="set_profile_picture">Фотография профиля Conversations</string>
+    <string name="set_profile_picture">Изображение профиля Conversations</string>
     <string name="choose_account">Выбрать аккаунт</string>
     <string name="restore_backup">Восстановить из резервной копии</string>
     <string name="restore">Восстановить</string>
@@ -859,7 +859,7 @@
     <string name="enter_jabber_id">Введите XMPP-адрес</string>
     <string name="create_group_chat">Создать конференцию</string>
     <string name="join_public_channel">Присоединиться к каналу</string>
-    <string name="create_private_group_chat">Создать закрытую конференцию</string>
+    <string name="create_private_group_chat">Создать приватную конференцию</string>
     <string name="create_public_channel">Создать публичный канал</string>
     <string name="create_dialog_channel_name">Название канала</string>
     <string name="xmpp_address">XMPP-адрес</string>
@@ -883,7 +883,7 @@
     <string name="no_users_hint_group_chat">У этой приватной конференции нет участников.</string>
     <string name="manage_permission">Управление правами</string>
     <string name="search_participants">Поиск участников</string>
-    <string name="file_too_large">Объём файла слишком велик</string>
+    <string name="file_too_large">Размер файла слишком велик</string>
     <string name="attach">Прикрепить</string>
     <string name="discover_channels">Найти каналы</string>
     <string name="search_channels">Поиск каналов</string>
@@ -904,7 +904,7 @@
     <string name="unable_to_perform_this_action">Невозможно совершить это действие</string>
     <string name="open_join_dialog">Присоединиться к публичному каналу…</string>
     <string name="sharing_application_not_grant_permission">Приложение для обмена не предоставило право доступа к этому файлу.</string>
-    <string name="group_chats_and_channels"><![CDATA[Группы и каналы]]></string>
+    <string name="group_chats_and_channels"><![CDATA[Конференции и каналы]]></string>
     <string name="jabber_network">jabber.network</string>
     <string name="local_server">Локальный сервер</string>
     <string name="pref_channel_discovery_summary">Большинству пользователей следует выбрать \"jabber.network\" для получения наиболее подходящих предложений от всей публичной экосистемы XMPP.</string>
@@ -943,12 +943,12 @@
     <string name="only_one_call_at_a_time">Нельзя одновременно совершать больше одного вызова.</string>
     <string name="return_to_ongoing_call">Вернуться к текущему вызову</string>
     <string name="could_not_switch_camera">Невозможно переключить камеру</string>
-    <string name="add_to_favorites">Прикрепить</string>
+    <string name="add_to_favorites">Закрепить</string>
     <string name="remove_from_favorites">Открепить</string>
     <string name="gpx_track">GPX-трек</string>
     <string name="could_not_correct_message">Невозможно исправить сообщение</string>
-    <string name="search_all_conversations">Все чаты</string>
-    <string name="search_this_conversation">Этот чат</string>
+    <string name="search_all_conversations">Все беседы</string>
+    <string name="search_this_conversation">Эта беседа</string>
     <string name="your_avatar">Ваш аватар</string>
     <string name="avatar_for_x">Аватар для %s</string>
     <string name="encrypted_with_omemo">Зашифровано с помощью OMEMO</string>
@@ -988,7 +988,7 @@
         <item quantity="other">%d пропущенных вызовов</item>
     </plurals>
     <string name="account_status_incompatible_client">Несовместимый клиент</string>
-    <string name="group_chats">Групповые беседы</string>
+    <string name="group_chats">Конференции</string>
     <string name="multimedia_file">файл мультимедиа</string>
     <string name="continue_btn">Продолжить</string>
     <string name="pref_up_push_server_title">Сервер уведомлений</string>
@@ -997,7 +997,7 @@
     <string name="rtp_state_reconnecting">Переподключение</string>
     <string name="no_xmpp_adddress_found">Адрес XMPP не найден</string>
     <string name="pref_up_push_account_title">Аккаунт XMPP</string>
-    <string name="conference_technical_problems">Вы покинули данный групповой чат по техническим причинам</string>
+    <string name="conference_technical_problems">Вы покинули эту конференцию по техническим причинам</string>
     <string name="rtp_state_content_add">Добавить дополнительные треки\?</string>
     <string name="reconnecting_call">Переподключение к звонку</string>
     <string name="reconnecting_video_call">Переподключение к видеовызову</string>
@@ -1011,15 +1011,15 @@
     <string name="could_not_delete_account_from_server">Невозможно удалить аккаунт на сервере</string>
     <string name="delete_from_server">Удалить аккаунт на сервере</string>
     <string name="pref_autojoin">Синхронизировать закладки</string>
-    <string name="pref_autojoin_summary">Устанавливать флаг \"автоприсоединение\" при входе и выходе из MUC, и реагировать на изменения от других клиентов.</string>
-    <string name="search_group_chats">Поиск по групповым беседам</string>
-    <string name="download_failed_invalid_file">Загрузка неудачна: Неизвестный файл</string>
+    <string name="pref_autojoin_summary">Устанавливать флаг \"автоприсоединение\" при входе и выходе из MUC, и реагировать на изменения от других клиентов</string>
+    <string name="search_group_chats">Поиск конференций</string>
+    <string name="download_failed_invalid_file">Загрузка не выполнена: файл испорчен</string>
     <string name="rtp_state_content_add_video">Перейти на видеовызов\?</string>
     <string name="outgoing_call_duration_timestamp">Исходящий вызов (%s) · %s</string>
     <string name="incoming_call_duration_timestamp">Входящий вызов (%s) · %s</string>
     <string name="account_registrations_are_not_supported">Регистрации аккаунтов не поддерживаются</string>
     <string name="audio_video_disabled_tor">Звонки отключены при использовании Tor</string>
-    <string name="pref_up_push_account_summary">Аккаунт для получения push-уведомлений.</string>
+    <string name="pref_up_push_account_summary">Аккаунт для получения push-уведомлений</string>
     <string name="no_account_deactivated">Нет (неактивно)</string>
     <string name="verifying_omemo_keys_trusted_source_account">Вы собираетесь проверить ключи OMEMO своего аккаунта. Это безопасно только в том случае, если вы перешли по этой ссылке из надёжного источника, где только вы могли опубликовать эту ссылку.</string>
     <string name="restore_warning_continued">Не пытайтесь восстановить резервные копии, которые не были созданы вами!</string>
@@ -1055,8 +1055,8 @@
     <string name="call_integration_not_available">Интеграция вызовов недоступна!</string>
     <string name="no_permission_to_place_call">Нет разрешения на телефонный звонок</string>
     <string name="remove_bookmark">Удалить закладку для \"%s\"?</string>
-    <string name="delete_and_close">Архивировать и удалить чат</string>
-    <string name="remove_bookmark_and_close">Удалить закладку для %s и архивировать чат?</string>
+    <string name="delete_and_close">Архивировать и удалить беседу</string>
+    <string name="remove_bookmark_and_close">Удалить закладку для %s и архивировать беседу?</string>
     <string name="pref_send_crash_reports">Отправлять отчёты о сбоях</string>
     <string name="switch_to_chat">Перейти к беседе</string>
     <string name="pref_summary_appearance">Тема, цвета, снимки, ввод</string>
@@ -1065,9 +1065,9 @@
     <string name="pref_title_security">Безопасность</string>
     <string name="pref_allow_screenshots">Разрешить снимки экрана</string>
     <string name="pref_keyboard_options">Клавиатура</string>
-    <string name="title_activity_share_with">Поделиться с…</string>
-    <string name="pref_use_colorful_bubbles">Цветные пузырьки сообщений</string>
-    <string name="pref_use_colorful_bubbles_summary">Различайте отправленные сообщения и фон беседы</string>
+    <string name="title_activity_share_with">Поделиться…</string>
+    <string name="pref_use_colorful_bubbles">Цветные пузыри сообщений</string>
+    <string name="pref_use_colorful_bubbles_summary">Различные цвета фона для отправленных и полученных сообщений</string>
     <string name="pref_dynamic_colors">Динамичные цвета</string>
     <string name="pref_dynamic_colors_summary">Цвет системы (Material You)</string>
     <string name="channel_discover_opt_in_message">Обзор каналов использует сторонний сервис <a href=https://search.jabber.network>search.jabber.network</a>.<br><br>Эта функция передаст ваш IP-адрес и поисковый запрос этому сервису. Ознакомьтесь с его <a href=https://search.jabber.network/privacy>политикой конфиденциальности</a> для получения подробностей.</string>
@@ -1085,7 +1085,7 @@
     <string name="pref_automatic_download">Автоматическое скачивание</string>
     <string name="appearance">Внешний вид</string>
     <string name="pref_title_interface">Интерфейс</string>
-    <string name="pref_light_dark_mode">Светлый/тёмный режим</string>
+    <string name="pref_light_dark_mode">Светлая/тёмная тема</string>
     <string name="pref_allow_screenshots_summary">Отображать содержимое экрана приложения в переключателе и разрешить делать снимки экрана</string>
     <string name="pref_category_e2ee">Сквозное шифрование</string>
     <string name="pref_title_trust_system_ca_store">Удостоверяющие центры</string>
@@ -1103,11 +1103,11 @@
     <string name="pref_title_trust_system_ca_store_summary">Доверять сертификатам системных УЦ</string>
     <string name="pref_connection_summary_w_cd">Имя хоста и порт, Tor, обзор каналов</string>
     <string name="allow_private_messages">Разрешить личные сообщения</string>
-    <string name="pref_accept_invites_from_strangers_summary">Принимать приглашения в групповые беседы от неизвестных</string>
+    <string name="pref_accept_invites_from_strangers_summary">Принимать приглашения в конференции от неизвестных</string>
     <string name="unsupported_operation">Операция не поддерживается</string>
     <string name="pref_fullscreen_notification">Полноэкранные уведомления</string>
-    <string name="pref_backup_recurring">Повторяющиеся резервные копии</string>
-    <string name="pref_fullscreen_notification_summary">Разрешить приложению показывать экраны входящих звонков при заблокированном экране.</string>
+    <string name="pref_backup_recurring">Регулярные резервные копии</string>
+    <string name="pref_fullscreen_notification_summary">Разрешить приложению показывать экраны входящих звонков при заблокированном экране</string>
     <string name="pref_up_long_summary">Conversations, будучи распределителем UnifiedPush, будет использовать стойкое, надёжное и малопотребляющее подключение XMPP для пробуждения других совместимых с UnifiedPush приложений, таких как Tusky, Ltt.rs, FluffyChat и др.</string>
     <string name="detect_mim">Требовать привязку канала</string>
     <string name="detect_mim_summary">Привязка канала может обнаружить некоторые атаки посредником</string>
@@ -1133,7 +1133,27 @@
     <string name="edit_configuration">Изменить настройки</string>
     <string name="edit_nick">Изменить имя</string>
     <string name="edit_name_and_topic">Изменить имя и тему</string>
-    <string name="pref_backup_summary">Создание разовых и повторяющихся резервных копий</string>
+    <string name="pref_backup_summary">Создание разовых и регулярных резервных копий</string>
     <string name="server_info_sasl2">XEP-0388: расширяемый профиль SASL</string>
     <string name="server_info_bind2">XEP-0386: привязка 2</string>
+    <string name="could_not_add_reaction">Невозможно добавить реакцию</string>
+    <string name="add_reaction">Добавить реакцию…</string>
+    <string name="add_reaction_title">Добавить реакцию</string>
+    <string name="more_reactions">Другие реакции</string>
+    <string name="could_not_modify_call">Невозможно изменить звонок</string>
+    <string name="clients_may_not_support_av">XMPP-клиент вашего контакта может не поддерживать аудио- и видеозвонки.</string>
+    <string name="pref_chat_bubbles_summary">Цвет фона, размер текста, аватары</string>
+    <string name="pref_show_avatars">Показывать аватары</string>
+    <string name="pref_title_bubbles">Сообщения в пузырях</string>
+    <string name="pref_chat_bubbles">Пузыри сообщений</string>
+    <string name="pref_show_avatars_summary">Показывать аватары в своих сообщениях и приватных беседах в дополнение к конференциям</string>
+    <string name="pref_call_integration">Интеграция звонков</string>
+    <string name="pref_align_start">Выравнивать сообщения слева</string>
+    <string name="pref_align_start_summary">Показывать все сообщения, включая отправленные, выровненными по левому краю для единообразия вида беседы</string>
+    <string name="custom_notifications">Настраиваемые уведомления</string>
+    <string name="pref_call_integration_summary">Звонки из этого приложения взаимодействуют с обычными телефонными звонками, например, завершение одного звонка при начале другого</string>
+    <string name="custom_notifications_enable">Использовать индивидуальные настройки уведомлений (важность, звук, вибрация) для этой беседы?</string>
+    <string name="delete_avatar_message">Хотите удалить свой аватар? Некоторые клиенты могут продолжать отображать кэшированную копию вашего аватара.</string>
+    <string name="show_to_contacts_only">Показывать только контактам</string>
+    <string name="account_status_connection_timeout">Истекло время ожидания подключения</string>
 </resources>
  
  
  
    
    @@ -208,7 +208,7 @@
     <string name="skip">Anashkaloje</string>
     <string name="disable_notifications">Çaktivizo njoftimet</string>
     <string name="enable">Aktivizoje</string>
-    <string name="conference_requires_password">Fjalosja në grupi lyp fjalëkalim</string>
+    <string name="conference_requires_password">Fjalosja në grup lyp fjalëkalim</string>
     <string name="enter_password">Jepni fjalëkalim</string>
     <string name="request_now">Kërkoje tani</string>
     <string name="ignore">Shpërfille</string>
@@ -368,7 +368,7 @@
     <string name="download_failed_invalid_file">Shkarkimi dështoi: Kartelë e pavlefshme</string>
     <string name="account_status_tor_unavailable">Rrjet Tor jo në punë</string>
     <string name="account_status_bind_failure">Dështim lidhjeje</string>
-    <string name="account_status_host_unknown">Shërbyesi s’është përgjegjës për këtë përkatësi</string>
+    <string name="account_status_host_unknown">S’është përgjegjës për përkatësinë</string>
     <string name="server_info_broken">I dëmtuar</string>
     <string name="pref_away_when_screen_off">“I larguar”, kur pajisja është e kyçur</string>
     <string name="pref_away_when_screen_off_summary">Shfaqmë si “I lraguar”, kur pajisja është e kyçur</string>
@@ -674,7 +674,7 @@
     <string name="account_already_setup">Kjo llogari është ujdisur tashmë</string>
     <string name="please_enter_password">Ju lutemi, jepni fjalëkalimin për këtë llogari</string>
     <string name="unable_to_perform_this_action">S’u krye dot ky veprim</string>
-    <string name="group_chats_and_channels">Fjalosje në Grup & Kanale</string>
+    <string name="group_chats_and_channels"><![CDATA[Fjalosje në Grup & Kanale]]></string>
     <string name="local_server">Shërbyes vendor</string>
     <string name="pref_channel_discovery">Metodë zbulimi kanalesh</string>
     <string name="backup">Kopjeruajtje</string>
@@ -829,7 +829,7 @@
 \n<b>Kujdes:</b> Kjo s’do të fshijë kopje të kësaj kartele që janë depozituar në pajisje apo shërbyes të tjerë. </string>
     <string name="decryption_failed">Shfshehtëzimi dështoi. Ndoshta s’keni kyçin e duhur privat.</string>
     <string name="openkeychain_required">OpenKeychain</string>
-    <string name="openkeychain_required_long">%1$s përdor <b>OpenKeychain</b> që të fshehtëzojë dhe shfshehtëzojë mesazhe dhe të administrojë kyçet tuaj publikë.<br><br>Licensohet sipas kushteve të GPLv3+ dhe mund të merret në F-Droid dhe Google Play.<br><br><small>(Ju lutemi, riniseni %1$s më pas.)</small></string>
+    <string name="openkeychain_required_long"><![CDATA[%1$s përdor <b>OpenKeychain</b> që të fshehtëzojë dhe shfshehtëzojë mesazhe dhe të administrojë kyçet tuaj publikë.<br><br>Licensohet sipas kushteve të GPLv3+ dhe mund të merret në F-Droid dhe Google Play.<br><br><small>(Ju lutemi, riniseni %1$s më pas.)</small>]]></string>
     <string name="contact_has_no_pgp_key">Mesazhi juaj s’u fshehtëzua dot, ngaqë kontakti juaj s’po deklaron kyçin e vet publik.
 \n
 \n<small>Ju lutemi, kërkojini kontaktit tuaj të ujdisë OpenPGP-në.</small></string>
@@ -950,8 +950,8 @@
     <string name="please_enter_pin_below">Ju lutemi, jepni më poshtë PIN-in tuaj prej 6 shifrash.</string>
     <string name="please_enter_pin">Ju lutemi, jepni PIN-in tuaj prej 6 shifrash.</string>
     <string name="security_violation_not_attaching_file">Kartelë e shpërfillur, për shkak cenimi sigurie.</string>
-    <string name="we_will_be_verifying">Do të verifikojmë numrin e telefonit<br/><br/><b>%s</b><br/><br/>Dakord, apo do të donit të përpunonit numrin\?</string>
-    <string name="we_have_sent_you_an_sms_to_x">Ju kemi dërguar një SMS te <b>%s</b>.</string>
+    <string name="we_will_be_verifying"><![CDATA[Do të verifikojmë numrin e telefonit<br/><br/><b>%s</b><br/><br/>Dakord, apo do të donit të përpunonit numrin??]]></string>
+    <string name="we_have_sent_you_an_sms_to_x"><![CDATA[Ju kemi dërguar një SMS te <b>%s</b>.]]></string>
     <string name="enter_password_to_restore">Që të rikthehet kopjeruajtja, jepni fjalëkalimin tuaj për llogarinë %s.</string>
     <string name="restore_warning">Mos përdorni veçorinë e rikthimit të një kopjeruajtje në një përpjekje për të klonuar (xhiruar në të njëjtën kohë) një instalim. Rikthimi i një kopjeruajtje është menduar vetëm për migrime, ose në rast se humbët pajisjen origjinale.</string>
     <string name="no_users_hint_channel">Ky kanal publik s’ka pjesëmarrës. Ftoni kontaktet tuaj, ose përdorni butonin e ndarjes me të tjerët për të dhënë adresën XMPP të tij.</string>
@@ -1102,4 +1102,22 @@
     <string name="server_info_login_mechanism">Mekanizëm hyrjesh</string>
     <string name="could_not_add_reaction">S’u shtua dot reagim</string>
     <string name="add_reaction">Shtoni reagim…</string>
+    <string name="add_reaction_title">Shtoni reagim</string>
+    <string name="more_reactions">Më tepër reagime</string>
+    <string name="could_not_modify_call">S’u ndryshua dot thirrja</string>
+    <string name="clients_may_not_support_av">Klienti XMPP i kontaktit tuaj mund të mos mbulojë thirrje me audio/video.</string>
+    <string name="pref_show_avatars">Shfaq avatarë</string>
+    <string name="pref_show_avatars_summary">Shfaq avatarë për mesazhet tuaj të rinj dhe fjalosjet 1:1, përtej fjalosjeve grupi.</string>
+    <string name="pref_chat_bubbles">Flluska fjalosjesh</string>
+    <string name="pref_chat_bubbles_summary">Ngjyrë sfondi, Madhësi shkronjash, avatarë</string>
+    <string name="pref_title_bubbles">Flluska Fjalosjesh</string>
+    <string name="pref_align_start">Mesazhe të drejtuar majtas</string>
+    <string name="pref_align_start_summary">Shfaqi krejt mesazhet, përfshi ata të dërguarit, në anën e majtë, për një skemë të njëtrajtshme të fjalosjes.</string>
+    <string name="custom_notifications">Njoftime vetjake</string>
+    <string name="pref_call_integration">Integrim thirrjesh</string>
+    <string name="custom_notifications_enable">Të aktivizohen rregullime njoftimesh vetjake (rëndësi, tingull, dridhje) për këtë bisedë?</string>
+    <string name="pref_call_integration_summary">Thirrjet prej këtij aplikacioni ndërveprojnë me thirrjet e zakonshme telefonike, fjala vjen, përfundim i një thirrjeje, kur niset një tjetër.</string>
+    <string name="show_to_contacts_only">Shfaqja vetëm kontakteve</string>
+    <string name="delete_avatar_message">Doni të fshihet avatari jua? Disa klientë mund të vazhdojnë të shfaqin një kopje të ruajtur në fshehtinat e tyre të avatarit tuaj.</string>
+    <string name="account_status_connection_timeout">Mbarim kohe për lidhjen</string>
 </resources>
  
  
  
    
    @@ -461,7 +461,7 @@
     <string name="download_failed_could_not_write_file">Преузимање није успело: није могуће уписивање фајла</string>
     <string name="account_status_tor_unavailable">Тор мрежа недоступна</string>
     <string name="account_status_bind_failure">Неуспех везивања</string>
-    <string name="account_status_host_unknown">Сервер није одговоран за овај домен</string>
+    <string name="account_status_host_unknown">Није одговоран за домен</string>
     <string name="server_info_broken">Оштећен</string>
     <string name="pref_presence_settings">Присутност</string>
     <string name="pref_away_when_screen_off">Одсутан/на кад је уређај закључан</string>
@@ -603,7 +603,7 @@
     <plurals name="seconds">
         <item quantity="one">%d секунда</item>
         <item quantity="few">%d секунде</item>
-        <item quantity="other">%d скунди</item>
+        <item quantity="other">%d секунди</item>
     </plurals>
     <plurals name="minutes">
         <item quantity="one">%d минута</item>
@@ -726,7 +726,7 @@
     <string name="create_public_channel">Направи јавни канал</string>
     <string name="allow_participants_to_edit_subject">Дозволи било коме да измени тему</string>
     <string name="share_backup_files">Подели резервне копије</string>
-    <string name="group_chats_and_channels">Групне Преписке и Канали</string>
+    <string name="group_chats_and_channels"><![CDATA[Групне Преписке и Канали]]></string>
     <string name="rtp_state_retracted">Опозван позив</string>
     <string name="reconnecting_call">Поновно успостављање позива</string>
     <string name="reconnecting_video_call">Поновно успостављање видео позива</string>
@@ -822,7 +822,7 @@
 \nСада ћеш добити упит да је искључиш.</string>
     <string name="magic_create_text">Припремљено је упутство за отварање налога на conversations.im.
 \nАко одабереш conversations.im као провајдера можеш да комуницираш са корисницима других провајдера тако што им проследиш своју пуну XMPP адресу.</string>
-    <string name="pref_use_colorful_bubbles">Разнобојни балончићи преписке</string>
+    <string name="pref_use_colorful_bubbles">Разнобојни балончићи порука</string>
     <string name="no_permission_to_place_call">Недостатак пермисија за обављање позива</string>
     <string name="pref_validate_hostname">Валидирај назив сервера користећи DNSSEC</string>
     <string name="foreground_service_channel_description">Ова категорија обавештења се користи за трајни приказ обавештења да %1$s ради.</string>
@@ -879,7 +879,7 @@
     <string name="sharing_application_not_grant_permission">Апликација за дељење није дозволила пермисију за приступ овом фајлу.</string>
     <string name="incoming_call_duration_timestamp">Долазни позив (%s) · %s</string>
     <string name="send_encrypted_message">Пошаљи шифровану поруку</string>
-    <string name="openkeychain_required_long">%1$s користи <b>OpenKeychain</b> за шифровање и дешифровање порука и управљање твојим јавним кључевима.<br><br>Лиценциран је под GPLv3+ и доступан је на F-Droid и Google Play продавницама.<br><br><small>(После поново покрени %1$s.)</small></string>
+    <string name="openkeychain_required_long"><![CDATA[%1$s користи <b>OpenKeychain</b> за шифровање и дешифровање порука и управљање твојим јавним кључевима.<br><br>Лиценциран је под GPLv3+ и доступан је на F-Droid и Google Play продавницама.<br><br><small>(После поново покрени %1$s.)</small>]]></string>
     <string name="pref_notification_grace_period_summary">Временски период током којег су обавештења утишана након примећене активности на једном од твојих других уређаја.</string>
     <string name="pref_prevent_screenshots">Забрани скриншотове</string>
     <string name="pref_prevent_screenshots_summary">Сакриј садржај апликације у прегледу недавних апликација и блокирај скриншотове</string>
@@ -988,10 +988,10 @@
     <string name="feature_not_implemented">Функција није имплементирана</string>
     <string name="choose_a_country">Одабери земљу</string>
     <string name="verify_your_phone_number">Овери свој број телефона</string>
-    <string name="we_will_be_verifying">Оверићемо број телефона<br/><br/><b>%s</b><br/><br/>Да ли је ово у реду, или би хтео/ла да измениш број?</string>
+    <string name="we_will_be_verifying"><![CDATA[Оверићемо број телефона<br/><br/><b>%s</b><br/><br/>Да ли је ово у реду, или би хтео/ла да измениш број?]]></string>
     <string name="not_a_valid_phone_number">%s није важећи број телефона.</string>
     <string name="please_enter_your_phone_number">Унеси свој број телефона.</string>
-    <string name="we_have_sent_you_an_sms_to_x">Послали смо ти SMS на <b>%s</b>.</string>
+    <string name="we_have_sent_you_an_sms_to_x"><![CDATA[Послали смо ти SMS на <b>%s</b>.]]></string>
     <string name="please_enter_pin_below">Унеси 6-оцифрени PIN испод.</string>
     <string name="resend_sms">Поново пошаљи SMS</string>
     <string name="resend_sms_in">Поново пошаљи SMS (%s)</string>
@@ -1123,4 +1123,24 @@
     <string name="rtp_state_security_error">Проблем са овером</string>
     <string name="mtm_accept_cert">Прихвати непознат сертификат?</string>
     <string name="pref_dynamic_colors_summary">Системске боје (Material You)</string>
+    <string name="could_not_add_reaction">Није могуће ставити реакцију</string>
+    <string name="add_reaction">Стави реакцију…</string>
+    <string name="add_reaction_title">Стави реакцију</string>
+    <string name="more_reactions">Још реакција</string>
+    <string name="could_not_modify_call">Није могуће изменити позив</string>
+    <string name="clients_may_not_support_av">XMPP клијент вашег контакта можда не подржава аудио/видео позиве.</string>
+    <string name="pref_show_avatars">Прикажи аватаре</string>
+    <string name="pref_show_avatars_summary">Приказуј аватаре за поруке и у препискама 1-на-1, као и у групним препискама.</string>
+    <string name="pref_chat_bubbles">Балончићи у препискама</string>
+    <string name="pref_chat_bubbles_summary">Боја позадине, Величина слова, Аватари</string>
+    <string name="pref_title_bubbles">Балончићи у препискама</string>
+    <string name="pref_call_integration">Интеграција позива</string>
+    <string name="pref_call_integration_summary">Позиви из ове апликације реагују на обичне телефонске позиве, попут прекидања једног позива када почне други.</string>
+    <string name="pref_align_start">Поруке с леве стране</string>
+    <string name="pref_align_start_summary">Приказуј све поруке, укључујући послате, са леве стране за обједињени изглед преписке.</string>
+    <string name="custom_notifications">Посебна обавештења</string>
+    <string name="custom_notifications_enable">Укључи посебна подешавања обавештења (важност, звук, вибрација) за ову преписку?</string>
+    <string name="delete_avatar_message">Желиш да обришеш свој аватар? Неки клијенти ће можда наставити да приказују кеширану копију твог аватара.</string>
+    <string name="show_to_contacts_only">Приказуј само контактима</string>
+    <string name="account_status_connection_timeout">Истекла веза</string>
 </resources>
  
  
  
    
    @@ -941,7 +941,7 @@
     <string name="resend_sms">Skicka SMS igen</string>
     <string name="resend_sms_in">Skicka SMS igen (%s)</string>
     <string name="wait_x">Var god vänta (%s)</string>
-    <string name="we_will_be_verifying">Vi kommer att verifiera telefonnumret<br/><br/><b>%s</b><br/><br/>Är det här okej, eller vill du ändra telefonnumret\?</string>
+    <string name="we_will_be_verifying">Vi kommer att verifiera telefonnumret<br/><br/><b>%s</b><br/><br/>Är det här okej, eller vill du ändra telefonnumret?</string>
     <string name="not_a_valid_phone_number">%s är inte ett giltigt telefonnummer.</string>
     <string name="security_violation_not_attaching_file">Filen har utelämnats på grund av säkerhetsöverträdelse.</string>
     <string name="pref_video_compression_summary">Lägre kvalitet resulterar i mindre filer</string>
  
  
  
    
    @@ -795,7 +795,7 @@
     <string name="phone_number">numer telefōnu</string>
     <string name="verify_your_phone_number">Zweryfikuj swōj numer telefōnu</string>
     <string name="enter_country_code_and_phone_number">Quicksy wyśle SMS (mogōm być naliczane płaty) coby zweryfikować numer telefōnu. Wpisz kod ôd kraju i numer telefōnu:</string>
-    <string name="we_will_be_verifying">Zweryfikujymy numer telefōnu <br/><br/><b>%s</b><br/><br/>Zgodzo sie wszysko, abo chcesz zmiynić numer\?</string>
+    <string name="we_will_be_verifying">Zweryfikujymy numer telefōnu <br/><br/><b>%s</b><br/><br/>Zgodzo sie wszysko, abo chcesz zmiynić numer?</string>
     <string name="not_a_valid_phone_number">%s to niy ma noleżny numer telefōnu.</string>
     <string name="please_enter_your_phone_number">Wkludź swōj numer telefōnu.</string>
     <string name="search_countries">Przeszukej kraje</string>
  
  
  
    
    @@ -1063,4 +1063,9 @@
     <string name="pref_summary_security">Uçtan Uca Şifreleme, Doğrulamadan Körü Körüne Güven, MITM Algılama</string>
     <string name="pref_category_engagement_notifications">Etkileşim bildirimleri</string>
     <string name="pref_up_long_summary">UnifiedPush Dağıtıcısı olarak davranılırken devamlı, güvenli ve pil ömrü dostu olan XMPP bağlantısı diğer UnifiedPush ile uyumlu olan Tusky, Ltt.rs, FluffyChat ve benzeri uygulamaları uyandırmak için kullanılacaktır.</string>
+    <string name="reconnect_on_other_host">Diğer sunucuda yeniden bağlan</string>
+    <string name="allow_private_messages">Özel mesajlara izin ver</string>
+    <string name="pref_accept_invites_from_strangers">Yabancılardan gelen davetler</string>
+    <string name="pref_accept_invites_from_strangers_summary">Yabancılardan gelen grup davetlerini kabul et</string>
+    <string name="edit_configuration">Yapılandırmayı değiştir</string>
 </resources>
  
  
  
    
    @@ -27,7 +27,7 @@
     <string name="minutes_ago">%d хвилин тому</string>
     <string name="sending">надсилання…</string>
     <string name="message_decrypting">Повідомлення розшифровується. Зачекайте, будь ласка…</string>
-    <string name="pgp_message">Повідомлення зашифроване OpenPGP</string>
+    <string name="pgp_message">Повідомлення, зашифроване OpenPGP</string>
     <string name="nick_in_use">Таке прізвисько вже використовується</string>
     <string name="invalid_muc_nick">Неприйнятне прізвисько</string>
     <string name="admin">Адміністратор</string>
@@ -84,7 +84,7 @@
     <string name="send_message">Надіслати повідомлення</string>
     <string name="send_message_to_x">Повідомлення %s</string>
     <string name="send_omemo_x509_message">Повідомлення, зашифроване v\\OMEMO</string>
-    <string name="your_nick_has_been_changed">Ваше прізвисько змінено</string>
+    <string name="your_nick_has_been_changed">Використовується нове прізвисько</string>
     <string name="send_unencrypted">Надіслати без шифрування</string>
     <string name="decryption_failed">Не вдалося розшифрувати. Можливо, у Вас відсутній потрібний приватний ключ.</string>
     <string name="openkeychain_required">OpenKeychain</string>
@@ -129,10 +129,10 @@
     <string name="send_presence_updates">Повідомляти про присутність</string>
     <string name="receive_presence_updates">Отримувати оновлення присутності</string>
     <string name="ask_for_presence_updates">Надіслати запит на оновлення присутності</string>
-    <string name="attach_choose_picture">Обрати зображення</string>
+    <string name="attach_choose_picture">Вибрати зображення</string>
     <string name="attach_take_picture">Зняти світлину</string>
     <string name="preemptively_grant">Попередньо давати запит на підписку</string>
-    <string name="error_not_an_image_file">Обраний файл не є зображенням</string>
+    <string name="error_not_an_image_file">Вибраний файл не є зображенням</string>
     <string name="error_compressing_image">Не вдалося конвертувати зображення</string>
     <string name="error_file_not_found">Файл не знайдено</string>
     <string name="error_io_exception">Загальна помилка вводу-виводу. Можливо, на пристрої закінчилася вільна пам\'ять\?</string>
@@ -200,8 +200,8 @@
     <string name="last_seen_hours">у мережі %d годин тому</string>
     <string name="last_seen_day">у мережі 1 день тому</string>
     <string name="last_seen_days">у мережі %d днів тому</string>
-    <string name="install_openkeychain">Зашифроване повідомлення. Будь ласка, встановіть OpenKeychain, щоб розшифрувати.</string>
-    <string name="openpgp_messages_found">Знайдено повідомлення, зашифроване OpenPGP</string>
+    <string name="install_openkeychain">Повідомлення зашифровано. Щоб його розшифрувати, будь ласка, встановіть OpenKeychain.</string>
+    <string name="openpgp_messages_found">Знайдено нові повідомлення, зашифровані OpenPGP</string>
     <string name="openpgp_key_id">Ідентифікатор ключа OpenPGP</string>
     <string name="omemo_fingerprint">Цифровий підпис OMEMO</string>
     <string name="omemo_fingerprint_x509">Цифровий підпис v\\OMEMO</string>
@@ -242,7 +242,7 @@
     <string name="add_back">Також додати</string>
     <string name="contact_has_read_up_to_this_point">%s дочитав(ла) до цього місця</string>
     <string name="contacts_have_read_up_to_this_point">%s дочитали до цього місця</string>
-    <string name="contacts_and_n_more_have_read_up_to_this_point">%1$s +%2$d дочитали до цього місця</string>
+    <string name="contacts_and_n_more_have_read_up_to_this_point">%1$s і %2$d дочитали до цього місця</string>
     <string name="everyone_has_read_up_to_this_point">Усі прочитали до цього місця</string>
     <string name="publish">Опублікувати</string>
     <string name="touch_to_choose_picture">Торкніться аватара, щоб вибрати зображення з галереї</string>
@@ -258,23 +258,23 @@
     <string name="connect">Підключення</string>
     <string name="account_already_exists">Цей обліковий запис уже існує</string>
     <string name="next">Далі</string>
-    <string name="server_info_session_established">Поточну сесію встановлено</string>
+    <string name="server_info_session_established">Сесію встановлено</string>
     <string name="skip">Пропустити</string>
     <string name="disable_notifications">Вимкнути сповіщення</string>
     <string name="enable">Увімкнути</string>
-    <string name="conference_requires_password">Група вимагає пароль</string>
-    <string name="enter_password">Зазначте пароль</string>
-    <string name="request_presence_updates">Будь ласка, спершу надішліть запит на оновлення присутності від Вашого контакту.
-\n
-\n<small>Оновлення буде використано, щоб визначити, яку програму-клієнт (які програми-клієнти) він використовує</small>.</string>
+    <string name="conference_requires_password">Групу захищено паролем</string>
+    <string name="enter_password">Уведіть пароль</string>
+    <string name="request_presence_updates">Будь ласка, спершу надішліть запит на оновлення присутності від Вашого контакту. 
+\n 
+\n<small>Це потрібно, щоб визначити, яку програму-клієнт він використовує</small>.</string>
     <string name="request_now">Надіслати запит зараз</string>
     <string name="ignore">Ігнорувати</string>
     <string name="without_mutual_presence_updates"><b>Попередження:</b> Надсилання без взаємної згоди на оновлення присутності може спричинити неочікувані проблеми.
 \n
 \n<small>Перегляньте деталі контакту та перевірте отримання стану присутності.</small></string>
     <string name="pref_security_settings">Безпека</string>
-    <string name="pref_allow_message_correction">Редагування повідомлень</string>
-    <string name="pref_allow_message_correction_summary">Дозволити контактам редагувати свої повідомлення після надсилання</string>
+    <string name="pref_allow_message_correction">Виправлення повідомлень</string>
+    <string name="pref_allow_message_correction_summary">Дозволити контактам виправляти свої повідомлення після надсилання</string>
     <string name="pref_expert_options">Експертні налаштування</string>
     <string name="pref_expert_options_summary">Будь ласка, будьте обережними з цим</string>
     <string name="title_activity_about_x">Про %s</string>
@@ -288,12 +288,12 @@
     <string name="conference_banned">Вам заборонили доступ до цієї групи</string>
     <string name="conference_members_only">Ця група лише для учасників</string>
     <string name="conference_resource_constraint">Обмеження ресурсів</string>
-    <string name="conference_kicked">Вас виключили з цієї групи</string>
+    <string name="conference_kicked">Вас вигнали з цієї групи</string>
     <string name="conference_shutdown">Цю групу закрили</string>
-    <string name="conference_unknown_error">Ви більше не берете участі у цій групі</string>
+    <string name="conference_unknown_error">Ви більше не є учасником цієї групи</string>
     <string name="using_account">обліковий запис %s</string>
     <string name="hosted_on">розміщений на %s</string>
-    <string name="checking_x">Перевіряю %s на вузлі з HTTP</string>
+    <string name="checking_x">Перевірка %s на вузлі з HTTP</string>
     <string name="not_connected_try_again">Ви поза мережею. Спробуйте ще раз пізніше</string>
     <string name="check_x_filesize">Перевірити %s розмір</string>
     <string name="check_x_filesize_on_host">Перевірити %1$s розмір на %2$s</string>
@@ -319,9 +319,9 @@
     <string name="pref_create_backup_summary">Резервні копії зберігатимуться до %s</string>
     <string name="notification_create_backup_title">Створення резервних копій</string>
     <string name="notification_backup_created_title">Резервну копію створено</string>
-    <string name="notification_backup_created_subtitle">Резервні копії збережено до %s</string>
-    <string name="restoring_backup">Відтворення з резервної копії</string>
-    <string name="notification_restored_backup_title">Відтворено з резервної копії</string>
+    <string name="notification_backup_created_subtitle">Резервну копію збережено до %s</string>
+    <string name="restoring_backup">Відновлення з резервної копії</string>
+    <string name="notification_restored_backup_title">Відновлено з резервної копії</string>
     <string name="notification_restored_backup_subtitle">Не забудьте увімкнути обліковий запис.</string>
     <string name="choose_file">Файл</string>
     <string name="receiving_x_file">Отримання %1$s (%2$d%% завершено)</string>
@@ -334,8 +334,8 @@
     <string name="x_file_offered_for_download">%s запропоновано для завантаження</string>
     <string name="cancel_transmission">Припинити передачу</string>
     <string name="file_transmission_failed">передача файлу не вдалася</string>
-    <string name="file_transmission_cancelled">скасовано передачу файлу</string>
-    <string name="file_deleted">Файл вилучено</string>
+    <string name="file_transmission_cancelled">передачу файлу скасовано</string>
+    <string name="file_deleted">Файл видалено</string>
     <string name="no_application_found_to_open_file">Не знайдено застосунку, щоб відкрити файл</string>
     <string name="no_application_found_to_open_link">Не знайдено застосунку, щоб відкрити посилання</string>
     <string name="no_application_found_to_view_contact">Не знайдено застосунку, щоб переглянути контакт</string>
@@ -378,12 +378,12 @@
     <string name="grant_owner_privileges">Надати права власника</string>
     <string name="remove_owner_privileges">Відкликати права власника</string>
     <string name="remove_from_room">Вилучити з групи</string>
-    <string name="remove_from_channel">Прибрати з каналу</string>
-    <string name="could_not_change_affiliation">Неможливо змінити роль %s</string>
+    <string name="remove_from_channel">Вилучити з каналу</string>
+    <string name="could_not_change_affiliation">Неможливо змінити статус участі %s</string>
     <string name="ban_from_conference">Заборонити доступ до групи</string>
-    <string name="ban_from_channel">Вилучити з каналу</string>
+    <string name="ban_from_channel">Заборонити в каналі</string>
     <string name="removing_from_public_conference">Ви намагаєтеся вилучити %s з публічного каналу. Єдиним способом для цього є заблокувати користувача назавжди.</string>
-    <string name="ban_now">Вилучити зараз</string>
+    <string name="ban_now">Заборонити зараз</string>
     <string name="could_not_change_role">Неможливо змінити роль %s</string>
     <string name="conference_options">Налаштування приватного чату</string>
     <string name="channel_options">Налаштування публічного каналу</string>
@@ -400,19 +400,19 @@
     <string name="mark_as_read">Позначити як прочитане</string>
     <string name="pref_input_options">Введення</string>
     <string name="pref_enter_is_send">Enter для надсилання</string>
-    <string name="pref_enter_is_send_summary">Використовувати кнопку Enter для надсилання повідомлень. Якщо цей параметр вимкнено, повідомлення можна надсилати за допомогою комбінації Ctrl+Enter.</string>
+    <string name="pref_enter_is_send_summary">Використовувати кнопку Enter для надсилання повідомлень. Також для надсилання можна використовувати Ctrl+Enter, навіть якщо цей параметр вимкнено.</string>
     <string name="pref_display_enter_key">Показувати кнопку Enter</string>
     <string name="pref_display_enter_key_summary">Змінити клавішу емоційок на кнопку Enter</string>
     <string name="audio">аудіо</string>
     <string name="video">відео</string>
     <string name="image">зображення</string>
     <string name="pdf_document">документ PDF</string>
-    <string name="apk">Програма Android</string>
-    <string name="vcard">Контакт</string>
+    <string name="apk">програма Android</string>
+    <string name="vcard">контакт</string>
     <string name="avatar_has_been_published">Аватар опубліковано!</string>
     <string name="sending_x_file">Надсилання %s</string>
     <string name="offering_x_file">Пропозиція %s</string>
-    <string name="hide_offline">Сховати поза мережею</string>
+    <string name="hide_offline">Сховати «Поза мережею»</string>
     <string name="contact_is_typing">%s пише…</string>
     <string name="contact_has_stopped_typing">%s припинив писати</string>
     <string name="contacts_are_typing">%s пишуть…</string>
@@ -427,7 +427,7 @@
     <string name="title_undo_swipe_out_channel">Залишити публічний канал</string>
     <string name="pref_dont_trust_system_cas_title">Не довіряти системним центрам сертифікації</string>
     <string name="pref_dont_trust_system_cas_summary">Усі сертифікати мають бути підтверджені вручну</string>
-    <string name="pref_remove_trusted_certificates_title">Вилучати сертифікати</string>
+    <string name="pref_remove_trusted_certificates_title">Вилучити сертифікати</string>
     <string name="pref_remove_trusted_certificates_summary">Вилучити сертифікати, які було підтверджено вручну</string>
     <string name="toast_no_trusted_certs">Немає сертифікатів, підтверджених вручну</string>
     <string name="dialog_manage_certs_title">Вилучити сертифікати</string>
@@ -448,14 +448,14 @@
     <string name="user_has_left_conference">%1$s залишив групу</string>
     <string name="username">Ім\'я користувача</string>
     <string name="username_hint">Ім\'я користувача</string>
-    <string name="invalid_username">Таке ім\'я користувача не допустиме</string>
-    <string name="download_failed_server_not_found">Звантаження не вдалося: сервер не знайдено</string>
-    <string name="download_failed_file_not_found">Звантаження не вдалося: файл не знайдено</string>
-    <string name="download_failed_could_not_connect">Звантаження не вдалося: неможливо з\'єднатися з вузлом</string>
+    <string name="invalid_username">Таке ім\'я користувача недопустиме</string>
+    <string name="download_failed_server_not_found">Завантаження не вдалося: сервер не знайдено</string>
+    <string name="download_failed_file_not_found">Завантаження не вдалося: файл не знайдено</string>
+    <string name="download_failed_could_not_connect">Завантаження не вдалося: неможливо з\'єднатися з вузлом</string>
     <string name="download_failed_could_not_write_file">Завантаження не вдалося: неможливо записати файл</string>
     <string name="account_status_tor_unavailable">Мережа Tor недоступна</string>
     <string name="account_status_bind_failure">Прив\'язка не спрацювала</string>
-    <string name="account_status_host_unknown">Сервер не відповідає за цей домен</string>
+    <string name="account_status_host_unknown">Не несе відповідальності за домен</string>
     <string name="server_info_broken">Поламано</string>
     <string name="pref_presence_settings">Доступність</string>
     <string name="pref_treat_vibrate_as_silent">Вважати вібро за безшумний режим</string>
@@ -509,9 +509,9 @@
     <string name="disable">Вимкнути</string>
     <string name="selection_too_large">Вибрана ділянка завелика</string>
     <string name="no_accounts">(Немає активних облікових записів)</string>
-    <string name="this_field_is_required">Це обов\'язкове поле</string>
-    <string name="correct_message">Відредагувати</string>
-    <string name="send_corrected_message">Відредаговане повідомлення</string>
+    <string name="this_field_is_required">Це поле обов\'язкове</string>
+    <string name="correct_message">Виправити</string>
+    <string name="send_corrected_message">Надіслати виправлене</string>
     <string name="no_keys_just_confirm">Ви вже довіряєте цифровому підпису цієї особи. Вибираючи «Зроблено», Ви лише підтверджуєте, що %s є учасником групи.</string>
     <string name="this_account_is_disabled">Ви вимкнули цей обліковий запис</string>
     <string name="security_error_invalid_file_access">Помилка безпеки: неправильний доступ до файлу!</string>
@@ -522,7 +522,7 @@
 \nОбравши conversations.im в якості свого постачальника, Ви зможете спілкуватися з користувачами інших постачальників, для цього повідомте їм свою повну адресу XMPP.</string>
     <string name="your_full_jid_will_be">Ваша повна адреса XMPP: %s</string>
     <string name="create_account">Створити обліковий запис</string>
-    <string name="use_own_provider">Застосувати дані мого власного постачальника</string>
+    <string name="use_own_provider">Застосувати дані мого постачальника</string>
     <string name="pick_your_username">Придумайте ім\'я користувача</string>
     <string name="pref_manually_change_presence">Керувати станом вручну</string>
     <string name="pref_manually_change_presence_summary">Показувати доступність під час редагування повідомлення стану.</string>
@@ -567,7 +567,7 @@
     <string name="remote_server_not_found">Віддалений сервер не знайдено</string>
     <string name="remote_server_timeout">Затримка відповіді сервера</string>
     <string name="unable_to_update_account">Не вдалося оновити обліковий запис</string>
-    <string name="report_jid_as_spammer">Надіслати скаргу про те, що контакт з цим ID розсилає спам.</string>
+    <string name="report_jid_as_spammer">Надіслати скаргу про те, що контакт із цим ID розсилає спам.</string>
     <string name="pref_delete_omemo_identities">Вилучити ідентифікаційні дані OMEMO</string>
     <string name="pref_delete_omemo_identities_summary">Створити наново Ваші ключі OMEMO. Всі Ваші контакти будуть змушені підтвердити Вас знову. Використовуйте це, лише якщо немає іншого вибору.</string>
     <string name="delete_selected_keys">Вилучити вибрані ключі</string>
@@ -592,11 +592,11 @@
     <string name="pref_clean_cache_summary">Очистити теку з кешем (використовується застосунком Камера)</string>
     <string name="pref_clean_cache">Очистити кеш</string>
     <string name="pref_clean_private_storage">Очистити приватне сховище</string>
-    <string name="pref_clean_private_storage_summary">Очистити приватне сховище, де зберігаються файли (Їх можна повторно звантажити з сервера)</string>
+    <string name="pref_clean_private_storage_summary">Очистити приватне сховище, де зберігаються файли (Їх можна повторно завантажити з сервера)</string>
     <string name="i_followed_this_link_from_a_trusted_source">Я перейшов за цим посиланням від довіреного джерела</string>
     <string name="verifying_omemo_keys_trusted_source">Ви збираєтеся підтвердити ключі OMEMO для %1$s після переходу за посиланням. Це безпечно, лише якщо Ви отримали посилання з довіреного джерела, де лише %2$s міг опублікувати це посилання.</string>
     <string name="verify_omemo_keys">Підтвердити ключі OMEMO</string>
-    <string name="show_inactive_devices">Показувати неактивні</string>
+    <string name="show_inactive_devices">Показати неактивні</string>
     <string name="hide_inactive_devices">Приховати неактивні</string>
     <string name="distrust_omemo_key">Не довіряти пристрою</string>
     <string name="distrust_omemo_key_text">Ви впевнені, що більше не довіряєте цьому пристрою\?
@@ -639,7 +639,7 @@
     </plurals>
     <string name="pref_automatically_delete_messages">Автоматичне вилучення повідомлень</string>
     <string name="pref_automatically_delete_messages_description">Автоматично вилучати повідомлення з цього пристрою, старіші за зазначений проміжок часу.</string>
-    <string name="encrypting_message">Зашифровую повідомлення</string>
+    <string name="encrypting_message">Шифрування повідомлення</string>
     <string name="not_fetching_history_retention_period">Повідомлення не завантажуються з огляду на локально встановлений строк зберігання.</string>
     <string name="transcoding_video">Стиснення відео</string>
     <string name="contact_blocked_past_tense">Контакт заблоковано.</string>
@@ -669,7 +669,7 @@
     <string name="message">Повідомлення</string>
     <string name="private_messages_are_disabled">Приватні повідомлення вимкнено</string>
     <string name="mtm_accept_cert">Прийняти незнайомий сертифікат?</string>
-    <string name="mtm_trust_anchor">Сертифікат сервера не підтверджено відомим центром сертифікації.</string>
+    <string name="mtm_trust_anchor">Сертифікат сервера не підписано відомим центром сертифікації.</string>
     <string name="mtm_accept_servername">Прийняти сервер з невідповідним ім\'ям?</string>
     <string name="mtm_hostname_mismatch">Серверу не вдалося авторизуватися як «%s». Сертифікат чинний лише для:</string>
     <string name="mtm_connect_anyway">Усе одно бажаєте підключитися?</string>
@@ -681,11 +681,11 @@
     <string name="edit_status_message_title">Редагувати повідомлення стану</string>
     <string name="edit_status_message">Редагувати повідомлення стану</string>
     <string name="disable_encryption">Вимкнути шифрування</string>
-    <string name="error_trustkey_device_list">Неможливо отримати перелік пристроїв</string>
-    <string name="error_trustkey_bundle">Неможливо отримати пакети пристроїв</string>
+    <string name="error_trustkey_device_list">Не вдалося отримати перелік пристроїв</string>
+    <string name="error_trustkey_bundle">Не вдалося отримати ключі шифрування</string>
     <string name="error_trustkey_hint_mutual">Підказка: В деяких випадках це можна виправити, якщо додати один одного у свої списки контактів.</string>
     <string name="disable_encryption_message">Дійсно вимкнути шифрування OMEMO для цієї розмови?
-\nПісля цього в адміністратора Вашого сервера буде можливість мати доступ до Ваших повідомлень. Проте, це може бути єдиним способом спілкуватися з людьми, які використовують застарілі застосунки.</string>
+\nПісля цього в адміністратора сервера буде можливість читати Ваші повідомлення. Проте, це може бути єдиним способом спілкуватися з людьми, які використовують застарілі застосунки.</string>
     <string name="disable_now">Вимкнути зараз</string>
     <string name="draft">Чернетка:</string>
     <string name="pref_omemo_setting">Шифрування OMEMO</string>
@@ -727,7 +727,7 @@
     <string name="group_chat_name">Ім\'я</string>
     <string name="providing_a_name_is_optional">Ім\'я вказувати необов\'язково</string>
     <string name="create_dialog_group_chat_name">Назва групи</string>
-    <string name="conference_destroyed">Цю групу закрито</string>
+    <string name="conference_destroyed">Цю групу видалено</string>
     <string name="unable_to_save_recording">Неможливо зберегти запис</string>
     <string name="foreground_service_channel_name">Процес на передньому плані</string>
     <string name="notification_group_status_information">Інформація про стан</string>
@@ -760,13 +760,13 @@
     <string name="phone_number">номер телефону</string>
     <string name="verify_your_phone_number">Перевірте номер телефону</string>
     <string name="enter_country_code_and_phone_number">Quicksy надішле SMS, щоб перевірити Ваш номер телефону. Тарифами Вашого оператора може бути передбачено плату за отримання SMS. Зазначте код Вашої країни та номер телефону:</string>
-    <string name="we_will_be_verifying">Ми перевіримо номер телефону<br/><br/><b>%s</b><br/><br/>Гаразд чи бажаєте вказати інший номер\?</string>
+    <string name="we_will_be_verifying"><![CDATA[Ми перевіримо номер телефону<br/><br/><b>%s</b><br/><br/>Гаразд чи бажаєте вказати інший номер?]]></string>
     <string name="not_a_valid_phone_number">%s не є дійсним номером телефону.</string>
     <string name="please_enter_your_phone_number">Будь ласка, введіть свій номер телефону.</string>
     <string name="search_countries">Шукати країну</string>
     <string name="verify_x">Перевірити %s</string>
-    <string name="we_have_sent_you_an_sms_to_x">Ми направили Вам SMS на <b>%s</b>.</string>
-    <string name="we_have_sent_you_another_sms">Ми направили Вам SMS із кодом з 6 цифр.</string>
+    <string name="we_have_sent_you_an_sms_to_x"><![CDATA[SMS надіслано на <b>%s</b>.]]></string>
+    <string name="we_have_sent_you_another_sms">Ми надіслали Вам SMS із 6-цифровим кодом.</string>
     <string name="please_enter_pin_below">Будь ласка, введіть нижче 6 цифр коду.</string>
     <string name="resend_sms">Надіслати нове SMS</string>
     <string name="resend_sms_in">Надіслати нове SMS (%s)</string>
@@ -793,7 +793,7 @@
     <string name="try_again_in_x">Будь ласка, спробуйте ще раз через %s</string>
     <string name="rate_limited">Ви обмежені за рейтингом</string>
     <string name="too_many_attempts">Забагато спроб</string>
-    <string name="the_app_is_out_of_date">Версія Вашого застосунку застаріла.</string>
+    <string name="the_app_is_out_of_date">Ви користуєтеся застарілою версією застосунку.</string>
     <string name="update">Оновити</string>
     <string name="logged_in_with_another_device">Зараз цей номер телефону авторизований на іншому пристрої.</string>
     <string name="enter_your_name_instructions">Будь ласка, вкажіть своє ім\'я, щоб надати можливість людям, які не мають Вашого контакту, дізнатися, хто Ви є.</string>
@@ -812,10 +812,10 @@
     <string name="choose_account">Виберіть обліковий запис</string>
     <string name="restore_backup">Відновити з резервної копії</string>
     <string name="restore">Відновити</string>
-    <string name="enter_password_to_restore">Зазначте пароль до облікового запису %s, щоб відновити з резервної копії.</string>
+    <string name="enter_password_to_restore">Введіть пароль до облікового запису %s, щоб відновити з резервної копії.</string>
     <string name="restore_warning">Не використовуйте відновлення з резервної копії з метою клонування застосунку (запускати одночасно ще один примірник). Відновлення з резервної копії призначене виключно для перенесення даних або на випадок втрати оригінального пристрою.</string>
     <string name="unable_to_restore_backup">Неможливо відновити з резервної копії.</string>
-    <string name="unable_to_decrypt_backup">Неможливо розшифрувати резервну копію. Чи правильний пароль\?</string>
+    <string name="unable_to_decrypt_backup">Не вдалося розшифрувати резервну копію. Чи правильний пароль?</string>
     <string name="backup_channel_name">Створити або відновити резервну копію</string>
     <string name="enter_jabber_id">Введіть адресу XMPP</string>
     <string name="create_group_chat">Створити групу</string>
@@ -841,14 +841,14 @@
     <string name="jabber_ids_are_visible_to_admins">Адміністратори бачать адреси XMPP.</string>
     <string name="jabber_ids_are_visible_to_anyone">Будь-хто бачить адреси XMPP.</string>
     <string name="no_users_hint_channel">Цей публічний канал не має учасників. Запросіть Ваші контакти або скористайтеся кнопкою поширення, щоб поділитися адресою XMPP.</string>
-    <string name="no_users_hint_group_chat">У цій приватній групі відсутні учасники.</string>
+    <string name="no_users_hint_group_chat">У цій приватній групі немає учасників.</string>
     <string name="manage_permission">Керувати правами</string>
     <string name="search_participants">Шукати учасників</string>
     <string name="file_too_large">Файл надто великий</string>
     <string name="attach">Прикріпити</string>
-    <string name="discover_channels">Пошук каналу</string>
+    <string name="discover_channels">Пошук каналів</string>
     <string name="search_channels">Шукати канали</string>
-    <string name="channel_discovery_opt_in_title">Можливе порушення приватності!</string>
+    <string name="channel_discovery_opt_in_title">Можливе порушення конфіденційності!</string>
     <string name="i_already_have_an_account">Я вже маю обліковий запис</string>
     <string name="add_existing_account">Додати наявний обліковий запис</string>
     <string name="register_new_account">Зареєструвати новий обліковий запис</string>
@@ -873,7 +873,7 @@
     <string name="backup">Резервне копіювання</string>
     <string name="category_about">Про застосунок</string>
     <string name="please_enable_an_account">Будь ласка, активуйте обліковий запис</string>
-    <string name="make_call">Здійснити виклик</string>
+    <string name="make_call">Виклик</string>
     <string name="rtp_state_incoming_call">Вхідний виклик</string>
     <string name="rtp_state_incoming_video_call">Вхідний відеовиклик</string>
     <string name="rtp_state_connecting">З\'єднання</string>
@@ -883,7 +883,7 @@
     <string name="answer_call">Відповісти</string>
     <string name="dismiss_call">Відхилити</string>
     <string name="rtp_state_finding_device">Пошук пристроїв</string>
-    <string name="rtp_state_ringing">Викликаю</string>
+    <string name="rtp_state_ringing">Виклик</string>
     <string name="rtp_state_declined_or_busy">Зайнято</string>
     <string name="rtp_state_connectivity_error">Неможливо здійснити виклик</string>
     <string name="rtp_state_connectivity_lost_error">З\'єднання втрачено</string>
@@ -930,7 +930,7 @@
         <item quantity="many">%1$d пропущених викликів від %2$s</item>
         <item quantity="other">%1$d пропущених викликів від %2$s</item>
     </plurals>
-    <string name="download_failed_invalid_file">Звантаження не вдалося: неправильний файл</string>
+    <string name="download_failed_invalid_file">Завантаження не вдалося: неправильний файл</string>
     <string name="pref_dnd_on_silent_mode_summary">Встановити статус «Зайнятий», коли пристрій у безшумному режимі</string>
     <string name="rtp_state_content_add_video">Перемкнути на відеовиклик\?</string>
     <string name="no_storage_permission">Надати %1$s доступ до зовнішньої пам\'яті</string>
@@ -952,7 +952,7 @@
     <string name="add_contact_or_create_or_join_group_chat">Додати контакт, створити чи приєднатися до групи або знайти канали</string>
     <string name="pref_up_push_account_title">Обліковий запис XMPP</string>
     <string name="outgoing_call_timestamp">Вихідний виклик · %s</string>
-    <string name="openkeychain_required_long">%1$s використовує <b>OpenKeychain</b> для шифрування/дешифрування повідомлень і керування публічними ключами.<br><br>OpenKeychain поширюється на умовах ліцензії GPLv3+ і доступний для завантаження на F-Droid та Google Play.<br><br><small>(Після встановлення необхідно перезапустити %1$s.)</small></string>
+    <string name="openkeychain_required_long"><![CDATA[%1$s використовує <b>OpenKeychain</b> для шифрування/дешифрування повідомлень і керування публічними ключами.<br><br>OpenKeychain поширюється на умовах ліцензії GPLv3+ і доступний для завантаження на F-Droid та Google Play.<br><br><small>(Після встановлення необхідно перезапустити %1$s.)</small>]]></string>
     <string name="more_options">Додатково</string>
     <string name="unable_to_enable_video">Не вдалося увімкнути відео.</string>
     <string name="no_active_accounts_support_this">Жоден з активних облікових записів не підтримує цієї функції</string>
@@ -969,12 +969,12 @@
 \n
 \nУсі дані списку контактів залишаються на Вашому пристрої!</string>
     <string name="missed_calls_channel_name">Пропущені виклики</string>
-    <string name="data_saver_enabled_explained">Ваша операційна система обмежує для %1$s доступ до Інтернету у фоновому режимі. Щоб отримувати сповіщення про нові повідомлення, Вам потрібно дозволити %1$s необмежений доступ, коли заощадження трафіку увімкнено.
+    <string name="data_saver_enabled_explained">Ваша операційна система обмежує для %1$s доступ до Інтернету у фоновому режимі. Щоб отримувати сповіщення про нові повідомлення, Вам потрібно дозволити необмежений доступ для %1$s, коли заощадження трафіку ввімкнено.
 \n%1$s намагатиметься по можливості економити трафік.</string>
     <string name="pref_autojoin_summary">Встановлювати прапорець «autojoin» під час приєднання або виходу з групового чату/каналу та реагувати на зміни, зроблені іншими клієнтами.</string>
     <string name="battery_optimizations_enabled_explained">Ваш пристрій застосовує до %1$s інтенсивний режим енергозбереження, що може спричинити затримку сповіщень чи навіть втрату повідомлень.
 \nРекомендуємо вимкнути режим енергозбереження.</string>
-    <string name="could_not_correct_message">Не вдалося відредагувати повідомлення</string>
+    <string name="could_not_correct_message">Не вдалося виправити повідомлення</string>
     <string name="omemo_fingerprint_selected_message">Цифровий підпис OMEMO (джерело повідомлення)</string>
     <string name="omemo_fingerprint_x509_selected_message">Цифровий підпис v\\OMEMO (джерело повідомлення)</string>
     <string name="continue_btn">Продовжити</string>
@@ -984,7 +984,7 @@
     <string name="vector_graphic">векторна графіка</string>
     <string name="multimedia_file">мультимедіа</string>
     <string name="search_group_chats">Шукати групу</string>
-    <string name="pref_away_when_screen_off">«Відійшов» якщо екран заблоковано</string>
+    <string name="pref_away_when_screen_off">«Відійшов», якщо екран заблоковано</string>
     <string name="pref_away_when_screen_off_summary">Встановити статус «Відійшов», коли пристрій заблоковано</string>
     <string name="pref_dnd_on_silent_mode">«Зайнятий» у безшумному режимі</string>
     <string name="pref_treat_vibrate_as_dnd_summary">Встановити статус «Зайнятий», коли пристрій у віброрежимі</string>
@@ -1037,7 +1037,7 @@
     <string name="rtp_state_content_add">Додати ще пісні\?</string>
     <string name="restore_warning_continued">Не намагайтеся відновити резервні копії, які створили не Ви!</string>
     <string name="outdated_backup_file_format">Ви намагаєтеся імпортувати файл резервної копії у застарілому форматі</string>
-    <string name="audiobook">Аудіокнига</string>
+    <string name="audiobook">аудіокнига</string>
     <string name="reconnect_on_other_host">Відновити з\'єднання на іншому вузлі</string>
     <string name="this_account_is_logged_out">Ви вийшли з цього облікового запису</string>
     <string name="log_in">Увійти</string>
@@ -1067,7 +1067,7 @@
     <string name="pref_dynamic_colors">Динамічні кольори</string>
     <string name="pref_dynamic_colors_summary">Системні кольори (Material You)</string>
     <string name="pref_use_colorful_bubbles_summary">Різні кольори фону для надісланих та отриманих повідомлень</string>
-    <string name="pref_use_colorful_bubbles">Кольорові бульбашки в розмовах</string>
+    <string name="pref_use_colorful_bubbles">Кольорові бульбашки повідомлень</string>
     <string name="title_activity_new_chat">Нова розмова</string>
     <string name="action_archive_chat">Архівувати розмову</string>
     <string name="archive_this_chat">Після цього видалити розмову</string>
@@ -1137,4 +1137,22 @@
     <string name="server_info_sasl2">XEP-0388: Розширюваний профіль SASL</string>
     <string name="could_not_add_reaction">Не вдалося додати реакцію</string>
     <string name="add_reaction">Додати реакцію…</string>
+    <string name="more_reactions">Більше реакцій</string>
+    <string name="add_reaction_title">Додати реакцію</string>
+    <string name="could_not_modify_call">Не вдалося змінити виклик</string>
+    <string name="clients_may_not_support_av">Можливо, клієнт XMPP Вашого контакту не підтримує аудіо- та відеовиклики.</string>
+    <string name="pref_show_avatars">Показувати аватари</string>
+    <string name="pref_chat_bubbles">Бульбашки повідомлень</string>
+    <string name="pref_chat_bubbles_summary">Колір фону, розмір шрифту, аватари</string>
+    <string name="pref_title_bubbles">Вигляд повідомлень</string>
+    <string name="pref_show_avatars_summary">Крім групових чатів, показувати аватари також у Ваших повідомленнях і приватних чатах.</string>
+    <string name="pref_call_integration">Інтеграція викликів</string>
+    <string name="pref_call_integration_summary">Виклики з цього застосунку взаємодіють зі звичайними телефонними дзвінками — наприклад, завершення одного дзвінка, коли починається інший.</string>
+    <string name="pref_align_start">Вирівнювання повідомлень ліворуч</string>
+    <string name="pref_align_start_summary">Вирівнювати всі повідомлення, включно з надісланими, по лівому краю для одноманітного вигляду розмов.</string>
+    <string name="custom_notifications_enable">Увімкнути індивідуальні налаштування сповіщень (важливість, звук, вібрація) для цієї розмови?</string>
+    <string name="custom_notifications">Індивідуальні сповіщення</string>
+    <string name="show_to_contacts_only">Показувати лише контактам</string>
+    <string name="delete_avatar_message">Бажаєте видалити свій аватар? Деякі клієнти можуть продовжувати відображати копію Вашого аватара з кешу.</string>
+    <string name="account_status_connection_timeout">Час очікування з\'єднання вичерпано</string>
 </resources>
  
  
  
    
    @@ -9,45 +9,45 @@
     <string name="action_add_account">添加账号</string>
     <string name="action_edit_contact">编辑名称</string>
     <string name="action_add_phone_book">添加到通讯录</string>
-    <string name="action_delete_contact">从联系人列表删除</string>
+    <string name="action_delete_contact">删除联系人</string>
     <string name="action_block_contact">屏蔽联系人</string>
-    <string name="action_unblock_contact">解除屏蔽联系人</string>
+    <string name="action_unblock_contact">取消屏蔽联系人</string>
     <string name="action_block_domain">屏蔽域名</string>
-    <string name="action_unblock_domain">解除屏蔽域名</string>
+    <string name="action_unblock_domain">取消屏蔽域名</string>
     <string name="action_block_participant">屏蔽参与者</string>
-    <string name="action_unblock_participant">解除屏蔽参与者</string>
+    <string name="action_unblock_participant">取消屏蔽参与者</string>
     <string name="title_activity_manage_accounts">管理账号</string>
     <string name="title_activity_settings">设置</string>
     <string name="title_activity_choose_contact">选择联系人</string>
     <string name="title_activity_choose_contacts">选择联系人</string>
     <string name="title_activity_share_via_account">通过账号分享</string>
     <string name="title_activity_block_list">屏蔽列表</string>
-    <string name="just_now">刚才</string>
+    <string name="just_now">刚刚</string>
     <string name="minute_ago">1 分钟前</string>
     <string name="minutes_ago">%d 分钟前</string>
     <plurals name="x_unread_conversations">
         <item quantity="other">%d 个未读对话</item>
     </plurals>
     <string name="sending">正在发送…</string>
-    <string name="message_decrypting">解密消息中。请稍候…</string>
+    <string name="message_decrypting">正在解密消息。请稍候…</string>
     <string name="pgp_message">OpenPGP 加密消息</string>
-    <string name="nick_in_use">此昵称已在使用中</string>
+    <string name="nick_in_use">昵称已被使用</string>
     <string name="invalid_muc_nick">昵称无效</string>
     <string name="admin">管理员</string>
     <string name="owner">所有者</string>
     <string name="moderator">主持人</string>
     <string name="participant">参与者</string>
-    <string name="visitor">访客</string>
-    <string name="remove_contact_text">是否从联系人列表中移除 %s?将不会移除与对方的对话。</string>
+    <string name="visitor">参观者</string>
+    <string name="remove_contact_text">是否从联系人列表移除 %s?将不会移除与对方的对话。</string>
     <string name="block_contact_text">是否屏蔽 %s 向您发送消息?</string>
-    <string name="unblock_contact_text">是否解除屏蔽 %s 并允许对方向您发送消息?</string>
+    <string name="unblock_contact_text">是否取消屏蔽 %s 并允许对方向您发送消息?</string>
     <string name="block_domain_text">屏蔽来自 %s 的所有联系人?</string>
-    <string name="unblock_domain_text">解除屏蔽来自 %s 的所有联系人?</string>
+    <string name="unblock_domain_text">取消屏蔽来自 %s 的所有联系人?</string>
     <string name="contact_blocked">联系人已屏蔽</string>
     <string name="blocked">已屏蔽</string>
     <string name="register_account">在服务器上注册新账号</string>
-    <string name="change_password_on_server">在服务器上修改密码</string>
-    <string name="share_with">分享至…</string>
+    <string name="change_password_on_server">在服务器上更改密码</string>
+    <string name="share_with">分享…</string>
     <string name="invite_contact">邀请联系人</string>
     <string name="invite">邀请</string>
     <string name="contacts">联系人</string>
@@ -58,25 +58,25 @@
     <string name="edit">编辑</string>
     <string name="delete">删除</string>
     <string name="block">屏蔽</string>
-    <string name="unblock">解除屏蔽</string>
+    <string name="unblock">取消屏蔽</string>
     <string name="save">保存</string>
-    <string name="ok">完成</string>
-    <string name="crash_report_title">%1$s 已崩溃</string>
+    <string name="ok">确定</string>
+    <string name="crash_report_title">%1$s 崩溃了</string>
     <string name="crash_report_message">使用您的 XMPP 账号发送崩溃报告有助于 %1$s 的持续开发。</string>
     <string name="send_now">立即发送</string>
     <string name="send_never">不再询问</string>
     <string name="problem_connecting_to_account">无法连接到账号</string>
     <string name="problem_connecting_to_accounts">无法连接到多个账号</string>
-    <string name="touch_to_fix">点击以管理您的账号</string>
+    <string name="touch_to_fix">点按即可管理账号</string>
     <string name="attach_file">附加文件</string>
     <string name="not_in_roster">对方不在您的联系人列表中,是否添加?</string>
     <string name="add_contact">添加联系人</string>
-    <string name="send_failed">传递失败</string>
+    <string name="send_failed">发送失败</string>
     <string name="preparing_image">正在准备发送图片</string>
     <string name="preparing_images">正在准备发送图片</string>
-    <string name="sharing_files_please_wait">分享文件中。请稍候…</string>
-    <string name="action_clear_history">清空聊天记录</string>
-    <string name="clear_conversation_history">清空聊天记录</string>
+    <string name="sharing_files_please_wait">正在分享文件。请稍候…</string>
+    <string name="action_clear_history">清除历史记录</string>
+    <string name="clear_conversation_history">清除聊天记录</string>
     <string name="clear_histor_msg">是否要删除此对话中的所有消息?
 \n
 \n<b>警告:</b>存储在其他设备或服务器上的消息将不受影响。</string>
@@ -91,9 +91,9 @@
     <string name="send_omemo_x509_message">发送 v\\OMEMO 加密消息</string>
     <string name="your_nick_has_been_changed">正在使用新昵称</string>
     <string name="send_unencrypted">发送未加密</string>
-    <string name="decryption_failed">解密失败。也许您没有正确的私钥。</string>
+    <string name="decryption_failed">解密失败。也许您没有合适的私钥。</string>
     <string name="openkeychain_required">OpenKeychain</string>
-    <string name="openkeychain_required_long">%1$s 使用 <b>OpenKeychain</b> 来加密和解密消息并管理公钥。<br><br>它在 GPLv3+ 许可证下授权并可在 F-Droid 和 Google Play 上获得。<br><br><small>(请之后重启 %1$s。)</small></string>
+    <string name="openkeychain_required_long"><![CDATA[%1$s 使用 <b>OpenKeychain</b> 来加密和解密消息并管理公钥。<br><br>它在 GPLv3+ 许可证下授权并可在 F-Droid 和 Google Play 上获得。<br><br><small>(请之后重启 %1$s。)</small>]]></string>
     <string name="restart">重启</string>
     <string name="install">安装</string>
     <string name="openkeychain_not_installed">请安装 OpenKeychain</string>
@@ -117,8 +117,8 @@
     <string name="pref_led">LED 通知</string>
     <string name="pref_led_summary">收到新消息时闪烁通知灯</string>
     <string name="pref_ringtone">铃声</string>
-    <string name="pref_notification_sound">通知铃声</string>
-    <string name="pref_notification_sound_summary">新消息的通知铃声</string>
+    <string name="pref_notification_sound">通知提示音</string>
+    <string name="pref_notification_sound_summary">新消息的通知提示音</string>
     <string name="pref_call_ringtone_summary">来电铃声</string>
     <string name="pref_notification_grace_period">静默期</string>
     <string name="pref_notification_grace_period_summary">在其他设备上检测到活动之后,通知在此期间静音。</string>
@@ -139,11 +139,11 @@
     <string name="receive_presence_updates">接收在线状态更新</string>
     <string name="ask_for_presence_updates">请求在线状态更新</string>
     <string name="attach_choose_picture">选择图片</string>
-    <string name="attach_take_picture">拍摄图片</string>
+    <string name="attach_take_picture">拍摄照片</string>
     <string name="preemptively_grant">预先同意订阅请求</string>
-    <string name="error_not_an_image_file">所选的文件不是图片</string>
+    <string name="error_not_an_image_file">所选文件不是图片</string>
     <string name="error_compressing_image">无法转换图片文件</string>
-    <string name="error_file_not_found">文件未找到</string>
+    <string name="error_file_not_found">未找到文件</string>
     <string name="error_io_exception">常规 I/O 错误。也许存储空间已用完?</string>
     <string name="error_security_exception_during_image_copy">用来选择图片的应用未提供读取文件的足够权限。
 \n
@@ -154,12 +154,12 @@
     <string name="account_status_online">在线</string>
     <string name="account_status_connecting">正在连接…</string>
     <string name="account_status_offline">离线</string>
-    <string name="account_status_unauthorized">未授权</string>
-    <string name="account_status_not_found">服务器未找到</string>
+    <string name="account_status_unauthorized">未经授权</string>
+    <string name="account_status_not_found">未找到服务器</string>
     <string name="account_status_no_internet">未连接</string>
     <string name="account_status_regis_fail">注册失败</string>
-    <string name="account_status_regis_conflict">此用户名已在使用中</string>
-    <string name="account_status_regis_success">注册已完成</string>
+    <string name="account_status_regis_conflict">用户名已被使用</string>
+    <string name="account_status_regis_success">注册完成</string>
     <string name="account_status_regis_not_sup">服务器不支持注册</string>
     <string name="account_status_regis_invalid_token">注册令牌无效</string>
     <string name="account_status_tls_error">TLS 协商失败</string>
@@ -182,7 +182,7 @@
 \n您的联系人将无法再向您发送 OpenPGP 加密消息。</string>
     <string name="openpgp_has_been_published">OpenPGP 公钥已发布。</string>
     <string name="mgmt_account_enable">启用账号</string>
-    <string name="mgmt_account_delete_confirm_text">是否确定要删除账号?删除账号会擦除全部聊天记录</string>
+    <string name="mgmt_account_delete_confirm_text">是否确定要删除账号?删除账号会清除全部聊天记录</string>
     <string name="attach_record_voice">录制语音</string>
     <string name="account_settings_jabber_id">XMPP 地址</string>
     <string name="block_jabber_id">屏蔽 XMPP 地址</string>
@@ -190,22 +190,22 @@
     <string name="password">密码</string>
     <string name="invalid_jid">此 XMPP 地址无效</string>
     <string name="error_out_of_memory">空间不足。图片太大</string>
-    <string name="add_phone_book_text">是否要添加 %s 到您的通讯录中?</string>
+    <string name="add_phone_book_text">是否要添加 %s 到您的通讯录?</string>
     <string name="server_info_show_more">服务器信息</string>
-    <string name="server_info_mam">XEP-0313:消息存档管理</string>
+    <string name="server_info_mam">XEP-0313:消息归档管理</string>
     <string name="server_info_carbon_messages">XEP-0280:消息抄送</string>
     <string name="server_info_csi">XEP-0352:客户端状态指示</string>
     <string name="server_info_blocking">XEP-0191:屏蔽命令</string>
     <string name="server_info_roster_version">XEP-0237:花名册版本控制</string>
     <string name="server_info_stream_management">XEP-0198:流管理</string>
     <string name="server_info_external_service_discovery">XEP-0215:外部服务发现</string>
-    <string name="server_info_pep">XEP-0163:个人事件协议 (头像/OMEMO)</string>
+    <string name="server_info_pep">XEP-0163:个人事件协议(头像/OMEMO)</string>
     <string name="server_info_http_upload">XEP-0363:HTTP 文件上传</string>
     <string name="server_info_push">XEP-0357:推送</string>
     <string name="server_info_available">有效</string>
     <string name="server_info_unavailable">无效</string>
     <string name="missing_public_keys">缺少公钥公布</string>
-    <string name="last_seen_now">最后上线于刚才</string>
+    <string name="last_seen_now">最后上线于刚刚</string>
     <string name="last_seen_min">最后上线于 1 分钟前</string>
     <string name="last_seen_mins">最后上线于 %d 分钟前</string>
     <string name="last_seen_hour">最后上线于 1 小时前</string>
@@ -217,8 +217,8 @@
     <string name="openpgp_key_id">OpenPGP 密钥 ID</string>
     <string name="omemo_fingerprint">OMEMO 指纹</string>
     <string name="omemo_fingerprint_x509">v\\OMEMO 指纹</string>
-    <string name="omemo_fingerprint_selected_message">OMEMO 指纹 (消息来源)</string>
-    <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO 指纹 (消息来源)</string>
+    <string name="omemo_fingerprint_selected_message">OMEMO 指纹(消息来源)</string>
+    <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO 指纹(消息来源)</string>
     <string name="other_devices">其他设备</string>
     <string name="trust_omemo_fingerprints">信任 OMEMO 指纹</string>
     <string name="fetching_keys">正在获取密钥…</string>
@@ -229,8 +229,8 @@
     <string name="delete_contact">删除联系人</string>
     <string name="view_contact_details">查看联系人详情</string>
     <string name="block_contact">屏蔽联系人</string>
-    <string name="unblock_contact">解除屏蔽联系人</string>
-    <string name="create">新建</string>
+    <string name="unblock_contact">取消屏蔽联系人</string>
+    <string name="create">创建</string>
     <string name="select">选择</string>
     <string name="contact_already_exists">此联系人已存在</string>
     <string name="join">加入</string>
@@ -259,12 +259,12 @@
     <string name="contacts_and_n_more_have_read_up_to_this_point">%1$s 和其他 %2$d 人已阅读至此</string>
     <string name="everyone_has_read_up_to_this_point">每个人都已阅读至此</string>
     <string name="publish">发布</string>
-    <string name="touch_to_choose_picture">点击头像从图库中选择图片</string>
+    <string name="touch_to_choose_picture">点按头像即可从图库选择图片</string>
     <string name="publishing">正在发布…</string>
     <string name="error_publish_avatar_server_reject">服务器拒绝了您的发布</string>
     <string name="error_publish_avatar_converting">无法转换图片</string>
     <string name="error_saving_avatar">无法将头像保存到磁盘</string>
-    <string name="or_long_press_for_default">(或长按恢复默认)</string>
+    <string name="or_long_press_for_default">(或长按恢复默认)</string>
     <string name="error_publish_avatar_no_server_support">服务器不支持发布头像</string>
     <string name="private_message">私聊</string>
     <string name="private_message_to">至 %s</string>
@@ -276,7 +276,7 @@
     <string name="skip">跳过</string>
     <string name="disable_notifications">禁用通知</string>
     <string name="enable">启用</string>
-    <string name="conference_requires_password">进入群聊需要输入密码</string>
+    <string name="conference_requires_password">进入群聊需要密码</string>
     <string name="enter_password">输入密码</string>
     <string name="request_presence_updates">请先向您的联系人请求在线状态更新。
 \n
@@ -290,28 +290,28 @@
     <string name="pref_allow_message_correction">消息更正</string>
     <string name="pref_allow_message_correction_summary">允许您的联系人发送后编辑其消息</string>
     <string name="pref_expert_options">专家设置</string>
-    <string name="pref_expert_options_summary">请谨慎设置这些</string>
+    <string name="pref_expert_options_summary">请谨慎设置</string>
     <string name="title_activity_about_x">关于 %s</string>
-    <string name="title_pref_quiet_hours">免打扰时间</string>
+    <string name="title_pref_quiet_hours">勿扰时段</string>
     <string name="title_pref_quiet_hours_start_time">开始时间</string>
     <string name="title_pref_quiet_hours_end_time">结束时间</string>
-    <string name="title_pref_enable_quiet_hours">启用免打扰时间</string>
-    <string name="pref_quiet_hours_summary">通知将在免打扰时间内静音</string>
+    <string name="title_pref_enable_quiet_hours">启用勿扰时段</string>
+    <string name="pref_quiet_hours_summary">通知将在勿扰时段内静音</string>
     <string name="pref_expert_options_other">其他</string>
     <string name="pref_autojoin">同步书签</string>
     <string name="pref_autojoin_summary">在进入或离开群聊时设置“自动加入”标志,并回应其他客户端所做的改变。</string>
     <string name="toast_message_omemo_fingerprint">OMEMO 指纹已复制到剪贴板</string>
     <string name="conference_banned">禁止您进入此群聊</string>
-    <string name="conference_members_only">此群聊仅成员进入</string>
+    <string name="conference_members_only">只有成员才能进入此群聊</string>
     <string name="conference_resource_constraint">资源限制</string>
-    <string name="conference_kicked">已从群聊中踢出了您</string>
+    <string name="conference_kicked">您已被踢出此群聊</string>
     <string name="conference_shutdown">此群聊已关闭</string>
-    <string name="conference_unknown_error">您已不在群聊中</string>
-    <string name="conference_technical_problems">由于技术原因,您离开了群聊</string>
-    <string name="using_account">正在使用账号 %s</string>
-    <string name="hosted_on">托管于 %s</string>
+    <string name="conference_unknown_error">您已不在此群聊</string>
+    <string name="conference_technical_problems">由于技术原因,您离开了此群聊</string>
+    <string name="using_account">使用账号 %s</string>
+    <string name="hosted_on">托管在 %s</string>
     <string name="checking_x">正在 HTTP 主机上检查 %s</string>
-    <string name="not_connected_try_again">您未连接。稍后再试</string>
+    <string name="not_connected_try_again">您未连接,请稍后重试</string>
     <string name="check_x_filesize">检查 %s 的大小</string>
     <string name="check_x_filesize_on_host">在 %2$s 上检查 %1$s 的大小</string>
     <string name="message_options">消息选项</string>
@@ -322,31 +322,31 @@
     <string name="file_url">文件 URL</string>
     <string name="url_copied_to_clipboard">已复制 URL 到剪贴板</string>
     <string name="jabber_id_copied_to_clipboard">已复制 XMPP 地址到剪贴板</string>
-    <string name="error_message_copied_to_clipboard">已复制错误消息到剪贴板</string>
-    <string name="web_address">web 地址</string>
+    <string name="error_message_copied_to_clipboard">已复制出错信息到剪贴板</string>
+    <string name="web_address">网址</string>
     <string name="scan_qr_code">扫描二维码</string>
     <string name="show_qr_code">显示二维码</string>
     <string name="show_block_list">显示屏蔽列表</string>
     <string name="account_details">账号详情</string>
     <string name="confirm">确认</string>
-    <string name="try_again">再试一次</string>
+    <string name="try_again">重试</string>
     <string name="pref_keep_foreground_service">前台服务</string>
     <string name="pref_keep_foreground_service_summary">防止操作系统中断连接</string>
     <string name="pref_create_backup">创建备份</string>
     <string name="pref_create_backup_summary">备份文件将存储在 %s</string>
     <string name="notification_create_backup_title">正在创建备份文件</string>
-    <string name="notification_backup_created_title">已创建备份</string>
+    <string name="notification_backup_created_title">备份已创建</string>
     <string name="notification_backup_created_subtitle">此备份文件已存储在 %s</string>
     <string name="restoring_backup">正在恢复备份</string>
-    <string name="notification_restored_backup_title">已恢复备份</string>
-    <string name="notification_restored_backup_subtitle">不要忘记启用账号。</string>
+    <string name="notification_restored_backup_title">备份已恢复</string>
+    <string name="notification_restored_backup_subtitle">请勿忘记启用账号。</string>
     <string name="choose_file">选择文件</string>
-    <string name="receiving_x_file">正在接收 %1$s (%2$d%% 已完成)</string>
+    <string name="receiving_x_file">正在接收 %1$s(%2$d%% 已完成)</string>
     <string name="download_x_file">下载 %s</string>
     <string name="delete_x_file">删除 %s</string>
     <string name="file">文件</string>
     <string name="open_x_file">打开 %s</string>
-    <string name="sending_file">正在发送 (%1$d%% 已完成)</string>
+    <string name="sending_file">正在发送(%1$d%% 已完成)</string>
     <string name="preparing_file">正在准备分享文件</string>
     <string name="x_file_offered_for_download">%s 可供下载</string>
     <string name="cancel_transmission">取消传输</string>
@@ -359,31 +359,29 @@
     <string name="pref_show_dynamic_tags">动态标签</string>
     <string name="pref_show_dynamic_tags_summary">在联系人下方显示动态标签汇总</string>
     <string name="enable_notifications">启用通知</string>
-    <string name="no_conference_server_found">群聊服务器未找到</string>
+    <string name="no_conference_server_found">未找到群聊服务器</string>
     <string name="conference_creation_failed">无法创建群聊</string>
     <string name="account_image_description">账号头像</string>
     <string name="copy_omemo_clipboard_description">复制 OMEMO 指纹到剪贴板</string>
     <string name="regenerate_omemo_key">重新生成 OMEMO 密钥</string>
     <string name="clear_other_devices">清除设备</string>
     <string name="clear_other_devices_desc">是否确定要从 OMEMO 公布中清除所有其他设备?下次连接时,设备将会重新公布,但可能不会收到在此期间发送的消息。</string>
-    <string name="error_no_keys_to_trust_server_error">此联系人没有可用的密钥。
-\n无法从服务器获取新密钥。也许是对方的服务器出了问题?</string>
-    <string name="error_no_keys_to_trust_presence">此联系人没有可用的密钥。
-\n请确保双方都有在线状态订阅。</string>
+    <string name="error_no_keys_to_trust_server_error">此联系人没有可用密钥。 \n无法从服务器获取新密钥。也许是对方的服务器有问题?</string>
+    <string name="error_no_keys_to_trust_presence">此联系人没有可用密钥。 \n请确保双方都有在线状态订阅。</string>
     <string name="error_trustkeys_title">出了点问题</string>
-    <string name="fetching_history_from_server">正在从服务器获取聊天记录</string>
-    <string name="no_more_history_on_server">服务器上没有更多聊天记录</string>
+    <string name="fetching_history_from_server">正在从服务器获取历史记录</string>
+    <string name="no_more_history_on_server">服务器上没有更多历史记录</string>
     <string name="updating">正在更新…</string>
-    <string name="password_changed">密码已修改!</string>
-    <string name="could_not_change_password">无法修改密码</string>
-    <string name="change_password">修改密码</string>
+    <string name="password_changed">密码已更改!</string>
+    <string name="could_not_change_password">无法更改密码</string>
+    <string name="change_password">更改密码</string>
     <string name="current_password">当前密码</string>
     <string name="new_password">新密码</string>
     <string name="password_should_not_be_empty">密码不能为空</string>
     <string name="enable_all_accounts">启用所有账号</string>
     <string name="disable_all_accounts">禁用所有账号</string>
     <string name="perform_action_with">执行操作</string>
-    <string name="no_affiliation">无从属关系</string>
+    <string name="no_affiliation">无</string>
     <string name="no_role">离线</string>
     <string name="outcast">被驱逐者</string>
     <string name="member">成员</string>
@@ -394,25 +392,25 @@
     <string name="remove_admin_privileges">撤销管理员权限</string>
     <string name="grant_owner_privileges">授予所有者权限</string>
     <string name="remove_owner_privileges">撤销所有者权限</string>
-    <string name="remove_from_room">从群聊中移除</string>
-    <string name="remove_from_channel">从频道中移除</string>
+    <string name="remove_from_room">从群聊移除</string>
+    <string name="remove_from_channel">从频道移除</string>
     <string name="could_not_change_affiliation">无法更改 %s 的从属关系</string>
-    <string name="ban_from_conference">从群聊中封禁</string>
-    <string name="ban_from_channel">从频道中封禁</string>
-    <string name="removing_from_public_conference">您正试图从公开频道中移除 %s。唯一的办法就是永远封禁该用户。</string>
+    <string name="ban_from_conference">从群聊封禁</string>
+    <string name="ban_from_channel">从频道封禁</string>
+    <string name="removing_from_public_conference">您正尝试从公开频道移除 %s。唯一办法是永久封禁该用户。</string>
     <string name="ban_now">立即封禁</string>
     <string name="could_not_change_role">无法更改 %s 的角色</string>
     <string name="conference_options">私人群聊配置</string>
     <string name="channel_options">公开频道配置</string>
-    <string name="members_only">私人,仅成员进入</string>
-    <string name="non_anonymous">公开用户的 XMPP 地址</string>
+    <string name="members_only">私人,仅成员</string>
+    <string name="non_anonymous">对任何参与者显示用户 XMPP 地址</string>
     <string name="moderated">开启频道发言审核</string>
-    <string name="you_are_not_participating">您未参与</string>
+    <string name="you_are_not_participating">您没有发言权</string>
     <string name="modified_conference_options">群聊配置修改成功!</string>
     <string name="could_not_modify_conference_options">无法修改群聊配置</string>
     <string name="never">从不</string>
     <string name="until_further_notice">直至另行通知</string>
-    <string name="snooze">稍后提醒</string>
+    <string name="snooze">暂停</string>
     <string name="reply">回复</string>
     <string name="mark_as_read">标记为已读</string>
     <string name="pref_input_options">输入</string>
@@ -428,7 +426,7 @@
     <string name="pdf_document">PDF 文档</string>
     <string name="apk">Android 应用</string>
     <string name="vcard">联系人</string>
-    <string name="avatar_has_been_published">已发布头像!</string>
+    <string name="avatar_has_been_published">头像已发布!</string>
     <string name="sending_x_file">正在发送 %s</string>
     <string name="offering_x_file">正在提供 %s</string>
     <string name="hide_offline">隐藏离线</string>
@@ -444,7 +442,7 @@
     <string name="location">位置</string>
     <string name="title_undo_swipe_out_group_chat">离开私人群聊</string>
     <string name="title_undo_swipe_out_channel">离开公开频道</string>
-    <string name="pref_dont_trust_system_cas_title">不信任系统证书颁发机构</string>
+    <string name="pref_dont_trust_system_cas_title">不信任系统 CA</string>
     <string name="pref_dont_trust_system_cas_summary">所有证书必须手动批准</string>
     <string name="pref_remove_trusted_certificates_title">移除证书</string>
     <string name="pref_remove_trusted_certificates_summary">删除手动批准的证书</string>
@@ -465,14 +463,14 @@
     <string name="username">用户名</string>
     <string name="username_hint">用户名</string>
     <string name="invalid_username">此用户名无效</string>
-    <string name="download_failed_server_not_found">下载失败:服务器未找到</string>
-    <string name="download_failed_file_not_found">下载失败:文件未找到</string>
+    <string name="download_failed_server_not_found">下载失败:未找到服务器</string>
+    <string name="download_failed_file_not_found">下载失败:未找到文件</string>
     <string name="download_failed_could_not_connect">下载失败:无法连接到主机</string>
     <string name="download_failed_could_not_write_file">下载失败:无法写入文件</string>
     <string name="download_failed_invalid_file">下载失败:文件无效</string>
-    <string name="account_status_tor_unavailable">Tor 网络不可用</string>
+    <string name="account_status_tor_unavailable">无法连接到 Tor 网络</string>
     <string name="account_status_bind_failure">绑定失败</string>
-    <string name="account_status_host_unknown">服务器不能为域名做出响应</string>
+    <string name="account_status_host_unknown">域名未响应</string>
     <string name="server_info_broken">损坏</string>
     <string name="pref_presence_settings">在线状态</string>
     <string name="pref_away_when_screen_off">设备锁定时离开</string>
@@ -486,17 +484,17 @@
     <string name="hostname_example">xmpp.example.com</string>
     <string name="action_add_account_with_certificate">用证书登录</string>
     <string name="unable_to_parse_certificate">无法解析证书</string>
-    <string name="mam_prefs">存档首选项</string>
-    <string name="server_side_mam_prefs">服务器端存档首选项</string>
-    <string name="fetching_mam_prefs">获取存档首选项中。请稍候…</string>
-    <string name="unable_to_fetch_mam_prefs">无法获取存档首选项</string>
+    <string name="mam_prefs">归档首选项</string>
+    <string name="server_side_mam_prefs">服务器端归档首选项</string>
+    <string name="fetching_mam_prefs">正在获取归档首选项。请稍候…</string>
+    <string name="unable_to_fetch_mam_prefs">无法获取归档首选项</string>
     <string name="captcha_required">需要验证码</string>
-    <string name="captcha_hint">输入上图中的文字</string>
-    <string name="certificate_chain_is_not_trusted">不受信任的证书链</string>
+    <string name="captcha_hint">请按上图输入文本</string>
+    <string name="certificate_chain_is_not_trusted">未受信任的证书链</string>
     <string name="jid_does_not_match_certificate">XMPP 地址与证书不匹配</string>
     <string name="action_renew_certificate">续订证书</string>
     <string name="error_fetching_omemo_key">获取 OMEMO 密钥时出错!</string>
-    <string name="verified_omemo_key_with_certificate">已通过证书验证 OMEMO 密钥!</string>
+    <string name="verified_omemo_key_with_certificate">OMEMO 密钥已通过证书验证!</string>
     <string name="device_does_not_support_certificates">设备不支持客户端证书选择!</string>
     <string name="pref_connection_options">连接</string>
     <string name="pref_use_tor">通过 Tor 连接</string>
@@ -506,7 +504,7 @@
     <string name="hostname_or_onion">服务器或 .onion 地址</string>
     <string name="not_a_valid_port">此端口号无效</string>
     <string name="not_valid_hostname">此主机名无效</string>
-    <string name="connected_accounts">已连接 %2$d 个账号中的 %1$d 个</string>
+    <string name="connected_accounts">已连接 %1$d 个账号(共 %2$d 个)</string>
     <plurals name="x_messages">
         <item quantity="other">%d 条消息</item>
     </plurals>
@@ -518,37 +516,31 @@
     <string name="no_storage_permission">授予 %1$s 访问外部存储的权限</string>
     <string name="no_camera_permission">授予 %1$s 访问相机的权限</string>
     <string name="sync_with_contacts">联系人列表集成</string>
-    <string name="sync_with_contacts_long">%1$s 在您的设备上本地处理您的联系人列表,以向您显示 XMPP 上匹配联系人的名称和个人资料图片。
-\n
-\n任何联系人列表数据都不会离开您的设备!</string>
+    <string name="sync_with_contacts_long">%1$s 在您的设备上本地处理您的联系人列表,以向您显示 XMPP 上匹配联系人的名称和个人资料照片。\n\n任何联系人列表数据都不会离开您的设备!</string>
     <string name="notify_on_all_messages">通知所有消息</string>
     <string name="notify_only_when_highlighted">仅在提及时通知</string>
     <string name="notify_never">通知已禁用</string>
     <string name="notify_paused">通知已暂停</string>
     <string name="pref_picture_compression">图片压缩</string>
-    <string name="pref_picture_compression_summary">提示:无论此设置如何,使用“选择文件”会发送未压缩的图片。</string>
+    <string name="pref_picture_compression_summary">提示:使用“选择文件”始终会发送未压缩的图片。</string>
     <string name="always">始终</string>
     <string name="large_images_only">仅限大图片</string>
     <string name="battery_optimizations_enabled">电池优化已启用</string>
-    <string name="battery_optimizations_enabled_explained">您的设备正在对 %1$s 使用重度电池优化,可能会导致通知延迟甚至消息丢失。
-\n建议禁用此设置。</string>
-    <string name="battery_optimizations_enabled_dialog">您的设备正在对 %1$s 使用重度电池优化,可能会导致通知延迟甚至消息丢失。
-\n
-\n现在将要求您禁用此设置。</string>
+    <string name="battery_optimizations_enabled_explained">您的设备正在对 %1$s 使用重度电池优化,可能会导致通知延迟甚至消息丢失。\n建议禁用电池优化。</string>
+    <string name="battery_optimizations_enabled_dialog">您的设备正在对 %1$s 使用重度电池优化,可能会导致通知延迟甚至消息丢失。\n\n现在将要求您禁用电池优化。</string>
     <string name="disable">禁用</string>
     <string name="selection_too_large">所选区域太大</string>
-    <string name="no_accounts">(没有启用的账号)</string>
-    <string name="this_field_is_required">此字段是必需的</string>
+    <string name="no_accounts">(没有启用的账号)</string>
+    <string name="this_field_is_required">必填字段</string>
     <string name="correct_message">更正消息</string>
     <string name="send_corrected_message">发送更正后的消息</string>
-    <string name="no_keys_just_confirm">您已信任此人的指纹。选择“完成”即表示您确认 %s 是此群聊的一员。</string>
-    <string name="this_account_is_disabled">您已禁用了此账号</string>
+    <string name="no_keys_just_confirm">您已信任此人的指纹。选择“完成”即表示您确认 %s 是此群聊的成员。</string>
+    <string name="this_account_is_disabled">您禁用了此账号</string>
     <string name="security_error_invalid_file_access">安全错误:文件访问无效!</string>
     <string name="no_application_to_share_uri">未找到可以分享 URI 的应用</string>
-    <string name="share_uri_with">分享 URI 至…</string>
+    <string name="share_uri_with">分享 URI…</string>
     <string name="agree_and_continue">同意并继续</string>
-    <string name="magic_create_text">指导您在 conversations.im 上创建账号。
-\n当选择 conversations.im 作为提供者时,向其他 XMPP 用户提供您的完整地址,就能和对方交流。</string>
+    <string name="magic_create_text">指导您在 conversations.im 上创建账号。\n选择 conversations.im 作为提供者时,向别人提供您的完整 XMPP 地址,就能和对方交流。</string>
     <string name="your_full_jid_will_be">您的完整 XMPP 地址将是:%s</string>
     <string name="create_account">创建账号</string>
     <string name="use_own_provider">使用我自己的提供者</string>
@@ -561,9 +553,9 @@
     <string name="presence_away">离开</string>
     <string name="presence_xa">没空</string>
     <string name="presence_dnd">忙碌</string>
-    <string name="secure_password_generated">已生成安全密码</string>
+    <string name="secure_password_generated">安全密码已生成</string>
     <string name="device_does_not_support_battery_op">您的设备不支持选择退出电池优化</string>
-    <string name="registration_please_wait">注册失败:稍后再试</string>
+    <string name="registration_please_wait">注册失败:请稍后重试</string>
     <string name="registration_password_too_weak">注册失败:密码太弱</string>
     <string name="choose_participants">选择参与者</string>
     <string name="creating_conference">正在创建群聊…</string>
@@ -576,37 +568,36 @@
     <string name="pref_broadcast_last_activity_summary">让您的联系人知道您最后一次使用此应用的时间</string>
     <string name="pref_privacy">隐私</string>
     <string name="pref_theme_options">主题</string>
-    <string name="pref_theme_options_summary">选择主题色彩</string>
+    <string name="pref_theme_options_summary">选择调色板</string>
     <string name="pref_theme_automatic">自动</string>
     <string name="pref_theme_light">浅色</string>
     <string name="pref_theme_dark">深色</string>
     <string name="unable_to_connect_to_keychain">无法连接到 OpenKeychain</string>
-    <string name="this_device_is_no_longer_in_use">此设备已不再使用</string>
+    <string name="this_device_is_no_longer_in_use">此设备不再使用</string>
     <string name="type_pc">电脑</string>
     <string name="type_phone">手机</string>
     <string name="type_tablet">平板</string>
-    <string name="type_web">Web 浏览器</string>
+    <string name="type_web">网络浏览器</string>
     <string name="type_console">控制台</string>
     <string name="payment_required">需要付款</string>
     <string name="missing_internet_permission">授予使用互联网的权限</string>
     <string name="me">我</string>
     <string name="contact_asks_for_presence_subscription">对方请求在线状态订阅</string>
     <string name="allow">允许</string>
-    <string name="no_permission_to_access_x">没有权限访问 %s</string>
-    <string name="remote_server_not_found">远程服务器未找到</string>
+    <string name="no_permission_to_access_x">没有访问 %s 的权限</string>
+    <string name="remote_server_not_found">未找到远程服务器</string>
     <string name="remote_server_timeout">远程服务器超时</string>
     <string name="unable_to_update_account">无法更新账号</string>
-    <string name="report_jid_as_spammer">报告此 XMPP 地址发送垃圾消息。</string>
+    <string name="report_jid_as_spammer">举报此 XMPP 地址发送垃圾信息。</string>
     <string name="pref_delete_omemo_identities">删除 OMEMO 身份</string>
     <string name="pref_delete_omemo_identities_summary">重新生成 OMEMO 密钥。您的所有联系人将必须再次验证您。仅将此作为最后的方法。</string>
-    <string name="delete_selected_keys">删除已选密钥</string>
-    <string name="error_publish_avatar_offline">需要连接到网络才能发布头像。</string>
-    <string name="show_error_message">显示出错消息</string>
-    <string name="error_message">出错消息</string>
-    <string name="data_saver_enabled">省流量模式已启用</string>
-    <string name="data_saver_enabled_explained">操作系统正限制 %1$s 在后台时访问互联网。要接收新消息通知,应当在“省流量模式”开启时允许 %1$s 无限制访问。
-\n%1$s 仍将在可能的情况下努力节省数据。</string>
-    <string name="device_does_not_support_data_saver">设备不支持对 %1$s 禁用省流量模式。</string>
+    <string name="delete_selected_keys">删除所选密钥</string>
+    <string name="error_publish_avatar_offline">连接后才能发布头像。</string>
+    <string name="show_error_message">显示出错信息</string>
+    <string name="error_message">出错信息</string>
+    <string name="data_saver_enabled">流量节省程序已启用</string>
+    <string name="data_saver_enabled_explained">操作系统正限制 %1$s 在后台时访问互联网。要接收新消息通知,应当在“流量节省程序”开启时允许 %1$s 无限制访问。 \n在可能的情况下,%1$s 仍会尽可能节省数据。</string>
+    <string name="device_does_not_support_data_saver">设备不支持为 %1$s 禁用流量节省程序。</string>
     <string name="error_unable_to_create_temporary_file">无法创建临时文件</string>
     <string name="this_device_has_been_verified">此设备已经过验证</string>
     <string name="copy_fingerprint">复制指纹</string>
@@ -614,28 +605,27 @@
     <string name="verified_fingerprints">已验证的指纹</string>
     <string name="use_camera_icon_to_scan_barcode">使用相机扫描对方二维码</string>
     <string name="please_wait_for_keys_to_be_fetched">正在获取密钥,请稍候</string>
-    <string name="share_as_barcode">分享为二维码</string>
-    <string name="share_as_uri">分享为 XMPP URI</string>
-    <string name="share_as_http">分享为 HTTP 链接</string>
+    <string name="share_as_barcode">以二维码形式分享</string>
+    <string name="share_as_uri">以 XMPP URI 形式分享</string>
+    <string name="share_as_http">以 HTTP 链接形式分享</string>
     <string name="pref_blind_trust_before_verification">验证前盲目信任</string>
-    <string name="pref_blind_trust_before_verification_summary">信任来自未经验证的联系人的新设备,但对于已验证的联系人,提示手动确认新设备。</string>
-    <string name="blindly_trusted_omemo_keys">盲目信任的 OMEMO 密钥,表示它们可能是其他人或者某人可能冒充别人发送消息。</string>
-    <string name="not_trusted">不信任的</string>
+    <string name="pref_blind_trust_before_verification_summary">信任未经验证的联系人的新设备,但提示手动确认已验证的联系人的新设备。</string>
+    <string name="blindly_trusted_omemo_keys">盲目信任的 OMEMO 密钥,这意味着它们可能是别人的,可能会有人窃听。</string>
+    <string name="not_trusted">未受信任</string>
     <string name="invalid_barcode">二维码无效</string>
-    <string name="pref_clean_cache_summary">清理缓存文件夹 (由相机应用使用)</string>
+    <string name="pref_clean_cache_summary">清理缓存文件夹(由相机使用)</string>
     <string name="pref_clean_cache">清理缓存</string>
     <string name="pref_clean_private_storage">清理私人存储空间</string>
-    <string name="pref_clean_private_storage_summary">清理保存文件的私人存储 (它们可以从服务器重新下载)</string>
+    <string name="pref_clean_private_storage_summary">清理保存文件的私人存储(可从服务器重新下载)</string>
     <string name="i_followed_this_link_from_a_trusted_source">我从可信来源收到此链接</string>
-    <string name="verifying_omemo_keys_trusted_source">单击链接后,您将验证 %1$s 的 OMEMO 密钥。只有从可信来源(只有 %2$s 可以发布此链接)收到此链接才是安全的。</string>
+    <string name="verifying_omemo_keys_trusted_source">点击链接后,您将验证 %1$s 的 OMEMO 密钥。只有从可信来源(只有 %2$s 可以发布此链接)收到此链接才是安全的。</string>
     <string name="verifying_omemo_keys_trusted_source_account">您将验证自己账号的 OMEMO 密钥。只有从可信来源(只有您可以发布此链接)收到此链接才是安全的。</string>
     <string name="continue_btn">继续</string>
     <string name="verify_omemo_keys">验证 OMEMO 密钥</string>
     <string name="show_inactive_devices">显示非活动设备</string>
     <string name="hide_inactive_devices">隐藏非活动设备</string>
     <string name="distrust_omemo_key">不再信任设备</string>
-    <string name="distrust_omemo_key_text">是否确定要移除此设备的验证?
-\n此设备及其消息将标记为“不信任的”。</string>
+    <string name="distrust_omemo_key_text">是否确定要移除此设备的验证?\n此设备及其消息将标记为“未受信任”。</string>
     <plurals name="seconds">
         <item quantity="other">%d 秒</item>
     </plurals>
@@ -655,14 +645,14 @@
         <item quantity="other">%d 个月</item>
     </plurals>
     <string name="pref_automatically_delete_messages">自动删除消息</string>
-    <string name="pref_automatically_delete_messages_description">从此设备上自动删除超过配置时限的消息。</string>
+    <string name="pref_automatically_delete_messages_description">自动删除此设备上超过配置时限的消息。</string>
     <string name="encrypting_message">正在加密消息</string>
-    <string name="not_fetching_history_retention_period">由于本地保留期限设置,无法获取消息。</string>
+    <string name="not_fetching_history_retention_period">由于本地保留期,无法获取消息。</string>
     <string name="transcoding_video">正在压缩视频</string>
     <string name="contact_blocked_past_tense">联系人已屏蔽。</string>
     <string name="pref_notifications_from_strangers">陌生人的通知</string>
     <string name="pref_notifications_from_strangers_summary">收到陌生人的消息和来电时通知。</string>
-    <string name="received_message_from_stranger">收到了陌生人发来的消息</string>
+    <string name="received_message_from_stranger">收到了陌生人的消息</string>
     <string name="block_stranger">屏蔽陌生人</string>
     <string name="block_entire_domain">屏蔽整个域名</string>
     <string name="online_right_now">当前在线</string>
@@ -677,7 +667,7 @@
     <string name="today">今天</string>
     <string name="yesterday">昨天</string>
     <string name="pref_validate_hostname">用 DNSSEC 验证主机名</string>
-    <string name="pref_validate_hostname_summary">将包含主机名的服务器证书视为是已验证的</string>
+    <string name="pref_validate_hostname_summary">将包含已验证主机名的服务器证书视为已验证</string>
     <string name="certificate_does_not_contain_jid">证书不包含 XMPP 地址</string>
     <string name="server_info_partial">部分</string>
     <string name="attach_record_video">录制视频</string>
@@ -698,7 +688,7 @@
     <string name="edit_status_message_title">编辑状态信息</string>
     <string name="edit_status_message">编辑状态信息</string>
     <string name="disable_encryption">禁用加密</string>
-    <string name="error_trustkey_general">%1$s 无法发送加密消息到 %2$s。可能是由于您的联系人使用了过时的服务器或者无法处理 OMEMO 的客户端。</string>
+    <string name="error_trustkey_general">%1$s 无法向 %2$s 发送加密消息。可能是由于您的联系人使用了无法处理 OMEMO 的过时服务器或客户端。</string>
     <string name="error_trustkey_device_list">无法获取设备列表</string>
     <string name="error_trustkey_bundle">无法获取密钥</string>
     <string name="error_trustkey_hint_mutual">提示:某些情况下,双方可以添加到联系人列表解决此问题。</string>
@@ -707,21 +697,21 @@
     <string name="disable_now">立即禁用</string>
     <string name="draft">草稿:</string>
     <string name="pref_omemo_setting">OMEMO 加密</string>
-    <string name="pref_omemo_setting_summary_always">OMEMO 将始终用于一对一和私人群聊。</string>
+    <string name="pref_omemo_setting_summary_always">OMEMO 将始终用于一对一聊天和私人群聊。</string>
     <string name="pref_omemo_setting_summary_default_on">新对话将默认使用 OMEMO。</string>
     <string name="pref_omemo_setting_summary_default_off">新对话必须明确开启 OMEMO。</string>
     <string name="create_shortcut">创建快捷方式</string>
     <string name="default_on">默认开启</string>
     <string name="default_off">默认关闭</string>
-    <string name="not_encrypted_for_this_device">消息未对本设备加密。</string>
-    <string name="omemo_decryption_failed">解密 OMEMO 消息失败。</string>
+    <string name="not_encrypted_for_this_device">未对此设备加密消息。</string>
+    <string name="omemo_decryption_failed">无法解密 OMEMO 消息。</string>
     <string name="undo">撤销</string>
-    <string name="location_disabled">已禁用位置共享</string>
+    <string name="location_disabled">已禁用位置分享</string>
     <string name="action_fix_to_location">固定位置</string>
     <string name="action_unfix_from_location">取消固定位置</string>
     <string name="action_copy_location">复制位置</string>
     <string name="action_share_location">分享位置</string>
-    <string name="action_directions">方向</string>
+    <string name="action_directions">路线</string>
     <string name="title_activity_share_location">分享位置</string>
     <string name="title_activity_show_location">显示位置</string>
     <string name="share">分享</string>
@@ -731,13 +721,13 @@
     <string name="search_messages">搜索消息</string>
     <string name="gif">GIF</string>
     <string name="view_conversation">查看对话</string>
-    <string name="pref_use_share_location_plugin">位置共享插件</string>
-    <string name="pref_use_share_location_plugin_summary">使用位置共享插件代替内置地图</string>
-    <string name="copy_link">复制 web 地址</string>
+    <string name="pref_use_share_location_plugin">分享位置插件</string>
+    <string name="pref_use_share_location_plugin_summary">使用分享位置插件代替内置地图</string>
+    <string name="copy_link">复制网址</string>
     <string name="copy_jabber_id">复制 XMPP 地址</string>
-    <string name="p1_s3_filetransfer">用于 S3 的 HTTP 文件共享</string>
+    <string name="p1_s3_filetransfer">用于 S3 的 HTTP 文件分享</string>
     <string name="pref_start_search">直接搜索</string>
-    <string name="pref_start_search_summary">在“新对话”屏幕上打开键盘并将光标放在搜索栏中</string>
+    <string name="pref_start_search_summary">在“新对话”屏幕上打开键盘并将光标放入搜索栏</string>
     <string name="group_chat_avatar">群聊头像</string>
     <string name="host_does_not_support_group_chat_avatars">主机不支持群聊头像</string>
     <string name="only_the_owner_can_change_group_chat_avatar">只有所有者才能更改群聊头像</string>
@@ -760,43 +750,43 @@
     <string name="ongoing_calls_channel_name">正在进行的通话</string>
     <string name="missed_calls_channel_name">未接来电</string>
     <string name="silent_messages_channel_name">静音消息</string>
-    <string name="silent_messages_channel_description">此通知组用于显示不应触发任何声音的通知。例如,在另一台设备上处于活动状态时(静默期)。</string>
-    <string name="delivery_failed_channel_name">传递失败</string>
+    <string name="silent_messages_channel_description">(静默期)此通知组用于显示不应触发任何声音的通知。例如,在另一台设备上处于活动状态时。</string>
+    <string name="delivery_failed_channel_name">发送失败</string>
     <string name="pref_message_notification_settings">消息通知设置</string>
     <string name="pref_incoming_call_notification_settings">来电通知设置</string>
-    <string name="pref_more_notification_settings_summary">重要性、声音、振动</string>
+    <string name="pref_more_notification_settings_summary">重要程度、声音、振动</string>
     <string name="video_compression_channel_name">视频压缩</string>
     <string name="view_media">查看媒体</string>
     <string name="group_chat_members">参与者</string>
     <string name="media_browser">媒体浏览器</string>
     <string name="security_violation_not_attaching_file">由于违反安全规定,文件已删除。</string>
-    <string name="pref_video_compression">视频质量</string>
-    <string name="pref_video_compression_summary">质量越低,文件越小</string>
-    <string name="video_360p">中 (360p)</string>
-    <string name="video_720p">高 (720p)</string>
+    <string name="pref_video_compression">视频画质</string>
+    <string name="pref_video_compression_summary">画质越低,文件越小</string>
+    <string name="video_360p">360p(中)</string>
+    <string name="video_720p">720p(高)</string>
     <string name="cancelled">已取消</string>
     <string name="already_drafting_message">您已经在起草一条消息了。</string>
     <string name="feature_not_implemented">功能未实现</string>
-    <string name="invalid_country_code">无效的国家/地区代码</string>
+    <string name="invalid_country_code">国家/地区代码无效</string>
     <string name="choose_a_country">选择国家/地区</string>
     <string name="phone_number">电话号码</string>
     <string name="verify_your_phone_number">验证您的电话号码</string>
     <string name="enter_country_code_and_phone_number">Quicksy 将发送短信(运营商可能收费)来验证电话号码。请输入您的国家/地区代码和电话号码:</string>
-    <string name="we_will_be_verifying">我们将验证这个电话号码 <br/><br/><b>%s</b><br/><br/> 可以吗?是否编辑号码?</string>
+    <string name="we_will_be_verifying"><![CDATA[我们将验证这个电话号码 <br/><br/><b>%s</b><br/><br/> 可以吗?是否编辑号码?]]></string>
     <string name="not_a_valid_phone_number">%s 不是有效的电话号码。</string>
     <string name="please_enter_your_phone_number">请输入您的电话号码。</string>
     <string name="search_countries">搜索国家/地区</string>
     <string name="verify_x">验证 %s</string>
-    <string name="we_have_sent_you_an_sms_to_x">我们已向您发送短信至 <b>%s</b>。</string>
-    <string name="we_have_sent_you_another_sms">我们已经向您发送了另一条带有 6 位数验证码的短信。</string>
-    <string name="please_enter_pin_below">请在下方输入 6 位 PIN 码。</string>
+    <string name="we_have_sent_you_an_sms_to_x"><![CDATA[我们已向您发送短信至 <b>%s</b>。]]></string>
+    <string name="we_have_sent_you_another_sms">我们已向您发送了另一条带有六位数验证码的短信。</string>
+    <string name="please_enter_pin_below">请在下方输入六位数的 PIN 码。</string>
     <string name="resend_sms">重新发送短信</string>
     <string name="resend_sms_in">重新发送短信 (%s)</string>
     <string name="wait_x">请稍候 (%s)</string>
     <string name="back">返回</string>
     <string name="possible_pin">已自动从剪贴板粘贴可能的 PIN 码。</string>
     <string name="please_enter_pin">请输入您的 6 位 PIN 码。</string>
-    <string name="abort_registration_procedure">是否确定要中止注册程序?</string>
+    <string name="abort_registration_procedure">是否确定要中止注册过程?</string>
     <string name="yes">是</string>
     <string name="no">否</string>
     <string name="verifying">正在验证…</string>
@@ -808,9 +798,9 @@
     <string name="unable_to_connect_to_server">无法连接到服务器。</string>
     <string name="unable_to_establish_secure_connection">无法建立安全连接。</string>
     <string name="unable_to_find_server">无法找到服务器。</string>
-    <string name="something_went_wrong_processing_your_request">处理请求时出了问题。</string>
+    <string name="something_went_wrong_processing_your_request">处理您的请求时出了点问题。</string>
     <string name="invalid_user_input">用户输入无效</string>
-    <string name="temporarily_unavailable">暂时不可用。稍后再试。</string>
+    <string name="temporarily_unavailable">暂时不可用,请稍后重试。</string>
     <string name="no_network_connection">无网络连接。</string>
     <string name="try_again_in_x">请在 %s 后重试</string>
     <string name="rate_limited">您的速率受到限制</string>
@@ -828,13 +818,13 @@
     <string name="no_market_app_installed">未安装应用商店。</string>
     <string name="group_chat_will_make_your_jabber_id_public">此频道将公开您的 XMPP 地址</string>
     <string name="ebook">电子书</string>
-    <string name="video_original">原始 (未压缩)</string>
-    <string name="open_with">打开为…</string>
-    <string name="set_profile_picture">Conversations 个人资料图片</string>
+    <string name="video_original">未压缩(原始)</string>
+    <string name="open_with">打开…</string>
+    <string name="set_profile_picture">Conversations 个人资料照片</string>
     <string name="choose_account">选择账号</string>
     <string name="restore_backup">恢复备份</string>
     <string name="restore">恢复</string>
-    <string name="enter_password_to_restore">输入 %s 账号的密码以恢复备份。</string>
+    <string name="enter_password_to_restore">请输入 %s 的密码以恢复备份。</string>
     <string name="restore_warning">请勿使用恢复备份功能尝试克隆(同时运行)安装。恢复备份仅适用于迁移或您丢失原始设备的情况。</string>
     <string name="unable_to_restore_backup">无法恢复备份。</string>
     <string name="unable_to_decrypt_backup">无法解密备份。密码是否正确?</string>
@@ -853,15 +843,15 @@
     <string name="channel_already_exists">此频道已存在</string>
     <string name="joined_an_existing_channel">您已加入现有的频道</string>
     <string name="unable_to_set_channel_configuration">无法保存频道配置</string>
-    <string name="allow_participants_to_edit_subject">允许参与者(非访客)编辑话题</string>
-    <string name="allow_participants_to_invite_others">允许任何参与者邀请其他用户</string>
-    <string name="anyone_can_edit_subject">参与者(非访客)可以编辑话题。</string>
+    <string name="allow_participants_to_edit_subject">允许参与者编辑话题</string>
+    <string name="allow_participants_to_invite_others">允许参与者邀请他人</string>
+    <string name="anyone_can_edit_subject">参与者可以编辑话题。</string>
     <string name="owners_can_edit_subject">所有者可以编辑话题。</string>
     <string name="admins_can_edit_subject">管理员可以编辑话题。</string>
-    <string name="owners_can_invite_others">所有者可以邀请其他用户。</string>
-    <string name="anyone_can_invite_others">任何参与者可以邀请其他用户。</string>
-    <string name="jabber_ids_are_visible_to_admins">用户的 XMPP 地址仅管理员可见。</string>
-    <string name="jabber_ids_are_visible_to_anyone">用户的 XMPP 地址对任何人可见。</string>
+    <string name="owners_can_invite_others">所有者可以邀请他人。</string>
+    <string name="anyone_can_invite_others">参与者可以邀请他人。</string>
+    <string name="jabber_ids_are_visible_to_admins">管理员可以看到用户 XMPP 地址。</string>
+    <string name="jabber_ids_are_visible_to_anyone">任何参与者可以看到用户 XMPP 地址。</string>
     <string name="no_users_hint_channel">此公开频道无参与者。邀请联系人或使用分享按钮分发频道的 XMPP 地址。</string>
     <string name="no_users_hint_group_chat">此私人群聊无参与者。</string>
     <string name="manage_permission">管理权限</string>
@@ -886,8 +876,8 @@
     <string name="please_enter_password">请输入此账号的密码</string>
     <string name="unable_to_perform_this_action">无法执行此操作</string>
     <string name="open_join_dialog">加入公开频道…</string>
-    <string name="sharing_application_not_grant_permission">共享应用未授予访问此文件的权限。</string>
-    <string name="group_chats_and_channels">群聊和频道</string>
+    <string name="sharing_application_not_grant_permission">分享应用未授予访问此文件的权限。</string>
+    <string name="group_chats_and_channels"><![CDATA[群聊和频道]]></string>
     <string name="jabber_network">jabber.network</string>
     <string name="local_server">本地服务器</string>
     <string name="pref_channel_discovery_summary">大多数用户应该选择“jabber.network”以便从整个公共 XMPP 生态系统中获得更好的建议。</string>
@@ -897,13 +887,13 @@
     <string name="please_enable_an_account">请启用账号</string>
     <string name="make_call">进行通话</string>
     <string name="rtp_state_incoming_call">来电</string>
-    <string name="rtp_state_incoming_video_call">视频来电</string>
+    <string name="rtp_state_incoming_video_call">视频通话来电</string>
     <string name="rtp_state_content_add_video">切换到视频通话?</string>
     <string name="rtp_state_content_add">添加额外轨道?</string>
     <string name="rtp_state_connecting">正在连接</string>
     <string name="rtp_state_connected">已连接</string>
     <string name="rtp_state_reconnecting">正在重新连接</string>
-    <string name="rtp_state_accepting_call">正在接受通话</string>
+    <string name="rtp_state_accepting_call">正在接听通话</string>
     <string name="rtp_state_ending_call">正在结束通话</string>
     <string name="answer_call">接听</string>
     <string name="dismiss_call">拒接</string>
@@ -911,13 +901,13 @@
     <string name="rtp_state_ringing">正在响铃</string>
     <string name="rtp_state_declined_or_busy">占线</string>
     <string name="rtp_state_connectivity_error">无法连接通话</string>
-    <string name="rtp_state_connectivity_lost_error">连接已中断</string>
-    <string name="rtp_state_retracted">已撤回通话</string>
+    <string name="rtp_state_connectivity_lost_error">连接中断</string>
+    <string name="rtp_state_retracted">已取消通话</string>
     <string name="rtp_state_application_failure">应用错误</string>
     <string name="rtp_state_security_error">验证问题</string>
     <string name="hang_up">挂断</string>
-    <string name="ongoing_call">正在进行的通话</string>
-    <string name="ongoing_video_call">正在进行的视频通话</string>
+    <string name="ongoing_call">正在通话</string>
+    <string name="ongoing_video_call">正在进行视频通话</string>
     <string name="reconnecting_call">正在重新连接通话</string>
     <string name="reconnecting_video_call">正在重新连接视频通话</string>
     <string name="disable_tor_to_make_call">禁用 Tor 以进行通话</string>
@@ -938,8 +928,8 @@
     <string name="video_call">视频通话</string>
     <string name="help">帮助</string>
     <string name="microphone_unavailable">麦克风无法使用</string>
-    <string name="only_one_call_at_a_time">一次只能打一通电话。</string>
-    <string name="return_to_ongoing_call">返回到正在进行的通话</string>
+    <string name="only_one_call_at_a_time">每次只能有一个通话。</string>
+    <string name="return_to_ongoing_call">返回当前通话</string>
     <string name="could_not_switch_camera">无法切换摄像头</string>
     <string name="add_to_favorites">置顶</string>
     <string name="remove_from_favorites">取消置顶</string>
@@ -961,9 +951,9 @@
         <item quantity="other">查看 %1$d 位参与者</item>
     </plurals>
     <plurals name="some_messages_could_not_be_delivered">
-        <item quantity="other">无法传递一些消息</item>
+        <item quantity="other">一些消息发送失败</item>
     </plurals>
-    <string name="failed_deliveries">传递失败</string>
+    <string name="failed_deliveries">发送失败</string>
     <string name="more_options">更多选项</string>
     <string name="no_application_found">未找到应用程序</string>
     <string name="invite_to_app">邀请至 Conversations</string>
@@ -975,19 +965,19 @@
     <string name="plain_text_document">纯文本文档</string>
     <string name="account_registrations_are_not_supported">不支持账号注册</string>
     <string name="no_xmpp_adddress_found">未找到 XMPP 地址</string>
-    <string name="account_status_temporary_auth_failure">验证暂时失败</string>
+    <string name="account_status_temporary_auth_failure">临时身份验证失败</string>
     <string name="delete_avatar">删除头像</string>
-    <string name="audio_video_disabled_tor">使用 Tor 时,已禁用通话</string>
+    <string name="audio_video_disabled_tor">使用 Tor 时会禁用通话</string>
     <string name="switch_to_video">切换到视频</string>
     <string name="reject_switch_to_video">拒绝视频切换请求</string>
     <string name="pref_up_push_account_title">XMPP 账号</string>
     <string name="pref_up_push_server_title">推送服务器</string>
-    <string name="no_account_deactivated">无 (已停用)</string>
+    <string name="no_account_deactivated">无(已停用)</string>
     <string name="unified_push_distributor">UnifiedPush 分发程序</string>
     <string name="pref_up_push_account_summary">接收推送消息的账号。</string>
     <string name="decline">拒绝</string>
-    <string name="incoming_call_duration_timestamp">来电 (%s) · %s</string>
-    <string name="outgoing_call_duration_timestamp">去电 (%s) · %s</string>
+    <string name="incoming_call_duration_timestamp">来电(%s)· %s</string>
+    <string name="outgoing_call_duration_timestamp">去电(%s)· %s</string>
     <string name="outgoing_call_timestamp">去电 · %s</string>
     <string name="could_not_delete_account_from_server">无法从服务器删除账号</string>
     <string name="group_chats">群聊</string>
@@ -997,65 +987,65 @@
     <string name="outdated_backup_file_format">您正尝试导入过时的备份文件格式</string>
     <string name="audiobook">有声读物</string>
     <string name="reconnect_on_other_host">在其他主机上重新连接</string>
-    <string name="this_account_is_logged_out">您已登出此账号</string>
+    <string name="this_account_is_logged_out">您登出了此账号</string>
     <string name="log_in">登入</string>
     <string name="hide_notification">隐藏通知</string>
     <string name="contact_uses_unverified_keys">您的联系人使用未经验证的设备。扫描对方二维码进行验证并阻止主动式中间人攻击。</string>
     <string name="log_out">登出</string>
     <string name="account_state_logged_out">已登出</string>
     <string name="unverified_devices">您正在使用未经验证的设备。扫描您其他设备的二维码进行验证并阻止主动式中间人攻击。</string>
-    <string name="report_spam_and_block">报告垃圾消息并屏蔽垃圾消息发送者</string>
-    <string name="report_spam">报告垃圾消息</string>
+    <string name="report_spam_and_block">举报垃圾信息并屏蔽垃圾信息发送者</string>
+    <string name="report_spam">举报垃圾信息</string>
     <string name="welcome_header_quicksy">欢迎使用 Quicksy!</string>
     <string name="quicksy_wants_your_consent">Quicksy 请求您同意使用您的数据</string>
     <string name="privacy_policy">隐私政策</string>
-    <string name="contact_list_integration_not_available">联系人列表集成不可用</string>
-    <string name="rtp_state_contact_offline">联系人不可用</string>
-    <string name="no_permission_to_place_call">没有拨打电话的权限</string>
-    <string name="call_integration_not_available">呼叫集成不可用!</string>
-    <string name="remove_bookmark_and_close">是否移除 %s 的书签并存档对话?</string>
+    <string name="contact_list_integration_not_available">无法使用联系人列表集成功能</string>
+    <string name="rtp_state_contact_offline">联系人已离线</string>
+    <string name="no_permission_to_place_call">没有发起通话的权限</string>
+    <string name="call_integration_not_available">无法使用通话集成功能!</string>
+    <string name="remove_bookmark_and_close">是否移除 %s 的书签并归档对话?</string>
     <string name="remove_bookmark">是否移除 %s 的书签?</string>
-    <string name="delete_and_close">删除并存档对话</string>
+    <string name="delete_and_close">删除并归档对话</string>
     <string name="channel_discover_opt_in_message">频道发现使用称为 <a href=https://search.jabber.network>search.jabber.network</a> 的第三方服务。<br><br>使用此功能会将您的 IP 地址和搜索词传输到此服务。请参阅其 <a href=https://search.jabber.network/privacy>隐私政策</a> 以获取更多信息。</string>
     <string name="start_chat">开始对话</string>
     <string name="title_activity_share_with">分享至…</string>
-    <string name="pref_use_colorful_bubbles_summary">为已发送和已接收消息使用不同的背景颜色</string>
+    <string name="pref_use_colorful_bubbles_summary">为发送和接收的消息使用不同的背景颜色</string>
     <string name="no_certificate_selected">未选择客户端证书!</string>
-    <string name="pref_use_colorful_bubbles">彩色聊天气泡</string>
-    <string name="pref_dynamic_colors">动态色彩</string>
-    <string name="pref_dynamic_colors_summary">系统色彩 (Material You)</string>
+    <string name="pref_use_colorful_bubbles">多彩消息气泡</string>
+    <string name="pref_dynamic_colors">动态配色</string>
+    <string name="pref_dynamic_colors_summary">系统配色 (Material You)</string>
     <string name="title_activity_new_chat">新对话</string>
     <string name="archive_this_chat">之后删除对话</string>
-    <string name="title_undo_swipe_out_chat">对话已存档</string>
+    <string name="title_undo_swipe_out_chat">对话已归档</string>
     <string name="switch_to_chat">切换到对话</string>
-    <string name="action_archive_chat">存档对话</string>
+    <string name="action_archive_chat">归档对话</string>
     <string name="barcode_does_not_contain_fingerprints_for_this_chat">二维码不包含此对话的指纹。</string>
     <string name="welcome_header">加入对话</string>
-    <string name="corresponding_chats_closed">相应的对话已存档。</string>
+    <string name="corresponding_chats_closed">相应的对话已归档。</string>
     <string name="pref_send_crash_reports">发送崩溃报告</string>
     <string name="pref_title_security">安全</string>
     <string name="notifications">通知</string>
-    <string name="pref_attachments_summary">文件大小、图片压缩、视频质量</string>
+    <string name="pref_attachments_summary">文件大小、图片压缩、视频画质</string>
     <string name="pref_notifications_summary">静默期、铃声、振动、陌生人</string>
     <string name="pref_automatic_download">自动下载</string>
     <string name="appearance">外观</string>
     <string name="pref_light_dark_mode">浅色/深色模式</string>
     <string name="pref_allow_screenshots">允许截屏</string>
     <string name="pref_category_e2ee">端到端加密</string>
-    <string name="pref_title_trust_system_ca_store">证书颁发机构</string>
+    <string name="pref_title_trust_system_ca_store">证书授权服务</string>
     <string name="pref_title_trust_system_ca_store_summary">信任系统的 CA 证书</string>
     <string name="detect_mim">需要通道绑定</string>
     <string name="detect_mim_summary">通道绑定可以检测某些中间人攻击</string>
     <string name="pref_category_server_connection">服务器连接</string>
-    <string name="pref_summary_appearance">主题、色彩、截屏、输入</string>
+    <string name="pref_summary_appearance">主题、配色、截屏、输入</string>
     <string name="pref_category_sending">发送</string>
     <string name="pref_category_receiving">接收</string>
     <string name="pref_connection_summary">主机名和端口、Tor</string>
     <string name="pref_connection_summary_w_cd">主机名和端口、Tor、频道发现</string>
     <string name="pref_keyboard_options">键盘</string>
     <string name="pref_category_application">应用程序</string>
-    <string name="pref_category_interaction">交互</string>
-    <string name="pref_category_on_this_device">在设备上</string>
+    <string name="pref_category_interaction">互动</string>
+    <string name="pref_category_on_this_device">设备上</string>
     <string name="pref_title_interface">界面</string>
     <string name="pref_category_operating_system">操作系统</string>
     <string name="pref_summary_security">端到端加密、验证前盲目信任、中间人攻击检测</string>
@@ -1067,15 +1057,17 @@
     <string name="send_encrypted_message">发送加密消息</string>
     <string name="pref_large_font">大字体</string>
     <string name="pref_large_font_summary">增加消息气泡中的字体大小</string>
+    <string name="pref_accept_invites_from_strangers">陌生人的邀请</string>
+    <string name="pref_accept_invites_from_strangers_summary">接受来自陌生人的群聊邀请</string>
     <string name="pref_create_backup_one_off_summary">创建一次性备份</string>
     <string name="pref_backup_recurring">定期备份</string>
     <string name="pref_fullscreen_notification">全屏通知</string>
     <string name="pref_fullscreen_notification_summary">当设备锁定时,允许此应用显示占据全屏的来电通知。</string>
     <string name="unsupported_operation">不支持的操作</string>
-    <string name="pref_backup_summary">创建一次性备份、计划定期备份</string>
+    <string name="pref_backup_summary">创建一次性备份、设置定期备份</string>
     <string name="allow_private_messages">允许私信</string>
     <string name="edit_nick">编辑昵称</string>
-    <string name="your_avatar_tap_to_select_new_avatar">您的头像。点击从图库中选择新头像。</string>
+    <string name="your_avatar_tap_to_select_new_avatar">您的头像。点按即可从图库选择新头像。</string>
     <string name="could_not_disable_video">无法禁用视频。</string>
     <string name="delete_pgp_key">删除 OpenPGP 密钥</string>
     <string name="edit_name_and_topic">编辑名称和话题</string>
@@ -1086,13 +1078,31 @@
     <string name="call_is_using_speaker">正在使用扬声器进行通话。</string>
     <string name="call_is_using_bluetooth">正在使用蓝牙进行通话。</string>
     <string name="flip_camera">翻转摄像头</string>
-    <string name="video_is_disabled_tap_to_enable">视频已禁用,点击以启用。</string>
-    <string name="video_is_enabled_tap_to_disable">视频已启用,点击以禁用。</string>
-    <string name="call_is_using_speaker_tap_to_switch_to_earpiece">正在使用扬声器进行通话,点击切换到听筒。</string>
-    <string name="call_is_using_earpiece_tap_to_switch_to_speaker">正在使用听筒进行通话,点击切换到扬声器。</string>
+    <string name="video_is_disabled_tap_to_enable">已禁用视频,点按即可启用。</string>
+    <string name="video_is_enabled_tap_to_disable">已启用视频,点按即可禁用。</string>
+    <string name="call_is_using_speaker_tap_to_switch_to_earpiece">正在使用扬声器进行通话,点按即可切换到听筒。</string>
+    <string name="call_is_using_earpiece_tap_to_switch_to_speaker">正在使用听筒进行通话,点按即可切换到扬声器。</string>
     <string name="server_info_login_mechanism">登录机制</string>
     <string name="server_info_bind2">XEP-0386:绑定 2</string>
     <string name="server_info_sasl2">XEP-0388:可扩展 SASL 配置文件</string>
     <string name="could_not_add_reaction">无法添加回应</string>
     <string name="add_reaction">添加回应…</string>
+    <string name="more_reactions">更多回应</string>
+    <string name="add_reaction_title">添加回应</string>
+    <string name="could_not_modify_call">无法修改通话</string>
+    <string name="clients_may_not_support_av">您的联系人的 XMPP 客户端可能不支持音频/视频通话。</string>
+    <string name="pref_show_avatars">显示头像</string>
+    <string name="pref_chat_bubbles_summary">背景颜色、字体大小、头像</string>
+    <string name="pref_chat_bubbles">消息气泡</string>
+    <string name="pref_title_bubbles">消息气泡</string>
+    <string name="pref_show_avatars_summary">在群聊和一对一聊天中为您的消息显示头像。</string>
+    <string name="pref_call_integration">通话集成</string>
+    <string name="custom_notifications">自定义通知</string>
+    <string name="custom_notifications_enable">是否为此对话启用自定义通知(重要程度、声音、振动)设置?</string>
+    <string name="pref_call_integration_summary">此应用的通话与常规通话交互,例如另一个通话开始时结束一个通话。</string>
+    <string name="pref_align_start_summary">在左侧显示所有消息,包括发送的消息,以实现统一的聊天布局。</string>
+    <string name="pref_align_start">左对齐消息</string>
+    <string name="delete_avatar_message">是否删除头像?某些客户端可能会继续显示您头像的缓存副本。</string>
+    <string name="show_to_contacts_only">仅显示给联系人</string>
+    <string name="account_status_connection_timeout">连接超时</string>
 </resources>
  
  
  
    
    @@ -381,7 +381,7 @@
     <string name="pref_display_enter_key">顯示 Enter 鍵</string>
     <string name="pref_display_enter_key_summary">將表情符號鍵變更為 Enter 鍵</string>
     <string name="audio">音訊</string>
-    <string name="video">影片</string>
+    <string name="video">視頻</string>
     <string name="image">圖片</string>
     <string name="vector_graphic">向量圖形</string>
     <string name="multimedia_file">多媒體檔案</string>
@@ -716,7 +716,7 @@
     <string name="please_enter_your_phone_number">請輸入您的電話號碼。</string>
     <string name="search_countries">搜尋國家</string>
     <string name="verify_x">驗證 %s</string>
-    <string name="we_have_sent_you_an_sms_to_x">我們傳送簡訊至 <b>%s</b>。</string>
+    <string name="we_have_sent_you_an_sms_to_x"><![CDATA[我們已向您發送一條簡訊至 <b>%s</b>。]]></string>
     <string name="we_have_sent_you_another_sms">我們已傳送另外一則帶有 6 位數代碼的簡訊給您。</string>
     <string name="please_enter_pin_below">請在下面輸入 6 位數字的 PIN 碼。</string>
     <string name="resend_sms">重新傳送簡訊</string>
@@ -912,9 +912,9 @@
     <string name="error_security_exception_during_image_copy">您用來選取此圖像的應用程式沒有足夠的權限以讀取此檔案。
 \n
 \n<small>請使用不同的檔案管理員以選擇圖像</small>。</string>
-    <string name="openkeychain_required_long">%1$s 使用 <b>OpenKeychain</b> 以加密和解密訊息並管理您的公開金鑰。<br><br>已獲 GPLv3+ 授權並可在 F-Droid 和 Google Play 中取得。<br><br><small>(之後請重新啟動 %1$s。)</small></string>
+    <string name="openkeychain_required_long"><![CDATA[%1$s 使用<b>OpenKeychain</b> 來加密和解密消息並管理您的公鑰。<br><br>它根據 GPLv3+ 獲得許可,可在 F-Droid 和 Google Play 上使用。<br><br><small>(請稍後重新啟動 %1$s。)</small>]]></string>
     <string name="pref_clean_cache_summary">清除快取資料夾 (由相機應用程式使用)</string>
-    <string name="we_will_be_verifying">我們將驗證 <br/><br/><b>%s</b><br/><br/> 電話號碼是否正確,或者您想編輯這個號碼嗎?</string>
+    <string name="we_will_be_verifying"><![CDATA[我們將驗證電話號碼<br/><br/><b>%s</b><br/><br/>這是否正確,或者您想編輯該號碼嗎?]]></string>
     <string name="distrust_omemo_key_text">您確定要移除此裝置的驗證嗎?
 \n此裝置和來自此裝置的訊息將被標示為「未受信任」。</string>
     <string name="mtm_hostname_mismatch">由於「%s」,伺服器無法驗證,憑證僅對此有效:</string>
@@ -1033,7 +1033,7 @@
     <string name="title_activity_share_with">分享至…</string>
     <string name="title_activity_new_chat">新會話</string>
     <string name="action_archive_chat">存檔會話</string>
-    <string name="archive_this_chat">存檔此會話</string>
+    <string name="archive_this_chat">之後刪除聊天</string>
     <string name="switch_to_chat">切換到會話</string>
     <string name="send_encrypted_message">傳送加密消息</string>
     <string name="quicksy_wants_your_consent">Quicksy 請求您同意使用您的數據</string>
@@ -1066,4 +1066,37 @@
     <string name="remove_bookmark_and_close">是否移除 %s 的書籤並存檔會話?</string>
     <string name="pref_dynamic_colors">自動配色</string>
     <string name="channel_discover_opt_in_message">頻道探索使用一個名為 <a href=https://search.jabber.network>search.jabber.network</a> 的第三方服務,<br><br>使用此功能會將您的 IP 位址和搜尋詞彙傳送至此服務。更多資訊請參見他們的 <a href=https://search.jabber.network/privacy>隱私權政策</a>。</string>
+    <string name="pref_accept_invites_from_strangers">來自陌生人的邀請</string>
+    <string name="pref_accept_invites_from_strangers_summary">接受來自陌生人的群聊邀請</string>
+    <string name="add_reaction_title">添加回應</string>
+    <string name="more_reactions">更多回應</string>
+    <string name="pref_create_backup_one_off_summary">創建一次性備份</string>
+    <string name="pref_backup_summary">創建一次性、計劃重複</string>
+    <string name="pref_backup_recurring">定期備份</string>
+    <string name="pref_fullscreen_notification">全屏通知</string>
+    <string name="unsupported_operation">不支援的操作</string>
+    <string name="pref_fullscreen_notification_summary">允許此應用程式在裝置鎖定時顯示全屏的來電通知。</string>
+    <string name="could_not_modify_call">無法修改通話</string>
+    <string name="allow_private_messages">允許私人消息</string>
+    <string name="your_avatar_tap_to_select_new_avatar">您的大頭貼。點擊可從圖庫中選擇新大頭貼。</string>
+    <string name="could_not_disable_video">無法禁用影片。</string>
+    <string name="edit_nick">編輯暱稱</string>
+    <string name="delete_pgp_key">刪除 OpenPGP 金鑰</string>
+    <string name="edit_name_and_topic">編輯名稱和主題</string>
+    <string name="edit_configuration">更改配置</string>
+    <string name="change_notification_settings">更改通知設置</string>
+    <string name="flip_camera">翻轉相機</string>
+    <string name="video_is_disabled_tap_to_enable">視頻已禁用。點擊以啟用。</string>
+    <string name="server_info_bind2">XEP-0386:綁定 2</string>
+    <string name="server_info_sasl2">XEP-0388:可擴展 SASL 配置檔</string>
+    <string name="call_is_using_bluetooth">呼叫正在使用藍牙。</string>
+    <string name="video_is_enabled_tap_to_disable">已啟用視頻。點擊以禁用。</string>
+    <string name="server_info_login_mechanism">登錄機制</string>
+    <string name="call_is_using_earpiece_tap_to_switch_to_speaker">呼叫正在使用聽筒。點擊可切換到揚聲器。</string>
+    <string name="call_is_using_earpiece">呼叫正在使用聽筒。</string>
+    <string name="call_is_using_wired_headset">呼叫正在使用有線耳機</string>
+    <string name="call_is_using_speaker_tap_to_switch_to_earpiece">呼叫正在使用揚聲器。點擊可切換到聽筒。</string>
+    <string name="call_is_using_speaker">呼叫正在使用揚聲器。</string>
+    <string name="could_not_add_reaction">無法添加回應</string>
+    <string name="add_reaction">添加回應…</string>
 </resources>
  
  
  
    
    @@ -31,7 +31,7 @@
 <resources>
     <string name="pref_about_message" translatable="false">
 			Conversations • the very last word in instant messaging.
-			\n\nCopyright © 2014-2024 Daniel Gultsch
+			\n\nCopyright © 2014-2025 Daniel Gultsch
 			\n\nThis program is free software: you can redistribute it and/or modify
 			it under the terms of the GNU General Public License as published by
 			the Free Software Foundation, either version 3 of the License, or
  
  
  
    
    @@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <bool name="show_avatar_incoming_call">true</bool>
+    <bool name="is_portrait_mode">true</bool>
 </resources>
  
  
  
    
    @@ -21,6 +21,7 @@
     <bool name="large_font">false</bool>
     <string name="quick_action">none</string>
     <bool name="show_dynamic_tags">true</bool>
+    <bool name="show_avatars">true</bool>
     <bool name="btbv">true</bool>
     <integer name="automatic_message_deletion">0</integer>
     <bool name="trust_system_ca_store">true</bool>
@@ -59,4 +60,6 @@
     <bool name="default_custom_tab">true</bool>
     <bool name="default_nomedia">true</bool>
     <bool name="show_muc_pm">false</bool>
+    <bool name="call_integration">true</bool>
+    <bool name="align_start">false</bool>
 </resources>
  
  
  
    
    @@ -54,4 +54,10 @@
     <dimen name="small_margin">4dp</dimen>
     <dimen name="actionbar_text_size">20sp</dimen>
     <dimen name="material_input_text_to_prefix_suffix_padding">0dp</dimen>
+
+    <dimen name="bubble_horizontal_padding">8dp</dimen>
+    <dimen name="bubble_vertical_padding">4dp</dimen>
+    <dimen name="bubble_vertical_padding_minimum">1dp</dimen>
+    <dimen name="bubble_avatar_size">48dp</dimen>
+    <dimen name="bubble_avatar_distance">6dp</dimen>
 </resources>
  
  
  
    
    @@ -160,6 +160,7 @@
     <string name="account_status_unauthorized">Unauthorized</string>
     <string name="account_status_not_found">Server not found</string>
     <string name="account_status_no_internet">No connectivity</string>
+    <string name="account_status_connection_timeout">Connection timeout</string>
     <string name="account_status_regis_fail">Registration failed</string>
     <string name="account_status_regis_conflict">Username already in use</string>
     <string name="account_status_regis_success">Registration completed</string>
@@ -471,7 +472,7 @@
     <string name="download_failed_invalid_file">Download failed: Invalid file</string>
     <string name="account_status_tor_unavailable">Tor network unavailable</string>
     <string name="account_status_bind_failure">Bind failure</string>
-    <string name="account_status_host_unknown">The server is not responsible for this domain</string>
+    <string name="account_status_host_unknown">Not responsible for domain</string>
     <string name="server_info_broken">Broken</string>
     <string name="pref_presence_settings">Availability</string>
     <string name="pref_away_when_screen_off">Away when device is locked</string>
@@ -1095,4 +1096,21 @@
     <string name="server_info_login_mechanism">Login mechanism</string>
     <string name="could_not_add_reaction">Could not add reaction</string>
     <string name="add_reaction">Add reaction…</string>
+    <string name="add_reaction_title">Add reaction</string>
+    <string name="more_reactions">More reactions</string>
+    <string name="could_not_modify_call">Could not modify call</string>
+    <string name="clients_may_not_support_av">Your contact’s XMPP client might not support audio/video calls.</string>
+    <string name="pref_show_avatars">Show avatars</string>
+    <string name="pref_show_avatars_summary">Display avatars for your messages and in 1:1 chats, in addition to group chats.</string>
+    <string name="pref_chat_bubbles">Chat bubbles</string>
+    <string name="pref_chat_bubbles_summary">Background color, Font size, Avatars</string>
+    <string name="pref_title_bubbles">Chat Bubbles</string>
+    <string name="pref_call_integration">Call integration</string>
+    <string name="pref_call_integration_summary">Calls from this app interact with regular phone calls, such as ending one call when another starts.</string>
+    <string name="pref_align_start">Left-aligned messages</string>
+    <string name="pref_align_start_summary">Display all messages, including sent ones, on the left side for a uniform chat layout.</string>
+    <string name="custom_notifications">Custom notifications</string>
+    <string name="custom_notifications_enable">Enable customized notification settings (importance, sound, vibration) settings for this conversation?</string>
+    <string name="delete_avatar_message">Would you like to delete your avatar? Some clients might continue to display a cached copy of your avatar.</string>
+    <string name="show_to_contacts_only">Show to contacts only</string>
 </resources>
  
  
  
    
    @@ -79,20 +79,13 @@
             android:title="Custom Background Color"
             android:defaultValue="@color/md_theme_dark_surface"
             app:colorpicker_showAlpha="false" />
-    </PreferenceCategory>>
+    </PreferenceCategory>
     <PreferenceCategory android:title="@string/appearance">
-        <SwitchPreferenceCompat
-            android:defaultValue="@bool/use_green_background"
+        <Preference
             android:icon="@drawable/ic_forum_24dp"
-            android:key="use_green_background"
-            android:summary="@string/pref_use_colorful_bubbles_summary"
-            android:title="@string/pref_use_colorful_bubbles" />
-        <SwitchPreferenceCompat
-            android:defaultValue="@bool/large_font"
-            android:icon="@drawable/ic_format_size_24dp"
-            android:key="large_font"
-            android:summary="@string/pref_large_font_summary"
-            android:title="@string/pref_large_font" />
+            app:fragment="eu.siacs.conversations.ui.fragment.settings.InterfaceBubblesSettingsFragment"
+            app:summary="@string/pref_chat_bubbles_summary"
+            app:title="@string/pref_chat_bubbles" />
         <SwitchPreferenceCompat
             android:defaultValue="@bool/show_dynamic_tags"
             android:icon="@drawable/ic_label_24dp"
  
  
  
    
    @@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="@bool/use_green_background"
+        android:icon="@drawable/ic_colors_24dp"
+        android:key="use_green_background"
+        android:summary="@string/pref_use_colorful_bubbles_summary"
+        android:title="@string/pref_use_colorful_bubbles" />
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_format_align_left_24dp"
+        android:key="align_start"
+        android:summary="@string/pref_align_start_summary"
+        android:title="@string/pref_align_start" />
+    <SwitchPreferenceCompat
+        android:defaultValue="@bool/show_avatars"
+        android:icon="@drawable/ic_account_circle_24dp"
+        android:key="show_avatars"
+        android:summary="@string/pref_show_avatars_summary"
+        android:title="@string/pref_show_avatars" />
+    <SwitchPreferenceCompat
+        android:defaultValue="@bool/large_font"
+        android:icon="@drawable/ic_format_size_24dp"
+        android:key="large_font"
+        android:summary="@string/pref_large_font_summary"
+        android:title="@string/pref_large_font" />
+</PreferenceScreen>
  
  
  
    
    @@ -40,11 +40,12 @@
     <Preference
         android:icon="@drawable/ic_archive_24dp"
         android:key="backup"
-        app:fragment="eu.siacs.conversations.ui.fragment.settings.BackupSettingsFragment"
         android:summary="@string/pref_backup_summary"
-        android:title="@string/backup" />
+        android:title="@string/backup"
+        app:fragment="eu.siacs.conversations.ui.fragment.settings.BackupSettingsFragment" />
     <Preference
         android:icon="@drawable/ic_cloud_sync_24dp"
+        android:key="up"
         app:fragment="eu.siacs.conversations.ui.fragment.settings.UpSettingsFragment"
         app:summary="@string/unified_push_summary"
         app:title="@string/unified_push_distributor" />
  
  
  
    
    @@ -39,8 +39,6 @@
         android:key="led"
         android:summary="@string/pref_led_summary"
         android:title="@string/pref_led" />
-
-
     <Preference
         android:icon="@drawable/ic_phone_24dp"
         android:key="call_ringtone"
@@ -58,6 +56,12 @@
         android:key="fullscreen_notification"
         android:summary="@string/pref_fullscreen_notification_summary"
         android:title="@string/pref_fullscreen_notification" />
+    <SwitchPreferenceCompat
+        android:defaultValue="@bool/call_integration"
+        android:icon="@drawable/ic_mobile_friendly_24dp"
+        android:key="call_integration"
+        android:summary="@string/pref_call_integration_summary"
+        android:title="@string/pref_call_integration" />
     <ListPreference
         android:defaultValue="@integer/grace_period"
         android:entries="@array/grace_periods"
  
  
  
    
    @@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
+    <share-target android:targetClass="eu.siacs.conversations.ui.ShareWithActivity">
+        <data android:mimeType="*/*" />
+        <category android:name="eu.siacs.conversations.category.SHARE_TARGET" />
+    </share-target>
+</shortcuts>
  
  
  
    
    @@ -1,14 +1,13 @@
-Quicksy es una bifurcación del popular cliente Jabber/XMPP Conversations con busqueda automático de contactos.
+Quicksy es un spin-off del popular cliente Jabber/XMPP Conversations, que ofrece descubrimiento automático de contactos.
 
-Te registras con tu número de teléfono y Quicksy automáticamente, basándose en los números de teléfono de tu agenda, te sugerirá posibles contactos.
+Te registras con tu número de teléfono y Quicksy, basándose en los números de teléfono de tu agenda, te sugerirá automáticamente posibles contactos.
 
-Quicksy es un cliente Jabber completo que te permite comunicarte con cualquier usuario en cualquier servidor público. Asimismo, puedes contactar a los usuarios de Quicksy desde el exterior simplemente agregando +phonenumber@quicksy.im a tu lista de contactos.
+Bajo el capó, Quicksy es un cliente Jabber completamente funcional que te permite comunicarte con cualquier usuario en cualquier servidor público que federé. Asimismo, los usuarios de Quicksy pueden ser contactados desde el exterior simplemente agregando +número de teléfono@quicksy.im a tu lista de contactos.
 
-Aparte de la sincronización de contactos, la interfaz de usuario se parece lo más posible a Conversations. Esto permite que los usuarios puedan migrar de Quicksy a Conversations sin tener que volver a aprender cómo funciona la aplicación.
+Aparte de la sincronización de contactos, la interfaz de usuario está diseñada para ser lo más parecida posible a la de Conversations. Esto permite a los usuarios migrar eventualmente de Quicksy a Conversations sin tener que volver a aprender cómo funciona la aplicación.
 
-Los contactos sugeridos consisten en otros usuarios de Quicksy y usuarios habituales de Jabber/XMPP que han ingresado su ID de Jabber en el Directorio de Quicksy (https://quicksy.im/#get-listed).
+Los contactos sugeridos consisten en otros usuarios de Quicksy y usuarios regulares de Jabber/XMPP que han ingresado su ID Jabber en el Directorio Quicksy (https://quicksy.im/#get-listed).
 
-NOTA: Para ingresar (https://quicksy.im/enter/) tu ID de Jabber en Quicksy
-Directorio se requiere un único pago de registro.
+NOTA: Para ingresar tu ID Jabber en el Directorio Quicksy (https://quicksy.im/enter/), se requiere un pago de registro único.
 
-Lee la Política de privacidad (https://quicksy.im/#privacy) para obtener más información.
+Lee la Política de Privacidad para más información.
  
  
  
    
    @@ -0,0 +1,14 @@
+Quicksy on variatsioon populaarsest Jabber/XMPP kliendist Conversationsist, kuhu on lisandunud kontaktide automaatne tuvastus.
+
+Sa lisad liitumisel oma telefoninumbri ja Quicksyautomaatselt telefoniraamatus leiduvate numbrite alusel pakub sulle välja võimalikke suhtluspartnereid.
+
+Olemuselt on Quicksy tavaline täisfunktsionaalne Jabberi klient, mis lubab sul suhelda kasutajatega ükspuha missuguses muus avalikus XMPP/Jabberi serveris. Samamoodi saad ükspuha missuguse Quicksy kasutajaga suhelda lisades +telefoninumber@quicksy.im oma kontaktide loendisse.
+
+Kui kontaktide sünkroniseerimine välja arvata, siis kasutajaliides on teadlikult võimalikult sarnane Conversationsiga. See võimaldab soovi korral hiljem Quicksy asemel asuda kasutama Conversationsit ning olulist ümberõppimist pole vaja ette võtta.
+
+Suhtlusparterite soovitused leitakse teiste Quicksy kasutajate hulgast ja tavaliste Jabber/XMPP kasutajate seast, kes on oma Jabberi kasutajatunnuse lisanud Quicksy kataloogi (https://quicksy.im/#get-listed).
+
+MÄRKUS: Oma Jabberi kasutajatunnuse lisamine (https://quicksy.im/enter/) Quicksy kataloogi
+eeldab ühekordse registreerimistasu maksmist.
+
+Lisateavet leiad meie privaatsuspoliitkast (https://quicksy.im/#privacy).
  
  
  
    
    @@ -1 +1 @@
-Jabber/XMPP клиент с простым присоединением и простым нахождением контактов
+Клиент Jabber/XMPP с простым присоединением и нахождением контактов
  
  
  
    
    @@ -1,6 +1,6 @@
 Quicksy është një program me fillesë klientin popullor Jabber/XMPP, me zbulim të automatizuar kontaktesh.
 
-Regjistroheni me numrin tuaj të telefonit dhe Quicksy—baszuar në numrat e telefonave në librin tuaj të adresave—do t’ju sugjerojë automatikisht kontakte të mundshëm.
+Regjistroheni me numrin tuaj të telefonit dhe Quicksy—bazuar në numrat e telefonave në librin tuaj të adresave—do t’ju sugjerojë automatikisht kontakte të mundshëm.
 
 Nën kapak, Quicksy është një klient i plotë Jabber, që ju lejon të komunikoni me cilindo përdorues në cilindo shërbyes federimi publik. Në mënyrë të ngjashme, me përdoruesit në Quicksy mund të lidhesh që nga jashtë thjesht duke shtuar +phonenumber@quicksy.im te lista juaj e kontakteve.
 
  
  
  
    
    @@ -6,8 +6,8 @@ Quicksy 是流行的 Jabber/XMPP 客户端 Conversations 的衍生品,具有
 
 除了联系人同步之外,用户界面尽可能地接近 Conversations。让用户最终可以从 Quicksy 迁移到 Conversations,而无需重新了解应用程序的工作方式。
 
-建议的联系人包括其他 Quicksy 用户和在 Quicksy 目录(https://quicksy.im/#get-listed)中输入 Jabber ID 的普通 Jabber/XMPP 用户。
+建议的联系人包括其他 Quicksy 用户和在 Quicksy 目录 (https://quicksy.im/#get-listed) 中输入 Jabber ID 的普通 Jabber/XMPP 用户。
 
-注意:要在 Quicksy 目录中输入(https://quicksy.im/enter/)您的 Jabber ID 需要缴纳一次性注册费。
+注意:要加入 (https://quicksy.im/enter/) Quicksy 目录,您的 Jabber ID 需要缴纳一次性注册费。
 
-请阅读隐私政策(https://quicksy.im/#privacy)了解更多信息。
+请阅读隐私政策 (https://quicksy.im/#privacy) 了解更多信息。
  
  
  
    
    @@ -1,2 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
-<resources></resources>
+<resources>
+    <string name="pref_notification_grace_period_summary">Ajavahemik tegevuse tuvastamise järel mõnes sinu teises seadmes, peale mida Quicksy jääb vaikseks</string>
+    <string name="pref_never_send_crash_summary">Saates rakenduse kokkujooksmise korral arendajatele teavet, aitad sa vigu parandada ja Quicksy rakendust edasi arendada</string>
+    <string name="pref_broadcast_last_activity_summary">Luba kõikidel oma kontaktidel teada, kui kasutad Quicksyt</string>
+    <string name="huawei_protected_apps_summary">Kui soovid saada teavitusi ka siis, kui ekraan on välja lülitatud, pead Quicksy lisama kaitstud rakenduste loendisse.</string>
+    <string name="set_profile_picture">Quicksy profiilipilt</string>
+    <string name="not_available_in_your_country">Quicksy pole sinu riigis saadaval.</string>
+    <string name="unable_to_verify_server_identity">Serveri identiteedi verifitseerimine ei õnnestu.</string>
+    <string name="unknown_security_error">Tundmatu turvaviga.</string>
+    <string name="timeout_while_connecting_to_server">Ühenduse loomisel serveriga päring aegus.</string>
+</resources>
  
  
  
    
    @@ -3,9 +3,9 @@
     <string name="pref_notification_grace_period_summary">Время, на которое уведомления от Quicksy будут отключены после активности с другого устройства</string>
     <string name="pref_never_send_crash_summary">Отправляя отчёты об ошибках, вы помогаете в разработке Quicksy</string>
     <string name="pref_broadcast_last_activity_summary">Извещать собеседников, когда вы пользуетесь Quicksy</string>
-    <string name="huawei_protected_apps_summary">Чтобы продолжать получать уведомления, даже если экран выключен, вам необходимо добавить Quicksy в список защищенных приложений.</string>
+    <string name="huawei_protected_apps_summary">Чтобы продолжать получать уведомления даже при отключённом экране необходимо добавить Quicksy в список защищённых приложений.</string>
     <string name="set_profile_picture">Аватар Quicksy</string>
-    <string name="not_available_in_your_country">Quicksy недоступен в Вашем регионе.</string>
+    <string name="not_available_in_your_country">Quicksy недоступен в вашем регионе.</string>
     <string name="unable_to_verify_server_identity">Не удалось подтвердить сервер.</string>
     <string name="unknown_security_error">Неизвестная ошибка безопасности.</string>
     <string name="timeout_while_connecting_to_server">Время ожидания подключения к серверу вышло.</string>
  
  
  
    
    @@ -4,7 +4,7 @@
     <string name="pref_never_send_crash_summary">通过发送崩溃报告,您可以帮助 Quicksy 的持续开发</string>
     <string name="pref_broadcast_last_activity_summary">让您的所有联系人知道您最后使用 Quicksy 的时间</string>
     <string name="huawei_protected_apps_summary">为了在屏幕关闭后继续接收通知,您需要将 Quicksy 加入受保护的应用列表。</string>
-    <string name="set_profile_picture">Quicksy 个人资料图片</string>
+    <string name="set_profile_picture">Quicksy 个人资料照片</string>
     <string name="not_available_in_your_country">Quicksy 在您所在的国家/地区无法使用。</string>
     <string name="unable_to_verify_server_identity">无法验证服务器身份。</string>
     <string name="unknown_security_error">安全错误未知。</string>