diff --git a/.gitignore b/.gitignore
index c689a5f596c0de88111406e80b6dd412d679c9ad..e2091fa3d3a8d0519cc96bec4b09b6cf1afd8a65 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ src/quicksyPlaystore/res/values/push.xml
build/
captures/
signing.properties
+signing.managed.properties
# Ignore Gradle GUI config
gradle-app.setting
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 47cf9350fe02e027444399fd13d6edc6d0fbdaf6..e20272a2c0d9d548294b726f20f9f07e35338faf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+### Version 2.13.4
+
+* Fix minor regressions introduced with 2.13.1
+
+### Version 2.13.3
+
+* Provide easier access to 'Privacy Policy' on Play Store version (Quicksy and Conversations)
+* Remove address book integration on Play Store version of Conversations
+
+### Version 2.13.2
+
+* minor bug fixes
+* slight modifications in Quicksy onboard flow
+
### Version 2.13.1
* Support P2P file transfer via WebRTC data channels
diff --git a/build.gradle b/build.gradle
index 7b5fb8fa1f76e8af628599a10b295d64fa833622..570b0144fc7861ff0a07fd3811bf8b97b04a63cb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -175,10 +175,12 @@ android {
def appName = "Quicksy"
resValue "string", "app_name", appName
buildConfigField "String", "APP_NAME", "\"$appName\""
+ buildConfigField "String", "PRIVACY_POLICY", "\"https://quicksy.im/privacy.htm\""
}
conversations {
dimension "mode"
+ buildConfigField "String", "PRIVACY_POLICY", "\"https://conversations.im/privacy.html\""
}
cheogram {
@@ -189,6 +191,7 @@ android {
def appName = "Cheogram"
resValue "string", "app_name", appName
buildConfigField "String", "APP_NAME", "\"$appName\"";
+ buildConfigField "String", "PRIVACY_POLICY", "\"https://cheogram.com/android-privacy.html\""
}
playstore {
diff --git a/fastlane/metadata/android/de-DE/changelogs/4208804.txt b/fastlane/metadata/android/de-DE/changelogs/4208804.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b3e0925f47ef56a90f18280c611b30c17d2b9cf8
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/changelogs/4208804.txt
@@ -0,0 +1,3 @@
+* Unterstützung von P2P-Dateiübertragung über WebRTC-Datenkanäle
+* Behebung von Interoperabilitätsproblemen mit Bind 2.0 auf ejabberd
+* Bündelung von Let's Encrypt Root-Zertifikaten für Android <= 7
diff --git a/fastlane/metadata/android/de-DE/changelogs/4209004.txt b/fastlane/metadata/android/de-DE/changelogs/4209004.txt
new file mode 100644
index 0000000000000000000000000000000000000000..018d6e1eb2a530fe92fdf2fc6b3f992a4c04a6dd
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/changelogs/4209004.txt
@@ -0,0 +1,2 @@
+* kleinere Fehlerbehebungen
+* Geringfügige Änderungen beim Quicksy-Onboarding
diff --git a/fastlane/metadata/android/de-DE/changelogs/4209204.txt b/fastlane/metadata/android/de-DE/changelogs/4209204.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7658709d2b7bd6c310638da9e33b46ec6f2bf2d5
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/changelogs/4209204.txt
@@ -0,0 +1,2 @@
+* Einfacherer Zugang zu den Datenschutzbestimmungen in der Play Store-Version (Quicksy und Conversations)
+* Entfernen der Adressbuchintegration in der Play Store-Version von Conversations
diff --git a/fastlane/metadata/android/de-DE/changelogs/4209404.txt b/fastlane/metadata/android/de-DE/changelogs/4209404.txt
new file mode 100644
index 0000000000000000000000000000000000000000..bb5feb5855a56d705f7011e1caaf4a757ea2eb3f
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/changelogs/4209404.txt
@@ -0,0 +1 @@
+* Behebung kleinerer Schwierigkeiten, die mit 2.13.1 eingeführt wurden
diff --git a/fastlane/metadata/android/en-US/changelogs/4208304.txt b/fastlane/metadata/android/en-US/changelogs/4208804.txt
similarity index 100%
rename from fastlane/metadata/android/en-US/changelogs/4208304.txt
rename to fastlane/metadata/android/en-US/changelogs/4208804.txt
diff --git a/fastlane/metadata/android/en-US/changelogs/4209004.txt b/fastlane/metadata/android/en-US/changelogs/4209004.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b062bedcbba33c7a1bce8add4c9f71e03896e7e6
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/4209004.txt
@@ -0,0 +1,2 @@
+* minor bug fixes
+* slight modifications in Quicksy onboard flow
diff --git a/fastlane/metadata/android/en-US/changelogs/4209204.txt b/fastlane/metadata/android/en-US/changelogs/4209204.txt
new file mode 100644
index 0000000000000000000000000000000000000000..83a947d5400a8c9f9fc6176b34afd5111748b8c6
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/4209204.txt
@@ -0,0 +1,2 @@
+* Provide easier access to 'Privacy Policy' on Play Store version (Quicksy and Conversations)
+* Remove address book integration on Play Store version of Conversations
diff --git a/fastlane/metadata/android/en-US/changelogs/4209404.txt b/fastlane/metadata/android/en-US/changelogs/4209404.txt
new file mode 100644
index 0000000000000000000000000000000000000000..764f13c526783c8c5c7756642df22202d7fb9474
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/4209404.txt
@@ -0,0 +1 @@
+* Fix minor regressions introduced with 2.13.1
diff --git a/fastlane/metadata/android/es-ES/changelogs/4208804.txt b/fastlane/metadata/android/es-ES/changelogs/4208804.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0a3ef27b100e12cd41db7b8595ea330d5d179cc5
--- /dev/null
+++ b/fastlane/metadata/android/es-ES/changelogs/4208804.txt
@@ -0,0 +1,3 @@
+* Admite transferencia de archivos P2P a través del canal de datos WebRTC
+* Solucionar problemas de interoperabilidad con Bind 2.0 en ejabberd
+* Paquetes de certificado raíz Let's Encrypt para Android <= 7
diff --git a/fastlane/metadata/android/es-ES/changelogs/4209004.txt b/fastlane/metadata/android/es-ES/changelogs/4209004.txt
new file mode 100644
index 0000000000000000000000000000000000000000..700284fd2790dcf45b9b9f3acf7d7f88c04b8642
--- /dev/null
+++ b/fastlane/metadata/android/es-ES/changelogs/4209004.txt
@@ -0,0 +1,2 @@
+* Correcciones de errores menores
+* ligeras modificaciones en el flujo interno de Quicksy
diff --git a/fastlane/metadata/android/es-ES/changelogs/4209204.txt b/fastlane/metadata/android/es-ES/changelogs/4209204.txt
new file mode 100644
index 0000000000000000000000000000000000000000..097d427282d9fe21f5e88e570aa7828f183503c3
--- /dev/null
+++ b/fastlane/metadata/android/es-ES/changelogs/4209204.txt
@@ -0,0 +1,2 @@
+* Facilitar el acceso a la "Política de privacidad" en la versión de Play Store (Quicksy y Conversations).
+* Eliminar la integración de la libreta de direcciones en la versión Play Store de Conversations
diff --git a/fastlane/metadata/android/es-ES/changelogs/4209404.txt b/fastlane/metadata/android/es-ES/changelogs/4209404.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e6999f067da4c82d68084d36448a8de187b62693
--- /dev/null
+++ b/fastlane/metadata/android/es-ES/changelogs/4209404.txt
@@ -0,0 +1 @@
+* Se corrigieron problemas menores introducidos en 2.13.1
diff --git a/fastlane/metadata/android/gl-ES/changelogs/4208104.txt b/fastlane/metadata/android/gl-ES/changelogs/4208104.txt
index f4532b6797151479e6086a6f92774ffd41b78906..ff912c254d3de8359ac93eeb72aa5d4ddec03862 100644
--- a/fastlane/metadata/android/gl-ES/changelogs/4208104.txt
+++ b/fastlane/metadata/android/gl-ES/changelogs/4208104.txt
@@ -1,4 +1,4 @@
-* Acceso mais rápido á 'Mostrar código QR'
-* Soporte para a PEP Marcadores Nativos
+* Acceso mais rápido a 'Mostrar código QR'
+* Soporte para PEP Marcadores Nativos
* Engadido soporte para SDP Offer / Answer Model (usado por pasarelas SIP)
* Establecida a API de Android 14 como obxectivo
diff --git a/fastlane/metadata/android/gl-ES/changelogs/4208804.txt b/fastlane/metadata/android/gl-ES/changelogs/4208804.txt
new file mode 100644
index 0000000000000000000000000000000000000000..03aa6966c497e32eb13cc5811ecda08be0c6413b
--- /dev/null
+++ b/fastlane/metadata/android/gl-ES/changelogs/4208804.txt
@@ -0,0 +1,3 @@
+* Soporte para a transferencia de ficheiros P2P a través de canles de datos WebRTC
+* Arranxo dos problemas de interoperabilidade con Bind 2.0 en ejabberd
+* Paquete de certificados raiz Let's Encrypt para Android <=7
diff --git a/fastlane/metadata/android/gl-ES/changelogs/4209004.txt b/fastlane/metadata/android/gl-ES/changelogs/4209004.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d1f8b3e262dce6673b03c719f812c6efcc6c1de7
--- /dev/null
+++ b/fastlane/metadata/android/gl-ES/changelogs/4209004.txt
@@ -0,0 +1,2 @@
+* arranxos menores
+* pequenos cambios no primeiro incio de Quicksy
diff --git a/fastlane/metadata/android/gl-ES/changelogs/4209204.txt b/fastlane/metadata/android/gl-ES/changelogs/4209204.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4b743012392ab238cade7e4925536c25a6ea90cf
--- /dev/null
+++ b/fastlane/metadata/android/gl-ES/changelogs/4209204.txt
@@ -0,0 +1,2 @@
+* Acceso máis doado á 'Política de Privacidade' na versión da Play Store (Quicksy e Conversations)
+* Retirada a integración coa libreta de enderezo na versión de Conversations da Play Store
diff --git a/fastlane/metadata/android/it-IT/changelogs/4208804.txt b/fastlane/metadata/android/it-IT/changelogs/4208804.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7139e8caa47a24531d5a45bd4af78584ab059fc2
--- /dev/null
+++ b/fastlane/metadata/android/it-IT/changelogs/4208804.txt
@@ -0,0 +1,3 @@
+* Supporto per trasferimenti P2P di file via canali di dati WebRTC
+* Corretti problemi di interoperabilità con Bind 2.0 su ejabberd
+* Integra certificati root di Let’s Encrypt su Android <= 7
diff --git a/fastlane/metadata/android/it-IT/changelogs/4209004.txt b/fastlane/metadata/android/it-IT/changelogs/4209004.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6186e821010d0929a8d4ce55501ba420b263f4da
--- /dev/null
+++ b/fastlane/metadata/android/it-IT/changelogs/4209004.txt
@@ -0,0 +1,2 @@
+* correzioni minori
+* piccole modifiche nel flusso di configurazione di Quicksy
diff --git a/fastlane/metadata/android/uk/changelogs/4208804.txt b/fastlane/metadata/android/uk/changelogs/4208804.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6aa707982bce5300373dc1cf4b89240d81ae7a11
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/4208804.txt
@@ -0,0 +1,3 @@
+* Підтримка передачі файлів P2P через канали даних WebRTC
+* Виправлено проблеми сумісності з Bind 2.0 на ejabberd
+* Пакет кореневих сертифікатів Let's Encrypt для версій Android до 7-ї включно
diff --git a/fastlane/metadata/android/uk/changelogs/4209004.txt b/fastlane/metadata/android/uk/changelogs/4209004.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2863414a669692182f02ea97e57a5e080032ef7f
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/4209004.txt
@@ -0,0 +1,2 @@
+* Незначні виправлення помилок
+* Деякі зміни у процесі підключення до Quicksy
diff --git a/fastlane/metadata/android/uk/changelogs/4209204.txt b/fastlane/metadata/android/uk/changelogs/4209204.txt
new file mode 100644
index 0000000000000000000000000000000000000000..652ea89d81ef6099cd1cdf1e0f732e159e7b3120
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/4209204.txt
@@ -0,0 +1,2 @@
+* Простіший доступ до «Політики конфіденційності» у версії Play Store (Quicksy та Conversations)
+* Видалено інтеграцію адресної книги у версії Conversations для Play Store
diff --git a/fastlane/metadata/android/uk/changelogs/4209404.txt b/fastlane/metadata/android/uk/changelogs/4209404.txt
new file mode 100644
index 0000000000000000000000000000000000000000..90e81392dc9ff7e28b5168831de13fa05e72e173
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/4209404.txt
@@ -0,0 +1 @@
+* Виправлено незначні регресії, які з'явилися у версії 2.13.1
diff --git a/fastlane/metadata/android/zh-CN/changelogs/4208804.txt b/fastlane/metadata/android/zh-CN/changelogs/4208804.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b35be34f5dddbc6927ee92b469c6b1691880e9aa
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/changelogs/4208804.txt
@@ -0,0 +1,3 @@
+* 支持通过 WebRTC 数据通道进行 P2P 文件传输
+* 修复 ejabberd 上 Bind 2.0 的互操作性问题
+* 捆绑适用于 Android <= 7 的 Let’s Encrypt 根证书
diff --git a/fastlane/metadata/android/zh-CN/changelogs/4209004.txt b/fastlane/metadata/android/zh-CN/changelogs/4209004.txt
new file mode 100644
index 0000000000000000000000000000000000000000..da11d968f30a9bc0222bc798b06963b6dc3ed900
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/changelogs/4209004.txt
@@ -0,0 +1,2 @@
+* 修正了一些小错误
+* Quicksy 板载流程略有修改
diff --git a/fastlane/metadata/android/zh-CN/changelogs/4209204.txt b/fastlane/metadata/android/zh-CN/changelogs/4209204.txt
new file mode 100644
index 0000000000000000000000000000000000000000..37c544ff2fe4e159e18a192551923558286b292f
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/changelogs/4209204.txt
@@ -0,0 +1,2 @@
+* 在 Play 商店版本(Quicksy 和 Conversations)上提供对“隐私政策”的更轻松访问
+* 移除 Play 商店版本的 Conversations 上的通讯录集成
diff --git a/src/conversations/fastlane/metadata/android/zh-CN/full_description.txt b/src/conversations/fastlane/metadata/android/zh-CN/full_description.txt
index 085d211918bbb84e521fbe495bae713c8c1cef40..14b7bc66b348c17f59eade500fc303dee7121818 100644
--- a/src/conversations/fastlane/metadata/android/zh-CN/full_description.txt
+++ b/src/conversations/fastlane/metadata/android/zh-CN/full_description.txt
@@ -28,7 +28,7 @@ Conversations 适用于所有 XMPP 服务器。然而,XMPP 是一种可扩展
到目前为止,这些 XEP 是:
-* XEP-0065:SOCKS5 字节流(or mod_proxy65)。如果双方都在防火墙(NAT)后面,将用于传输文件。
+* XEP-0065:SOCKS5 字节流(或 mod_proxy65)。如果双方都在防火墙(NAT)后面,将用于传输文件。
* XEP-0163:个人事件协议(头像)
* XEP-0191:屏蔽命令可让您将垃圾消息发送者列入黑名单或屏蔽的联系人中,而不会将其从花名册中删除。
* XEP-0198:流管理允许 XMPP 在小规模网络中断和底层 TCP 连接发生变化时继续运行。
diff --git a/src/conversations/res/values-bn-rIN/strings.xml b/src/conversations/res/values-bn-rIN/strings.xml
index 382343a3710f2d64ebad7aceafdff27b3ea59dec..b5fceb87a4fac8e8680058677bf60c891096e8df 100644
--- a/src/conversations/res/values-bn-rIN/strings.xml
+++ b/src/conversations/res/values-bn-rIN/strings.xml
@@ -1,10 +1,12 @@
- XMPP সার্ভার নির্বাচন করুন
- conversations.im-ই ব্যবহার করা যাক
- নতুন অ্যকাউন্ট তৈরী করা যাক
- আপনার কি একটা XMPP অ্যকাউন্ট ইতিমধ্যে করা আছে? সেরকমটা হতেই পারে যদি এর আগে আপনি কোনো অন্য XMPP প্রোগ্রাম বা অ্যাপ ব্যবহার করে থাকেন। এই মুহুর্তে আরেকটা অ্যকাউন্ট তৈরী করা সম্ভব না।\nHint: মাঝে মাঝে ইমেল অ্যকাউন্ট খুললেও এরকম অ্যকাউন্ট নিজে থেকেই তৈরী হয়ে যায়।
- XMPP কোনো একটি নির্দিষ্ট সংস্থার উপরে নির্ভরশীল নয়। এই অ্যপটি আপনি যেকোনো সংস্থার XMPP সার্ভারের সাথে ব্যবহার করতে পারেন।\nমনে রাখবেন, সুধুমাত্র আপনার সুবিধার্থেই conversations.im -এ আপনার জন্যে একটি অ্যকাউন্ট তৈরী করে দেওয়া হয়েছে। Conversations অ্যপটি এই সার্ভারের সাথে সবথেকে বেশী কার্যকারী।
+ আপনার XMPP প্রোভাইডার নির্বাচন করুন
+ conversations.im ব্যবহার করুন
+ নতুন অ্যকাউন্ট তৈরী করুন
+ আপনার কি কোনও XMPP অ্যকাউন্ট ইতিমধ্যে করা আছে? সেরকমটা হতেই পারে যদি এর আগে আপনি কোনো অন্য XMPP প্রোগ্রাম বা অ্যাপ ব্যবহার করে থাকেন। যদি না করে থাকেন, তাহলে আপনি এখন একটি XMPP অ্যাকাউন্ট বানাতে পারেন।
+\nসূত্র: মাঝে মধ্যে কিছু ইমেল প্রোভাইডাররা XMPP অ্যাকাউন্ট দেয়।
+ XMPP কোনো একটি নির্দিষ্ট সংস্থার উপরে নির্ভরশীল নয়। এই অ্যপটি আপনি যেকোনো সংস্থার XMPP সার্ভারের সাথে ব্যবহার করতে পারেন।
+\nআপনার সুবিধার্থে conversations.im-এ আপনার জন্যে একটি অ্যকাউন্ট তৈরী করে দেওয়া সুবিধাজনক করা হয়েছে। Conversations অ্যপটি এই সার্ভারের সাথে সবথেকে বেশী কার্যকারী।
আপনাকে %1$s-এ আমন্ত্রিত করা হয়েছে। অ্যকাউন্ট তৈরী করার সময় আপনাকে সাহায্য করা হবে।\n%1$s ব্যবহার করলেও, অন্য সেবা-প্রদানকারী সংস্থার ব্যবহারকারীদের সাথে আপনি কথা বলতে পারবেন, আপনার সম্পূর্ণ XMPP অ্যড্রেস তাদেরকে বলে দিয়ে।
আপনাকে %1$s-এ নিমন্ত্রণ করা হয়েছে। একটি username-ও আপনার জন্যে নির্দিষ্ট করে রাখা হয়েছে। অ্যকাউন্ট তৈরী করার সময় আপনাকে সাহায্য করা হবে।\nঅন্য XMPP সেবা প্রদানকারী সংস্থার ব্যবহারকারীদের সাথে আপনিও কথা বলতে পারবেন, আপনার সম্পূর্ণ XMPP অ্যড্রেস তাদেরকে বলে দিয়ে।
আপনার নিমন্ত্রণপত্র, সার্ভার থেকে
diff --git a/src/free/AndroidManifest.xml b/src/free/AndroidManifest.xml
index be251c7647d5e638edf59f624019d58c92f2559b..b4970dc1e70fc43424d6001c008afe3da13e6190 100644
--- a/src/free/AndroidManifest.xml
+++ b/src/free/AndroidManifest.xml
@@ -3,5 +3,6 @@
xmlns:tools="http://schemas.android.com/tools">
-
+
+
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index cf8e6ff217cddceeeeb3555807bc3884b275c511..b0a8b3d5acfae957f16f046b5c554a08565bf558 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -5,8 +5,6 @@
-
-
diff --git a/src/main/java/eu/siacs/conversations/android/JabberIdContact.java b/src/main/java/eu/siacs/conversations/android/JabberIdContact.java
index 0b701d27ac34cded2c222279f664073cffaa1eb5..32d5a53e75e4397e56fa7cae611f9a4d7cc68d08 100644
--- a/src/main/java/eu/siacs/conversations/android/JabberIdContact.java
+++ b/src/main/java/eu/siacs/conversations/android/JabberIdContact.java
@@ -8,28 +8,39 @@ import android.os.Build;
import android.provider.ContactsContract;
import android.util.Log;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.services.QuickConversationsService;
+import eu.siacs.conversations.xmpp.Jid;
+
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.xmpp.Jid;
-
public class JabberIdContact extends AbstractPhoneContact {
- private static final String[] PROJECTION = new String[]{ContactsContract.Data._ID,
- ContactsContract.Data.DISPLAY_NAME,
- ContactsContract.Data.PHOTO_URI,
- ContactsContract.Data.LOOKUP_KEY,
- ContactsContract.CommonDataKinds.Im.DATA
- };
- private static final String SELECTION = ContactsContract.Data.MIMETYPE + "=? AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? or (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? and lower(" + ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL + ")=?))";
+ private static final String[] PROJECTION =
+ new String[] {
+ ContactsContract.Data._ID,
+ ContactsContract.Data.DISPLAY_NAME,
+ ContactsContract.Data.PHOTO_URI,
+ ContactsContract.Data.LOOKUP_KEY,
+ ContactsContract.CommonDataKinds.Im.DATA
+ };
+ private static final String SELECTION =
+ ContactsContract.Data.MIMETYPE
+ + "=? AND ("
+ + ContactsContract.CommonDataKinds.Im.PROTOCOL
+ + "=? or ("
+ + ContactsContract.CommonDataKinds.Im.PROTOCOL
+ + "=? and lower("
+ + ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL
+ + ")=?))";
private static final String[] SELECTION_ARGS = {
- ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE,
- String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER),
- String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM),
- "xmpp"
+ ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE,
+ String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER),
+ String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM),
+ "xmpp"
};
private final Jid jid;
@@ -37,8 +48,12 @@ public class JabberIdContact extends AbstractPhoneContact {
private JabberIdContact(Cursor cursor) throws IllegalArgumentException {
super(cursor);
try {
- this.jid = Jid.of(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)));
- } catch (IllegalArgumentException | NullPointerException e) {
+ this.jid =
+ Jid.of(
+ cursor.getString(
+ cursor.getColumnIndexOrThrow(
+ ContactsContract.CommonDataKinds.Im.DATA)));
+ } catch (final IllegalArgumentException | NullPointerException e) {
throw new IllegalArgumentException(e);
}
}
@@ -47,11 +62,21 @@ public class JabberIdContact extends AbstractPhoneContact {
return jid;
}
- public static Map load(Context context) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
+ public static Map load(final Context context) {
+ if (!QuickConversationsService.isContactListIntegration(context)
+ || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && context.checkSelfPermission(Manifest.permission.READ_CONTACTS)
+ != PackageManager.PERMISSION_GRANTED)) {
return Collections.emptyMap();
}
- try (final Cursor cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, SELECTION_ARGS, null)) {
+ try (final Cursor cursor =
+ context.getContentResolver()
+ .query(
+ ContactsContract.Data.CONTENT_URI,
+ PROJECTION,
+ SELECTION,
+ SELECTION_ARGS,
+ null)) {
if (cursor == null) {
return Collections.emptyMap();
}
diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
index 1609922aab10fb30bb6b36341f7a4d5ecd9bf347..84bfae21eaae65553d08d4d168d6b7fe98949111 100644
--- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
@@ -963,7 +963,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
if (isTypeGroupChat) {
- if (packet.hasChild("subject") && !packet.hasChild("thread")) { // already know it has no body from above
+ if (packet.hasChild("subject") && !packet.hasChild("thread")) { // We already know it has no body per above
if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
final LocalizedContent subject = packet.findInternationalizedChildContentInDefaultNamespace("subject");
diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
index de0f4fbea6e6b7b2c66a7e7a612e09cec9f902d8..23c5f93e2d541b5f06395eecfcb5c7455ef00c80 100644
--- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
@@ -2,11 +2,6 @@ package eu.siacs.conversations.parser;
import android.util.Log;
-import org.openintents.openpgp.util.OpenPgpUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@@ -28,130 +23,162 @@ import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
-public class PresenceParser extends AbstractParser implements
- OnPresencePacketReceived {
+import org.openintents.openpgp.util.OpenPgpUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PresenceParser extends AbstractParser implements OnPresencePacketReceived {
+
+ public PresenceParser(XmppConnectionService service) {
+ super(service);
+ }
- public PresenceParser(XmppConnectionService service) {
- super(service);
- }
+ public void parseConferencePresence(PresencePacket packet, Account account) {
+ final Conversation conversation =
+ packet.getFrom() == null
+ ? null
+ : mXmppConnectionService.find(account, packet.getFrom().asBareJid());
+ if (conversation != null) {
+ final MucOptions mucOptions = conversation.getMucOptions();
+ boolean before = mucOptions.online();
+ int count = mucOptions.getUserCount();
+ final List tileUserBefore = mucOptions.getUsers(5);
+ processConferencePresence(packet, conversation);
+ final List tileUserAfter = mucOptions.getUsers(5);
+ if (!tileUserAfter.equals(tileUserBefore)) {
+ mXmppConnectionService.getAvatarService().clear(mucOptions);
+ }
+ if (before != mucOptions.online()
+ || (mucOptions.online() && count != mucOptions.getUserCount())) {
+ mXmppConnectionService.updateConversationUi();
+ } else if (mucOptions.online()) {
+ mXmppConnectionService.updateMucRosterUi();
+ }
+ }
+ }
- public void parseConferencePresence(PresencePacket packet, Account account) {
- final Conversation conversation = packet.getFrom() == null ? null : mXmppConnectionService.find(account, packet.getFrom().asBareJid());
- if (conversation != null) {
- final MucOptions mucOptions = conversation.getMucOptions();
- boolean before = mucOptions.online();
- int count = mucOptions.getUserCount();
- final List tileUserBefore = mucOptions.getUsers(5);
- processConferencePresence(packet, conversation);
- final List tileUserAfter = mucOptions.getUsers(5);
- if (!tileUserAfter.equals(tileUserBefore)) {
- mXmppConnectionService.getAvatarService().clear(mucOptions);
- }
- if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUserCount())) {
- mXmppConnectionService.updateConversationUi();
- } else if (mucOptions.online()) {
- mXmppConnectionService.updateMucRosterUi();
- }
- }
- }
+ private void processConferencePresence(PresencePacket packet, Conversation conversation) {
- private void processConferencePresence(PresencePacket packet, Conversation conversation) {
- final Account account = conversation.getAccount();
- final MucOptions mucOptions = conversation.getMucOptions();
- final Jid jid = conversation.getAccount().getJid();
- final Jid from = packet.getFrom();
- if (!from.isBareJid()) {
- final String type = packet.getAttribute("type");
- final Element x = packet.findChild("x", Namespace.MUC_USER);
- final Element nick = packet.findChild("nick", Namespace.NICK);
- Element hats = packet.findChild("hats", "urn:xmpp:hats:0");
- if (hats == null) {
- hats = packet.findChild("hats", "xmpp:prosody.im/protocol/hats:1");
- }
- if (hats == null) hats = new Element("hats", "urn:xmpp:hats:0");
- final Element occupantId = packet.findChild("occupant-id", "urn:xmpp:occupant-id:0");
- Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update"));
- final List codes = getStatusCodes(x);
- if (type == null) {
- if (x != null) {
- Element item = x.findChild("item");
- if (item != null && !from.isBareJid()) {
- mucOptions.setError(MucOptions.Error.NONE);
- MucOptions.User user = parseItem(conversation, item, from, occupantId, nick == null ? null : nick.getContent(), hats);
- if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || (codes.contains(MucOptions.STATUS_CODE_ROOM_CREATED) && jid.equals(InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"))))) {
- if (mucOptions.setOnline()) {
- mXmppConnectionService.getAvatarService().clear(mucOptions);
- }
- if (mucOptions.setSelf(user)) {
- Log.d(Config.LOGTAG,"role or affiliation changed");
- mXmppConnectionService.databaseBackend.updateConversation(conversation);
- }
- mXmppConnectionService.persistSelfNick(user);
- invokeRenameListener(mucOptions, true);
- }
- boolean isNew = mucOptions.updateUser(user);
- final AxolotlService axolotlService = conversation.getAccount().getAxolotlService();
- Contact contact = user.getContact();
- if (isNew
- && user.getRealJid() != null
- && mucOptions.isPrivateAndNonAnonymous()
- && (contact == null || !contact.mutualPresenceSubscription())
- && axolotlService.hasEmptyDeviceList(user.getRealJid())) {
- axolotlService.fetchDeviceIds(user.getRealJid());
- }
- if (codes.contains(MucOptions.STATUS_CODE_ROOM_CREATED) && mucOptions.autoPushConfiguration()) {
- Log.d(Config.LOGTAG,account.getJid().asBareJid()
- +": room '"
- +mucOptions.getConversation().getJid().asBareJid()
- +"' created. pushing default configuration");
- mXmppConnectionService.pushConferenceConfiguration(mucOptions.getConversation(),
- IqGenerator.defaultChannelConfiguration(),
- null);
- }
- if (mXmppConnectionService.getPgpEngine() != null) {
- Element signed = packet.findChild("x", "jabber:x:signed");
- if (signed != null) {
- Element status = packet.findChild("status");
- String msg = status == null ? "" : status.getContent();
- long keyId = mXmppConnectionService.getPgpEngine().fetchKeyId(mucOptions.getAccount(), msg, signed.getContent());
- if (keyId != 0) {
- user.setPgpKeyId(keyId);
- }
- }
- }
- if (avatar != null) {
- avatar.owner = from;
- if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
- if (user.setAvatar(avatar)) {
- mXmppConnectionService.getAvatarService().clear(user);
- }
- if (user.getRealJid() != null) {
- final Contact c = conversation.getAccount().getRoster().getContact(user.getRealJid());
- c.setAvatar(avatar);
- mXmppConnectionService.syncRoster(conversation.getAccount());
- mXmppConnectionService.getAvatarService().clear(c);
- mXmppConnectionService.updateRosterUi();
- }
- } else if (mXmppConnectionService.isDataSaverDisabled()) {
- mXmppConnectionService.fetchAvatar(mucOptions.getAccount(), avatar);
- }
- }
- }
- }
- } else if (type.equals("unavailable")) {
- final boolean fullJidMatches = from.equals(mucOptions.getSelf().getFullJid());
- if (x.hasChild("destroy") && fullJidMatches) {
- Element destroy = x.findChild("destroy");
- final Jid alternate = destroy == null ? null : InvalidJid.getNullForInvalid(destroy.getAttributeAsJid("jid"));
- mucOptions.setError(MucOptions.Error.DESTROYED);
- if (alternate != null) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": muc destroyed. alternate location " + alternate);
- }
- } else if (codes.contains(MucOptions.STATUS_CODE_SHUTDOWN) && fullJidMatches) {
- mucOptions.setError(MucOptions.Error.SHUTDOWN);
- } else if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE)) {
- if (codes.contains(MucOptions.STATUS_CODE_TECHNICAL_REASONS)) {
+ final Account account = conversation.getAccount();
+ final MucOptions mucOptions = conversation.getMucOptions();
+ final Jid jid = conversation.getAccount().getJid();
+ final Jid from = packet.getFrom();
+ if (!from.isBareJid()) {
+ final String type = packet.getAttribute("type");
+ final Element x = packet.findChild("x", Namespace.MUC_USER);
+ final Element nick = packet.findChild("nick", Namespace.NICK);
+ Element hats = packet.findChild("hats", "urn:xmpp:hats:0");
+ if (hats == null) {
+ hats = packet.findChild("hats", "xmpp:prosody.im/protocol/hats:1");
+ }
+ if (hats == null) hats = new Element("hats", "urn:xmpp:hats:0");
+ final Element occupantId = packet.findChild("occupant-id", "urn:xmpp:occupant-id:0");
+ Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update"));
+ final List codes = getStatusCodes(x);
+ if (type == null) {
+ if (x != null) {
+ Element item = x.findChild("item");
+ if (item != null && !from.isBareJid()) {
+ mucOptions.setError(MucOptions.Error.NONE);
+ MucOptions.User user = parseItem(conversation, item, from, occupantId, nick == null ? null : nick.getContent(), hats);
+ if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || (codes.contains(MucOptions.STATUS_CODE_ROOM_CREATED) && jid.equals(InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"))))) {
+ if (mucOptions.setOnline()) {
+ mXmppConnectionService.getAvatarService().clear(mucOptions);
+ }
+ if (mucOptions.setSelf(user)) {
+ Log.d(Config.LOGTAG,"role or affiliation changed");
+ mXmppConnectionService.databaseBackend.updateConversation(conversation);
+ }
+ mXmppConnectionService.persistSelfNick(user);
+ invokeRenameListener(mucOptions, true);
+ }
+ boolean isNew = mucOptions.updateUser(user);
+ final AxolotlService axolotlService =
+ conversation.getAccount().getAxolotlService();
+ Contact contact = user.getContact();
+ if (isNew
+ && user.getRealJid() != null
+ && mucOptions.isPrivateAndNonAnonymous()
+ && (contact == null || !contact.mutualPresenceSubscription())
+ && axolotlService.hasEmptyDeviceList(user.getRealJid())) {
+ axolotlService.fetchDeviceIds(user.getRealJid());
+ }
+ if (codes.contains(MucOptions.STATUS_CODE_ROOM_CREATED)
+ && mucOptions.autoPushConfiguration()) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": room '"
+ + mucOptions.getConversation().getJid().asBareJid()
+ + "' created. pushing default configuration");
+ mXmppConnectionService.pushConferenceConfiguration(
+ mucOptions.getConversation(),
+ IqGenerator.defaultChannelConfiguration(),
+ null);
+ }
+ if (mXmppConnectionService.getPgpEngine() != null) {
+ Element signed = packet.findChild("x", "jabber:x:signed");
+ if (signed != null) {
+ Element status = packet.findChild("status");
+ String msg = status == null ? "" : status.getContent();
+ long keyId =
+ mXmppConnectionService
+ .getPgpEngine()
+ .fetchKeyId(
+ mucOptions.getAccount(),
+ msg,
+ signed.getContent());
+ if (keyId != 0) {
+ user.setPgpKeyId(keyId);
+ }
+ }
+ }
+ if (avatar != null) {
+ avatar.owner = from;
+ if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
+ if (user.setAvatar(avatar)) {
+ mXmppConnectionService.getAvatarService().clear(user);
+ }
+ if (user.getRealJid() != null) {
+ final Contact c =
+ conversation
+ .getAccount()
+ .getRoster()
+ .getContact(user.getRealJid());
+ c.setAvatar(avatar);
+ mXmppConnectionService.syncRoster(conversation.getAccount());
+ mXmppConnectionService.getAvatarService().clear(c);
+ mXmppConnectionService.updateRosterUi();
+ }
+ } else if (mXmppConnectionService.isDataSaverDisabled()) {
+ mXmppConnectionService.fetchAvatar(mucOptions.getAccount(), avatar);
+ }
+ }
+ }
+ }
+ } else if (type.equals("unavailable")) {
+ final boolean fullJidMatches = from.equals(mucOptions.getSelf().getFullJid());
+ if (x.hasChild("destroy") && fullJidMatches) {
+ Element destroy = x.findChild("destroy");
+ final Jid alternate =
+ destroy == null
+ ? null
+ : InvalidJid.getNullForInvalid(
+ destroy.getAttributeAsJid("jid"));
+ mucOptions.setError(MucOptions.Error.DESTROYED);
+ if (alternate != null) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": muc destroyed. alternate location "
+ + alternate);
+ }
+ } else if (codes.contains(MucOptions.STATUS_CODE_SHUTDOWN) && fullJidMatches) {
+ mucOptions.setError(MucOptions.Error.SHUTDOWN);
+ } else if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE)) {
+ if (codes.contains(MucOptions.STATUS_CODE_TECHNICAL_REASONS)) {
final boolean wasOnline = mucOptions.online();
mucOptions.setError(MucOptions.Error.TECHNICAL_PROBLEMS);
Log.d(
@@ -164,238 +191,259 @@ public class PresenceParser extends AbstractParser implements
if (wasOnline) {
mXmppConnectionService.mucSelfPingAndRejoin(conversation);
}
- } else if (codes.contains(MucOptions.STATUS_CODE_KICKED)) {
- mucOptions.setError(MucOptions.Error.KICKED);
- } else if (codes.contains(MucOptions.STATUS_CODE_BANNED)) {
- mucOptions.setError(MucOptions.Error.BANNED);
- } else if (codes.contains(MucOptions.STATUS_CODE_LOST_MEMBERSHIP)) {
- mucOptions.setError(MucOptions.Error.MEMBERS_ONLY);
- } else if (codes.contains(MucOptions.STATUS_CODE_AFFILIATION_CHANGE)) {
- mucOptions.setError(MucOptions.Error.MEMBERS_ONLY);
- } else if (codes.contains(MucOptions.STATUS_CODE_SHUTDOWN)) {
- mucOptions.setError(MucOptions.Error.SHUTDOWN);
- } else if (!codes.contains(MucOptions.STATUS_CODE_CHANGED_NICK)) {
- mucOptions.setError(MucOptions.Error.UNKNOWN);
- Log.d(Config.LOGTAG, "unknown error in conference: " + packet);
- }
- } else if (!from.isBareJid()){
- Element item = x.findChild("item");
- if (item != null) {
+ } else if (codes.contains(MucOptions.STATUS_CODE_KICKED)) {
+ mucOptions.setError(MucOptions.Error.KICKED);
+ } else if (codes.contains(MucOptions.STATUS_CODE_BANNED)) {
+ mucOptions.setError(MucOptions.Error.BANNED);
+ } else if (codes.contains(MucOptions.STATUS_CODE_LOST_MEMBERSHIP)) {
+ mucOptions.setError(MucOptions.Error.MEMBERS_ONLY);
+ } else if (codes.contains(MucOptions.STATUS_CODE_AFFILIATION_CHANGE)) {
+ mucOptions.setError(MucOptions.Error.MEMBERS_ONLY);
+ } else if (codes.contains(MucOptions.STATUS_CODE_SHUTDOWN)) {
+ mucOptions.setError(MucOptions.Error.SHUTDOWN);
+ } else if (!codes.contains(MucOptions.STATUS_CODE_CHANGED_NICK)) {
+ mucOptions.setError(MucOptions.Error.UNKNOWN);
+ Log.d(Config.LOGTAG, "unknown error in conference: " + packet);
+ }
+ } else if (!from.isBareJid()) {
+ Element item = x.findChild("item");
+ if (item != null) {
mucOptions.updateUser(parseItem(conversation, item, from, occupantId, nick == null ? null : nick.getContent(), hats));
- }
- MucOptions.User user = mucOptions.deleteUser(from);
- if (user != null) {
- mXmppConnectionService.getAvatarService().clear(user);
- }
- }
- } else if (type.equals("error")) {
- final Element error = packet.findChild("error");
- if (error == null) {
- return;
- }
- if (error.hasChild("conflict")) {
- if (mucOptions.online()) {
- invokeRenameListener(mucOptions, false);
- } else {
- mucOptions.setError(MucOptions.Error.NICK_IN_USE);
- }
- } else if (error.hasChild("not-authorized")) {
- mucOptions.setError(MucOptions.Error.PASSWORD_REQUIRED);
- } else if (error.hasChild("forbidden")) {
- mucOptions.setError(MucOptions.Error.BANNED);
- } else if (error.hasChild("registration-required")) {
- mucOptions.setError(MucOptions.Error.MEMBERS_ONLY);
- } else if (error.hasChild("resource-constraint")) {
- mucOptions.setError(MucOptions.Error.RESOURCE_CONSTRAINT);
- } else if (error.hasChild("remote-server-timeout")) {
- mucOptions.setError(MucOptions.Error.REMOTE_SERVER_TIMEOUT);
- } else if (error.hasChild("gone")) {
- final String gone = error.findChildContent("gone");
- final Jid alternate;
- if (gone != null) {
- final XmppUri xmppUri = new XmppUri(gone);
- if (xmppUri.isValidJid()) {
- alternate = xmppUri.getJid();
- } else {
- alternate = null;
- }
- } else {
- alternate = null;
- }
- mucOptions.setError(MucOptions.Error.DESTROYED);
- if (alternate != null) {
- Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": muc destroyed. alternate location " + alternate);
- }
- } else {
- final String text = error.findChildContent("text");
- if (text != null && text.contains("attribute 'to'")) {
- if (mucOptions.online()) {
- invokeRenameListener(mucOptions, false);
- } else {
- mucOptions.setError(MucOptions.Error.INVALID_NICK);
- }
- } else {
- mucOptions.setError(MucOptions.Error.UNKNOWN);
- Log.d(Config.LOGTAG, "unknown error in conference: " + packet);
- }
- }
- }
- }
- }
+ }
+ MucOptions.User user = mucOptions.deleteUser(from);
+ if (user != null) {
+ mXmppConnectionService.getAvatarService().clear(user);
+ }
+ }
+ } else if (type.equals("error")) {
+ final Element error = packet.findChild("error");
+ if (error == null) {
+ return;
+ }
+ if (error.hasChild("conflict")) {
+ if (mucOptions.online()) {
+ invokeRenameListener(mucOptions, false);
+ } else {
+ mucOptions.setError(MucOptions.Error.NICK_IN_USE);
+ }
+ } else if (error.hasChild("not-authorized")) {
+ mucOptions.setError(MucOptions.Error.PASSWORD_REQUIRED);
+ } else if (error.hasChild("forbidden")) {
+ mucOptions.setError(MucOptions.Error.BANNED);
+ } else if (error.hasChild("registration-required")) {
+ mucOptions.setError(MucOptions.Error.MEMBERS_ONLY);
+ } else if (error.hasChild("resource-constraint")) {
+ mucOptions.setError(MucOptions.Error.RESOURCE_CONSTRAINT);
+ } else if (error.hasChild("remote-server-timeout")) {
+ mucOptions.setError(MucOptions.Error.REMOTE_SERVER_TIMEOUT);
+ } else if (error.hasChild("gone")) {
+ final String gone = error.findChildContent("gone");
+ final Jid alternate;
+ if (gone != null) {
+ final XmppUri xmppUri = new XmppUri(gone);
+ if (xmppUri.isValidJid()) {
+ alternate = xmppUri.getJid();
+ } else {
+ alternate = null;
+ }
+ } else {
+ alternate = null;
+ }
+ mucOptions.setError(MucOptions.Error.DESTROYED);
+ if (alternate != null) {
+ Log.d(
+ Config.LOGTAG,
+ conversation.getAccount().getJid().asBareJid()
+ + ": muc destroyed. alternate location "
+ + alternate);
+ }
+ } else {
+ final String text = error.findChildContent("text");
+ if (text != null && text.contains("attribute 'to'")) {
+ if (mucOptions.online()) {
+ invokeRenameListener(mucOptions, false);
+ } else {
+ mucOptions.setError(MucOptions.Error.INVALID_NICK);
+ }
+ } else {
+ mucOptions.setError(MucOptions.Error.UNKNOWN);
+ Log.d(Config.LOGTAG, "unknown error in conference: " + packet);
+ }
+ }
+ }
+ }
+ }
- private static void invokeRenameListener(final MucOptions options, boolean success) {
- if (options.onRenameListener != null) {
- if (success) {
- options.onRenameListener.onSuccess();
- } else {
- options.onRenameListener.onFailure();
- }
- options.onRenameListener = null;
- }
- }
+ private static void invokeRenameListener(final MucOptions options, boolean success) {
+ if (options.onRenameListener != null) {
+ if (success) {
+ options.onRenameListener.onSuccess();
+ } else {
+ options.onRenameListener.onFailure();
+ }
+ options.onRenameListener = null;
+ }
+ }
- private static List getStatusCodes(Element x) {
- List codes = new ArrayList<>();
- if (x != null) {
- for (Element child : x.getChildren()) {
- if (child.getName().equals("status")) {
- String code = child.getAttribute("code");
- if (code != null) {
- codes.add(code);
- }
- }
- }
- }
- return codes;
- }
+ private static List getStatusCodes(Element x) {
+ List codes = new ArrayList<>();
+ if (x != null) {
+ for (Element child : x.getChildren()) {
+ if (child.getName().equals("status")) {
+ String code = child.getAttribute("code");
+ if (code != null) {
+ codes.add(code);
+ }
+ }
+ }
+ }
+ return codes;
+ }
- private void parseContactPresence(final PresencePacket packet, final Account account) {
- final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator();
- final Jid from = packet.getFrom();
- if (from == null || from.equals(account.getJid())) {
- return;
- }
- final String type = packet.getAttribute("type");
- final Contact contact = account.getRoster().getContact(from);
- if (type == null) {
- final String resource = from.isBareJid() ? "" : from.getResource();
- Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update"));
- if (avatar != null && (!contact.isSelf() || account.getAvatar() == null)) {
- avatar.owner = from.asBareJid();
- if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
- if (avatar.owner.equals(account.getJid().asBareJid())) {
- account.setAvatar(avatar.getFilename());
- mXmppConnectionService.databaseBackend.updateAccount(account);
- mXmppConnectionService.getAvatarService().clear(account);
- mXmppConnectionService.updateConversationUi();
- mXmppConnectionService.updateAccountUi();
- } else {
- contact.setAvatar(avatar);
- mXmppConnectionService.syncRoster(account);
- mXmppConnectionService.getAvatarService().clear(contact);
- mXmppConnectionService.updateConversationUi();
- mXmppConnectionService.updateRosterUi();
- }
- } else if (mXmppConnectionService.isDataSaverDisabled()){
- mXmppConnectionService.fetchAvatar(account, avatar);
- }
- }
+ private void parseContactPresence(final PresencePacket packet, final Account account) {
+ final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator();
+ final Jid from = packet.getFrom();
+ if (from == null || from.equals(account.getJid())) {
+ return;
+ }
+ final String type = packet.getAttribute("type");
+ final Contact contact = account.getRoster().getContact(from);
+ if (type == null) {
+ final String resource = from.isBareJid() ? "" : from.getResource();
+ final Avatar avatar =
+ Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update"));
+ if (avatar != null && (!contact.isSelf() || account.getAvatar() == null)) {
+ avatar.owner = from.asBareJid();
+ if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
+ if (avatar.owner.equals(account.getJid().asBareJid())) {
+ account.setAvatar(avatar.getFilename());
+ mXmppConnectionService.databaseBackend.updateAccount(account);
+ mXmppConnectionService.getAvatarService().clear(account);
+ mXmppConnectionService.updateConversationUi();
+ mXmppConnectionService.updateAccountUi();
+ } else {
+ contact.setAvatar(avatar);
+ mXmppConnectionService.syncRoster(account);
+ mXmppConnectionService.getAvatarService().clear(contact);
+ mXmppConnectionService.updateConversationUi();
+ mXmppConnectionService.updateRosterUi();
+ }
+ } else if (mXmppConnectionService.isDataSaverDisabled()) {
+ mXmppConnectionService.fetchAvatar(account, avatar);
+ }
+ }
- if (mXmppConnectionService.isMuc(account, from)) {
- return;
- }
+ if (mXmppConnectionService.isMuc(account, from)) {
+ return;
+ }
- int sizeBefore = contact.getPresences().size();
+ final int sizeBefore = contact.getPresences().size();
- final String show = packet.findChildContent("show");
- final Element caps = packet.findChild("c", "http://jabber.org/protocol/caps");
- final String message = packet.findChildContent("status");
- final Presence presence = Presence.parse(show, caps, message);
- contact.updatePresence(resource, presence);
- if (presence.hasCaps()) {
- mXmppConnectionService.fetchCaps(account, from, presence);
- }
+ final String show = packet.findChildContent("show");
+ final Element caps = packet.findChild("c", "http://jabber.org/protocol/caps");
+ final String message = packet.findChildContent("status");
+ final Presence presence = Presence.parse(show, caps, message);
+ contact.updatePresence(resource, presence);
+ if (presence.hasCaps()) {
+ mXmppConnectionService.fetchCaps(account, from, presence);
+ }
- final Element idle = packet.findChild("idle", Namespace.IDLE);
- if (idle != null) {
- try {
- final String since = idle.getAttribute("since");
- contact.setLastseen(AbstractParser.parseTimestamp(since));
- contact.flagInactive();
- } catch (Throwable throwable) {
- if (contact.setLastseen(AbstractParser.parseTimestamp(packet))) {
- contact.flagActive();
- }
- }
- } else {
- if (contact.setLastseen(AbstractParser.parseTimestamp(packet))) {
- contact.flagActive();
- }
- }
+ final Element idle = packet.findChild("idle", Namespace.IDLE);
+ if (idle != null) {
+ try {
+ final String since = idle.getAttribute("since");
+ contact.setLastseen(AbstractParser.parseTimestamp(since));
+ contact.flagInactive();
+ } catch (Throwable throwable) {
+ if (contact.setLastseen(AbstractParser.parseTimestamp(packet))) {
+ contact.flagActive();
+ }
+ }
+ } else {
+ if (contact.setLastseen(AbstractParser.parseTimestamp(packet))) {
+ contact.flagActive();
+ }
+ }
- PgpEngine pgp = mXmppConnectionService.getPgpEngine();
- Element x = packet.findChild("x", "jabber:x:signed");
- if (pgp != null && x != null) {
- final String status = packet.findChildContent("status");
- final long keyId = pgp.fetchKeyId(account, status, x.getContent());
- if (keyId != 0 && contact.setPgpKeyId(keyId)) {
- Log.d(Config.LOGTAG,account.getJid().asBareJid()+": found OpenPGP key id for "+contact.getJid()+" "+OpenPgpUtils.convertKeyIdToHex(keyId));
- mXmppConnectionService.syncRoster(account);
- }
- }
- boolean online = sizeBefore < contact.getPresences().size();
- mXmppConnectionService.onContactStatusChanged.onContactStatusChanged(contact, online);
- } else if (type.equals("unavailable")) {
- if (contact.setLastseen(AbstractParser.parseTimestamp(packet,0L,true))) {
- contact.flagInactive();
- }
- if (from.isBareJid()) {
- contact.clearPresences();
- } else {
- contact.removePresence(from.getResource());
- }
- if (contact.getShownStatus() == Presence.Status.OFFLINE) {
- contact.flagInactive();
- }
- mXmppConnectionService.onContactStatusChanged.onContactStatusChanged(contact, false);
- } else if (type.equals("subscribe")) {
- if (contact.setPresenceName(packet.findChildContent("nick", Namespace.NICK))) {
- mXmppConnectionService.syncRoster(account);
- mXmppConnectionService.getAvatarService().clear(contact);
- }
- if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) {
- mXmppConnectionService.sendPresencePacket(account,
- mPresenceGenerator.sendPresenceUpdatesTo(contact));
- } else {
- contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST);
- final Conversation conversation = mXmppConnectionService.findOrCreateConversation(
- account, contact.getJid().asBareJid(), false, false);
- final String statusMessage = packet.findChildContent("status");
- if (statusMessage != null
- && !statusMessage.isEmpty()
- && conversation.countMessages() == 0) {
- conversation.add(new Message(
- conversation,
- statusMessage,
- Message.ENCRYPTION_NONE,
- Message.STATUS_RECEIVED
- ));
- }
- }
- }
- mXmppConnectionService.updateRosterUi();
- }
+ final PgpEngine pgp = mXmppConnectionService.getPgpEngine();
+ final Element x = packet.findChild("x", "jabber:x:signed");
+ if (pgp != null && x != null) {
+ final String status = packet.findChildContent("status");
+ final long keyId = pgp.fetchKeyId(account, status, x.getContent());
+ if (keyId != 0 && contact.setPgpKeyId(keyId)) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": found OpenPGP key id for "
+ + contact.getJid()
+ + " "
+ + OpenPgpUtils.convertKeyIdToHex(keyId));
+ mXmppConnectionService.syncRoster(account);
+ }
+ }
+ boolean online = sizeBefore < contact.getPresences().size();
+ mXmppConnectionService.onContactStatusChanged.onContactStatusChanged(contact, online);
+ } else if (type.equals("unavailable")) {
+ if (contact.setLastseen(AbstractParser.parseTimestamp(packet, 0L, true))) {
+ contact.flagInactive();
+ }
+ if (from.isBareJid()) {
+ contact.clearPresences();
+ } else {
+ contact.removePresence(from.getResource());
+ }
+ if (contact.getShownStatus() == Presence.Status.OFFLINE) {
+ contact.flagInactive();
+ }
+ mXmppConnectionService.onContactStatusChanged.onContactStatusChanged(contact, false);
+ } else if (type.equals("subscribe")) {
+ if (contact.isBlocked()) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": ignoring 'subscribe' presence from blocked "
+ + from);
+ return;
+ }
+ if (contact.setPresenceName(packet.findChildContent("nick", Namespace.NICK))) {
+ mXmppConnectionService.syncRoster(account);
+ mXmppConnectionService.getAvatarService().clear(contact);
+ }
+ if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) {
+ mXmppConnectionService.sendPresencePacket(
+ account, mPresenceGenerator.sendPresenceUpdatesTo(contact));
+ } else {
+ contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST);
+ final Conversation conversation =
+ mXmppConnectionService.findOrCreateConversation(
+ account, contact.getJid().asBareJid(), false, false);
+ final String statusMessage = packet.findChildContent("status");
+ if (statusMessage != null
+ && !statusMessage.isEmpty()
+ && conversation.countMessages() == 0) {
+ conversation.add(
+ new Message(
+ conversation,
+ statusMessage,
+ Message.ENCRYPTION_NONE,
+ Message.STATUS_RECEIVED));
+ }
+ }
+ }
+ mXmppConnectionService.updateRosterUi();
+ }
- @Override
- public void onPresencePacketReceived(Account account, PresencePacket packet) {
- if (packet.hasChild("x", Namespace.MUC_USER)) {
- this.parseConferencePresence(packet, account);
- } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
- this.parseConferencePresence(packet, account);
- } else if ("error".equals(packet.getAttribute("type")) && mXmppConnectionService.isMuc(account, packet.getFrom())) {
- this.parseConferencePresence(packet, account);
- } else {
- this.parseContactPresence(packet, account);
- }
- }
+ @Override
+ public void onPresencePacketReceived(Account account, PresencePacket packet) {
+ if (packet.hasChild("x", Namespace.MUC_USER)) {
+ this.parseConferencePresence(packet, account);
+ } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
+ this.parseConferencePresence(packet, account);
+ } else if ("error".equals(packet.getAttribute("type"))
+ && mXmppConnectionService.isMuc(account, packet.getFrom())) {
+ this.parseConferencePresence(packet, account);
+ } else {
+ this.parseContactPresence(packet, account);
+ }
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/services/AbstractQuickConversationsService.java b/src/main/java/eu/siacs/conversations/services/AbstractQuickConversationsService.java
index 5d6f8eee5090ae984653f5fdc4044da08e883623..8f6c3c1f2a1edf6c01d98a48b01ae8d888b45d0b 100644
--- a/src/main/java/eu/siacs/conversations/services/AbstractQuickConversationsService.java
+++ b/src/main/java/eu/siacs/conversations/services/AbstractQuickConversationsService.java
@@ -1,14 +1,22 @@
package eu.siacs.conversations.services;
+import android.Manifest;
+import android.content.Context;
import android.content.Intent;
-import android.os.Build;
+import android.content.pm.PackageManager;
+
+import com.google.common.collect.Iterables;
import eu.siacs.conversations.BuildConfig;
+import java.util.Arrays;
+
public abstract class AbstractQuickConversationsService {
+ public static final String SMS_RETRIEVED_ACTION =
+ "com.google.android.gms.auth.api.phone.SMS_RETRIEVED";
- public static final String SMS_RETRIEVED_ACTION = "com.google.android.gms.auth.api.phone.SMS_RETRIEVED";
+ private static Boolean declaredReadContacts = null;
protected final XmppConnectionService service;
@@ -30,6 +38,37 @@ public abstract class AbstractQuickConversationsService {
return "playstore".equals(BuildConfig.FLAVOR_distribution);
}
+ public static boolean isContactListIntegration(final Context context) {
+ if ("quicksy".equals(BuildConfig.FLAVOR_mode)) {
+ return true;
+ }
+ final var readContacts = AbstractQuickConversationsService.declaredReadContacts;
+ if (readContacts != null) {
+ return Boolean.TRUE.equals(readContacts);
+ }
+ AbstractQuickConversationsService.declaredReadContacts = hasDeclaredReadContacts(context);
+ return AbstractQuickConversationsService.declaredReadContacts;
+ }
+
+ private static boolean hasDeclaredReadContacts(final Context context) {
+ final String[] permissions;
+ try {
+ permissions =
+ context.getPackageManager()
+ .getPackageInfo(
+ context.getPackageName(), PackageManager.GET_PERMISSIONS)
+ .requestedPermissions;
+ } catch (final PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ return Iterables.any(
+ Arrays.asList(permissions), p -> p.equals(Manifest.permission.READ_CONTACTS));
+ }
+
+ public static boolean isQuicksyPlayStore() {
+ return isQuicksy() && isPlayStoreFlavor();
+ }
+
public abstract void signalAccountStateChange();
public abstract boolean isSynchronizing();
diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java
index 1a7e759252462640d898ad1a8bf40aac5efb3bd1..e524d82ed4b83a17b82988cdc7ab2a6f9d85136e 100644
--- a/src/main/java/eu/siacs/conversations/services/NotificationService.java
+++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java
@@ -125,6 +125,7 @@ public class NotificationService {
private long mLastNotification;
private static final String INCOMING_CALLS_NOTIFICATION_CHANNEL = "incoming_calls_channel";
+ private static final String MESSAGES_NOTIFICATION_CHANNEL = "messages";
private Ringtone currentlyPlayingRingtone = null;
private ScheduledFuture> vibrationFuture;
@@ -254,7 +255,7 @@ public class NotificationService {
final NotificationChannel messagesChannel =
new NotificationChannel(
- "messages",
+ MESSAGES_NOTIFICATION_CHANNEL,
c.getString(R.string.messages_channel_name),
NotificationManager.IMPORTANCE_HIGH);
messagesChannel.setShowBadge(true);
@@ -1208,7 +1209,7 @@ public class NotificationService {
final Builder mBuilder =
new NotificationCompat.Builder(
mXmppConnectionService,
- quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
+ quietHours ? "quiet_hours" : (notify ? MESSAGES_NOTIFICATION_CHANNEL : "silent_messages"));
final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
style.setBigContentTitle(
mXmppConnectionService
@@ -1274,157 +1275,157 @@ public class NotificationService {
private Builder buildSingleConversations(
final ArrayList messages, final boolean notify, final boolean quietHours) {
- final Builder mBuilder =
- new NotificationCompat.Builder(
- mXmppConnectionService,
- quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
- if (messages.size() >= 1) {
- final Conversation conversation = (Conversation) messages.get(0).getConversation();
- mBuilder.setLargeIcon(FileBackend.drawDrawable(
+ final var channel = quietHours ? "quiet_hours" : (notify ? MESSAGES_NOTIFICATION_CHANNEL : "silent_messages");
+ final Builder notificationBuilder =
+ new NotificationCompat.Builder(mXmppConnectionService, channel);
+ if (messages.isEmpty()) {
+ return notificationBuilder;
+ }
+ final Conversation conversation = (Conversation) messages.get(0).getConversation();
+ notificationBuilder.setLargeIcon(FileBackend.drawDrawable(
+ mXmppConnectionService
+ .getAvatarService()
+ .get(
+ conversation,
+ AvatarService.getSystemUiAvatarSize(mXmppConnectionService))));
+ notificationBuilder.setContentTitle(conversation.getName());
+ if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
+ int count = messages.size();
+ notificationBuilder.setContentText(
mXmppConnectionService
- .getAvatarService()
- .get(
- conversation,
- AvatarService.getSystemUiAvatarSize(mXmppConnectionService))));
- mBuilder.setContentTitle(conversation.getName());
- if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
- int count = messages.size();
- mBuilder.setContentText(
- mXmppConnectionService
- .getResources()
- .getQuantityString(R.plurals.x_messages, count, count));
+ .getResources()
+ .getQuantityString(R.plurals.x_messages, count, count));
+ } else {
+ final Message message;
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P
+ && (message = getImage(messages)) != null) {
+ modifyForImage(notificationBuilder, message, messages);
} else {
- Message message;
- // TODO starting with Android 9 we might want to put images in MessageStyle
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P
- && (message = getImage(messages)) != null) {
- modifyForImage(mBuilder, message, messages);
- } else {
- modifyForTextOnly(mBuilder, messages);
- }
- RemoteInput remoteInput =
- new RemoteInput.Builder("text_reply")
- .setLabel(
- UIHelper.getMessageHint(
- mXmppConnectionService, conversation))
- .build();
- PendingIntent markAsReadPendingIntent = createReadPendingIntent(conversation);
- NotificationCompat.Action markReadAction =
- new NotificationCompat.Action.Builder(
- R.drawable.ic_drafts_white_24dp,
- mXmppConnectionService.getString(R.string.mark_as_read),
- markAsReadPendingIntent)
- .setSemanticAction(
- NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
- .setShowsUserInterface(false)
- .build();
- final String replyLabel = mXmppConnectionService.getString(R.string.reply);
- final String lastMessageUuid = Iterables.getLast(messages).getUuid();
- final NotificationCompat.Action replyAction =
- new NotificationCompat.Action.Builder(
- R.drawable.ic_send_text_offline,
- replyLabel,
- createReplyIntent(conversation, lastMessageUuid, false))
- .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
- .setShowsUserInterface(false)
- .addRemoteInput(remoteInput)
- .build();
- final NotificationCompat.Action wearReplyAction =
+ modifyForTextOnly(notificationBuilder, messages);
+ }
+ RemoteInput remoteInput =
+ new RemoteInput.Builder("text_reply")
+ .setLabel(UIHelper.getMessageHint(mXmppConnectionService, conversation))
+ .build();
+ PendingIntent markAsReadPendingIntent = createReadPendingIntent(conversation);
+ NotificationCompat.Action markReadAction =
+ new NotificationCompat.Action.Builder(
+ R.drawable.ic_drafts_white_24dp,
+ mXmppConnectionService.getString(R.string.mark_as_read),
+ markAsReadPendingIntent)
+ .setSemanticAction(
+ NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
+ .setShowsUserInterface(false)
+ .build();
+ final String replyLabel = mXmppConnectionService.getString(R.string.reply);
+ final String lastMessageUuid = Iterables.getLast(messages).getUuid();
+ final NotificationCompat.Action replyAction =
+ new NotificationCompat.Action.Builder(
+ R.drawable.ic_send_text_offline,
+ replyLabel,
+ createReplyIntent(conversation, lastMessageUuid, false))
+ .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
+ .setShowsUserInterface(false)
+ .addRemoteInput(remoteInput)
+ .build();
+ final NotificationCompat.Action wearReplyAction =
+ new NotificationCompat.Action.Builder(
+ R.drawable.ic_wear_reply,
+ replyLabel,
+ createReplyIntent(conversation, lastMessageUuid, true))
+ .addRemoteInput(remoteInput)
+ .build();
+ notificationBuilder.extend(
+ new NotificationCompat.WearableExtender().addAction(wearReplyAction));
+ int addedActionsCount = 1;
+ notificationBuilder.addAction(markReadAction);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ notificationBuilder.addAction(replyAction);
+ ++addedActionsCount;
+ }
+
+ if (displaySnoozeAction(messages)) {
+ String label = mXmppConnectionService.getString(R.string.snooze);
+ PendingIntent pendingSnoozeIntent = createSnoozeIntent(conversation);
+ NotificationCompat.Action snoozeAction =
new NotificationCompat.Action.Builder(
- R.drawable.ic_wear_reply,
- replyLabel,
- createReplyIntent(conversation, lastMessageUuid, true))
- .addRemoteInput(remoteInput)
+ R.drawable.ic_notifications_paused_white_24dp,
+ label,
+ pendingSnoozeIntent)
.build();
- mBuilder.extend(
- new NotificationCompat.WearableExtender().addAction(wearReplyAction));
- int addedActionsCount = 1;
- mBuilder.addAction(markReadAction);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- mBuilder.addAction(replyAction);
- ++addedActionsCount;
- }
-
- if (displaySnoozeAction(messages)) {
- String label = mXmppConnectionService.getString(R.string.snooze);
- PendingIntent pendingSnoozeIntent = createSnoozeIntent(conversation);
- NotificationCompat.Action snoozeAction =
- new NotificationCompat.Action.Builder(
- R.drawable.ic_notifications_paused_white_24dp,
- label,
- pendingSnoozeIntent)
- .build();
- mBuilder.addAction(snoozeAction);
- ++addedActionsCount;
- }
- if (addedActionsCount < 3) {
- final Message firstLocationMessage = getFirstLocationMessage(messages);
- if (firstLocationMessage != null) {
- final PendingIntent pendingShowLocationIntent =
- createShowLocationIntent(firstLocationMessage);
- if (pendingShowLocationIntent != null) {
- final String label =
- mXmppConnectionService
- .getResources()
- .getString(R.string.show_location);
- NotificationCompat.Action locationAction =
- new NotificationCompat.Action.Builder(
- R.drawable.ic_room_white_24dp,
- label,
- pendingShowLocationIntent)
- .build();
- mBuilder.addAction(locationAction);
- ++addedActionsCount;
- }
- }
- }
- if (addedActionsCount < 3) {
- Message firstDownloadableMessage = getFirstDownloadableMessage(messages);
- if (firstDownloadableMessage != null) {
- String label =
+ notificationBuilder.addAction(snoozeAction);
+ ++addedActionsCount;
+ }
+ if (addedActionsCount < 3) {
+ final Message firstLocationMessage = getFirstLocationMessage(messages);
+ if (firstLocationMessage != null) {
+ final PendingIntent pendingShowLocationIntent =
+ createShowLocationIntent(firstLocationMessage);
+ if (pendingShowLocationIntent != null) {
+ final String label =
mXmppConnectionService
.getResources()
- .getString(
- R.string.download_x_file,
- UIHelper.getFileDescriptionString(
- mXmppConnectionService,
- firstDownloadableMessage));
- PendingIntent pendingDownloadIntent =
- createDownloadIntent(firstDownloadableMessage);
- NotificationCompat.Action downloadAction =
+ .getString(R.string.show_location);
+ NotificationCompat.Action locationAction =
new NotificationCompat.Action.Builder(
- R.drawable.ic_file_download_white_24dp,
+ R.drawable.ic_room_white_24dp,
label,
- pendingDownloadIntent)
+ pendingShowLocationIntent)
.build();
- mBuilder.addAction(downloadAction);
+ notificationBuilder.addAction(locationAction);
++addedActionsCount;
}
}
}
- final ShortcutInfoCompat info;
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
- final Contact contact = conversation.getContact();
- final Uri systemAccount = contact.getSystemAccount();
- if (systemAccount != null) {
- mBuilder.addPerson(systemAccount.toString());
+ if (addedActionsCount < 3) {
+ Message firstDownloadableMessage = getFirstDownloadableMessage(messages);
+ if (firstDownloadableMessage != null) {
+ String label =
+ mXmppConnectionService
+ .getResources()
+ .getString(
+ R.string.download_x_file,
+ UIHelper.getFileDescriptionString(
+ mXmppConnectionService,
+ firstDownloadableMessage));
+ PendingIntent pendingDownloadIntent =
+ createDownloadIntent(firstDownloadableMessage);
+ NotificationCompat.Action downloadAction =
+ new NotificationCompat.Action.Builder(
+ R.drawable.ic_file_download_white_24dp,
+ label,
+ pendingDownloadIntent)
+ .build();
+ notificationBuilder.addAction(downloadAction);
+ ++addedActionsCount;
}
- info = mXmppConnectionService.getShortcutService().getShortcutInfoCompat(contact);
- } else {
- info =
- mXmppConnectionService
- .getShortcutService()
- .getShortcutInfoCompat(conversation.getMucOptions());
}
- mBuilder.setWhen(conversation.getLatestMessage().getTimeSent());
- mBuilder.setSmallIcon(R.drawable.ic_notification);
- mBuilder.setDeleteIntent(createDeleteIntent(conversation));
- mBuilder.setContentIntent(createContentIntent(conversation));
- if (mXmppConnectionService.getAccounts().size() > 1) {
- mBuilder.setSubText(conversation.getAccount().getJid().asBareJid().toString());
+ }
+ final ShortcutInfoCompat info;
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ final Contact contact = conversation.getContact();
+ final Uri systemAccount = contact.getSystemAccount();
+ if (systemAccount != null) {
+ notificationBuilder.addPerson(systemAccount.toString());
}
-
- mBuilder.setShortcutInfo(info);
+ info = mXmppConnectionService.getShortcutService().getShortcutInfoCompat(contact);
+ } else {
+ info =
+ mXmppConnectionService
+ .getShortcutService()
+ .getShortcutInfoCompat(conversation.getMucOptions());
+ }
+ notificationBuilder.setWhen(conversation.getLatestMessage().getTimeSent());
+ notificationBuilder.setSmallIcon(R.drawable.ic_notification);
+ notificationBuilder.setDeleteIntent(createDeleteIntent(conversation));
+ notificationBuilder.setContentIntent(createContentIntent(conversation));
+ if (mXmppConnectionService.getAccounts().size() > 1) {
+ notificationBuilder.setSubText(conversation.getAccount().getJid().asBareJid().toString());
+ }
+ if (channel.equals(MESSAGES_NOTIFICATION_CHANNEL)) {
+ // when do not want 'customized' notifications for silent notifications in their
+ // respective channels
+ notificationBuilder.setShortcutInfo(info);
if (Build.VERSION.SDK_INT >= 30) {
mXmppConnectionService
.getSystemService(ShortcutManager.class)
@@ -1432,7 +1433,7 @@ public class NotificationService {
// mBuilder.setBubbleMetadata(new NotificationCompat.BubbleMetadata.Builder(info.getId()).build());
}
}
- return mBuilder;
+ return notificationBuilder;
}
private void modifyForImage(
diff --git a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java
index c821aa1e7838a9e162c520a498229b609aa00be1..bfa1785f11f855e11c1c9531331c672ce20f2431 100644
--- a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java
+++ b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java
@@ -12,6 +12,7 @@ import android.preference.PreferenceManager;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
@@ -85,11 +86,11 @@ public class UnifiedPushBroker {
service.sendPresencePacket(account, presence);
}
- public Optional renewUnifiedPushEndpoints() {
- return renewUnifiedPushEndpoints(null);
+ public void renewUnifiedPushEndpoints() {
+ renewUnifiedPushEndpoints(null);
}
- public Optional renewUnifiedPushEndpoints(final PushTargetMessenger pushTargetMessenger) {
+ public Optional renewUnifiedPushEndpoints(@Nullable final PushTargetMessenger pushTargetMessenger) {
final Optional transportOptional = getTransport();
if (transportOptional.isPresent()) {
final Transport transport = transportOptional.get();
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index 03eb47dfc2a7f879372bb174a5d57f471fc37b14..1ad12ce40761f626a2f556099c6e8a81c02c057d 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -1090,28 +1090,45 @@ public class XmppConnectionService extends Service {
manageAccountConnectionStates(ACTION_INTERNAL_PING, null);
}
- private synchronized void manageAccountConnectionStates(final String action, final Bundle extras) {
- Log.d(Config.LOGTAG, "manageAccountConnectionStates: " + action);
+ private synchronized void manageAccountConnectionStates(
+ final String action, final Bundle extras) {
final String pushedAccountHash = extras == null ? null : extras.getString("account");
- final boolean interactive = Arrays.asList(ACTION_TRY_AGAIN).contains(action);
+ final boolean interactive = java.util.Objects.equals(ACTION_TRY_AGAIN, action);
WakeLockHelper.acquire(wakeLock);
- boolean pingNow = ConnectivityManager.CONNECTIVITY_ACTION.equals(action) || (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0 && ACTION_POST_CONNECTIVITY_CHANGE.equals(action));
+ boolean pingNow =
+ ConnectivityManager.CONNECTIVITY_ACTION.equals(action)
+ || (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0
+ && ACTION_POST_CONNECTIVITY_CHANGE.equals(action));
final HashSet pingCandidates = new HashSet<>();
- final String androidId = PhoneHelper.getAndroidId(this);
+ final String androidId = pushedAccountHash == null ? null : PhoneHelper.getAndroidId(this);
for (final Account account : accounts) {
- final boolean pushWasMeantForThisAccount = CryptoHelper.getAccountFingerprint(account, androidId).equals(pushedAccountHash);
- pingNow |= processAccountState(account,
- interactive,
- "ui".equals(action),
- pushWasMeantForThisAccount,
- pingCandidates);
+ final boolean pushWasMeantForThisAccount =
+ androidId != null
+ && CryptoHelper.getAccountFingerprint(account, androidId)
+ .equals(pushedAccountHash);
+ pingNow |=
+ processAccountState(
+ account,
+ interactive,
+ "ui".equals(action),
+ pushWasMeantForThisAccount,
+ pingCandidates);
}
if (pingNow) {
- for (Account account : pingCandidates) {
+ for (final Account account : pingCandidates) {
final boolean lowTimeout = isInLowPingTimeoutMode(account);
account.getXmppConnection().sendPing();
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + " send ping (action=" + action + ",lowTimeout=" + lowTimeout + ")");
- scheduleWakeUpCall(lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT, account.getUuid().hashCode());
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + " send ping (action="
+ + action
+ + ",lowTimeout="
+ + lowTimeout
+ + ")");
+ scheduleWakeUpCall(
+ lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT,
+ account.getUuid().hashCode());
}
long msToMucPing = (mLastMucPing + (Config.PING_MAX_INTERVAL * 2000L)) - SystemClock.elapsedRealtime();
if (msToMucPing <= 0) {
@@ -1484,7 +1501,11 @@ public class XmppConnectionService extends Service {
restoreFromDatabase();
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
+ if (QuickConversationsService.isContactListIntegration(this)
+ && (Build.VERSION.SDK_INT < Build.VERSION_CODES.M
+ || ContextCompat.checkSelfPermission(
+ this, Manifest.permission.READ_CONTACTS)
+ == PackageManager.PERMISSION_GRANTED)) {
startContactObserver();
}
FILE_OBSERVER_EXECUTOR.execute(fileBackend::deleteHistoricAvatarPath);
@@ -1692,11 +1713,17 @@ public class XmppConnectionService extends Service {
Log.d(Config.LOGTAG, "ForegroundService: " + (status ? "on" : "off"));
}
- private void startForegroundOrCatch(final int id, final Notification notification, boolean needMic) {
+ private void startForegroundOrCatch(
+ final int id, final Notification notification, final boolean requireMicrophone) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- int foregroundServiceType;
- if (getSystemService(PowerManager.class)
+ final int foregroundServiceType;
+ if (requireMicrophone
+ && ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
+ == PackageManager.PERMISSION_GRANTED) {
+ foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
+ Log.d(Config.LOGTAG, "defaulting to microphone foreground service type");
+ } else if (getSystemService(PowerManager.class)
.isIgnoringBatteryOptimizations(getPackageName())) {
foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
} else if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
@@ -1707,14 +1734,9 @@ public class XmppConnectionService extends Service {
foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
} else {
foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
- Log.w(Config.LOGTAG,"falling back to special use foreground service type");
+ Log.w(Config.LOGTAG, "falling back to special use foreground service type");
}
- if (needMic && ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
- == PackageManager.PERMISSION_GRANTED) {
- foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
- }
-
startForeground(id, notification, foregroundServiceType);
} else {
startForeground(id, notification);
diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
index 7089164d810b280cd1f57a37d8c7775ac70026a2..4af310bfb3668cf014da297fd057f864f5e256e8 100644
--- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -62,6 +62,7 @@ import eu.siacs.conversations.entities.Bookmark;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.services.AbstractQuickConversationsService;
+import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
import eu.siacs.conversations.ui.adapter.MediaAdapter;
@@ -140,13 +141,13 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
private void checkContactPermissionAndShowAddDialog() {
if (hasContactsPermission()) {
showAddToPhoneBookDialog();
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ } 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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (QuickConversationsService.isContactListIntegration(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED;
} else {
return true;
@@ -612,18 +613,30 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
}
}
- private void onBadgeClick(View view) {
- final Uri systemAccount = contact.getSystemAccount();
- if (systemAccount == null) {
- checkContactPermissionAndShowAddDialog();
- } else {
- final Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(systemAccount);
- try {
- startActivity(intent);
- } catch (final ActivityNotFoundException e) {
- Toast.makeText(this, R.string.no_application_found_to_view_contact, Toast.LENGTH_SHORT).show();
+ private void onBadgeClick(final View view) {
+ if (QuickConversationsService.isContactListIntegration(this)) {
+ final Uri systemAccount = contact.getSystemAccount();
+ if (systemAccount == null) {
+ checkContactPermissionAndShowAddDialog();
+ } else {
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(systemAccount);
+ try {
+ startActivity(intent);
+ } catch (final ActivityNotFoundException e) {
+ Toast.makeText(
+ this,
+ R.string.no_application_found_to_view_contact,
+ Toast.LENGTH_SHORT)
+ .show();
+ }
}
+ } else {
+ Toast.makeText(
+ this,
+ R.string.contact_list_integration_not_available,
+ Toast.LENGTH_SHORT)
+ .show();
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java
index 227da6194ea896fa24a75426d2fb4c1466956503..3bb92fba23f499917e02166fc3aff09bf220dcb8 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java
@@ -60,6 +60,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
+import eu.siacs.conversations.BuildConfig;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.FragmentConversationsOverviewBinding;
@@ -67,6 +68,7 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Conversational;
import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.ui.adapter.ConversationAdapter;
import eu.siacs.conversations.ui.interfaces.OnConversationArchived;
import eu.siacs.conversations.ui.interfaces.OnConversationSelected;
diff --git a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java
index 5823484b2207b5f24fdbbf578a2cf8bad90db7c6..39a02904eed1c2d130610518a0cf5dbca83191f1 100644
--- a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java
@@ -19,6 +19,8 @@ import android.widget.Toast;
import androidx.databinding.DataBindingUtil;
+import com.google.common.collect.ImmutableSet;
+
import java.io.File;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
@@ -27,6 +29,7 @@ import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.Set;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
@@ -101,6 +104,16 @@ public class RecordingActivity extends Activity implements View.OnClickListener
return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
}
+ private static final Set AAC_SENSITIVE_DEVICES =
+ new ImmutableSet.Builder()
+ .add("FP4") // Fairphone 4 https://codeberg.org/monocles/monocles_chat/issues/133
+ .add("ONEPLUS A6000") // OnePlus 6 https://github.com/iNPUTmice/Conversations/issues/4329
+ .add("ONEPLUS A6003") // OnePlus 6 https://github.com/iNPUTmice/Conversations/issues/4329
+ .add("ONEPLUS A6010") // OnePlus 6T https://codeberg.org/monocles/monocles_chat/issues/133
+ .add("ONEPLUS A6013") // OnePlus 6T https://codeberg.org/monocles/monocles_chat/issues/133
+ .add("Pixel 4a") // Pixel 4a https://github.com/iNPUTmice/Conversations/issues/4223
+ .build();
+
private boolean startRecording() {
mRecorder = new MediaRecorder();
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
@@ -114,9 +127,16 @@ public class RecordingActivity extends Activity implements View.OnClickListener
} else if ("mpeg4".equals(userChosenCodec) || !Config.USE_OPUS_VOICE_MESSAGES) {
outputFormat = MediaRecorder.OutputFormat.MPEG_4;
mRecorder.setOutputFormat(outputFormat);
- mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
- mRecorder.setAudioEncodingBitRate(96000);
- mRecorder.setAudioSamplingRate(22050);
+ if (AAC_SENSITIVE_DEVICES.contains(Build.MODEL)) {
+ // Changing these three settings for AAC sensitive devices might lead to sporadically truncated (cut-off) voice messages.
+ mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);
+ mRecorder.setAudioSamplingRate(24_000);
+ mRecorder.setAudioEncodingBitRate(28_000);
+ } else {
+ mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+ mRecorder.setAudioSamplingRate(22_050);
+ mRecorder.setAudioEncodingBitRate(64_000);
+ }
} else {
outputFormat = MediaRecorder.OutputFormat.THREE_GPP;
mRecorder.setOutputFormat(outputFormat);
diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java
index 3cd197deda24a233f1424582ac0d68ee0de4111f..0006e5cdd580331c567e57bef3ab7ba90a8471f5 100644
--- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java
@@ -13,6 +13,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.opengl.GLException;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -881,8 +882,12 @@ public class RtpSessionActivity extends XmppActivity
surfaceViewRenderer.setVisibility(View.VISIBLE);
try {
surfaceViewRenderer.init(requireRtpConnection().getEglBaseContext(), null);
- } catch (final IllegalStateException e) {
- // Log.d(Config.LOGTAG, "SurfaceViewRenderer was already initialized");
+ } catch (final IllegalStateException ignored) {
+ // SurfaceViewRenderer was already initialized
+ } catch (final RuntimeException e) {
+ if (Throwables.getRootCause(e) instanceof GLException glException) {
+ Log.w(Config.LOGTAG, "could not set up hardware renderer", glException);
+ }
}
surfaceViewRenderer.setEnableHardwareScaler(true);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
index aab1fc189c3a90774ab09290b70f89b6ca71fa3f..edd89621bfa06bc0cbc51ca743d837ee2019e13f 100644
--- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
@@ -6,6 +6,7 @@ import android.app.Dialog;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
@@ -75,6 +76,7 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
+import eu.siacs.conversations.BuildConfig;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityStartConversationBinding;
@@ -108,6 +110,8 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public class StartConversationActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, CreatePrivateGroupChatDialog.CreateConferenceDialogListener, JoinConferenceDialog.JoinConferenceDialogListener, SwipeRefreshLayout.OnRefreshListener, CreatePublicChannelDialog.CreatePublicChannelDialogListener {
+ private static final String PREF_KEY_CONTACT_INTEGRATION_CONSENT = "contact_list_integration_consent";
+
public static final String EXTRA_INVITE_URI = "eu.siacs.conversations.invite_uri";
private final int REQUEST_SYNC_CONTACTS = 0x28cf;
@@ -718,11 +722,15 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
+ public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.start_conversation, menu);
AccountUtils.showHideMenuItems(menu);
- MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline);
- MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code);
+ final MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline);
+ final MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code);
+ final MenuItem privacyPolicyMenuItem = menu.findItem(R.id.action_privacy_policy);
+ privacyPolicyMenuItem.setVisible(
+ BuildConfig.PRIVACY_POLICY != null
+ && QuickConversationsService.isPlayStoreFlavor());
qrCodeScanMenuItem.setVisible(isCameraFeatureAvailable());
if (QuickConversationsService.isQuicksy()) {
menuHideOffline.setVisible(false);
@@ -851,39 +859,96 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}
private void askForContactsPermissions() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
+ if (QuickConversationsService.isContactListIntegration(this)
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
+ != PackageManager.PERMISSION_GRANTED) {
if (mRequestedContactsPermission.compareAndSet(false, true)) {
- if (QuickConversationsService.isQuicksy() || shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
+ final String consent =
+ PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
+ .getString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, null);
+ final boolean requiresConsent =
+ (QuickConversationsService.isQuicksy()
+ || QuickConversationsService.isPlayStoreFlavor())
+ && !"agreed".equals(consent);
+ if (requiresConsent && "declined".equals(consent)) {
+ Log.d(Config.LOGTAG,"not asking for contacts permission because consent has been declined");
+ return;
+ }
+ if (requiresConsent
+ || shouldShowRequestPermissionRationale(
+ Manifest.permission.READ_CONTACTS)) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
final AtomicBoolean requestPermission = new AtomicBoolean(false);
- builder.setTitle(R.string.sync_with_contacts);
- builder.setMessage(getString(R.string.sync_with_contacts_long, getString(R.string.app_name)));
- builder.setPositiveButton(R.string.next, (dialog, which) -> {
- if (requestPermission.compareAndSet(false, true)) {
- requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
- }
- });
- builder.setOnDismissListener(dialog -> {
- if (QuickConversationsService.isConversations() && requestPermission.compareAndSet(false, true)) {
- requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
- }
- });
if (QuickConversationsService.isQuicksy()) {
- builder.setNegativeButton(R.string.decline, null);
+ builder.setTitle(R.string.quicksy_wants_your_consent);
+ builder.setMessage(
+ Html.fromHtml(
+ getString(R.string.sync_with_contacts_quicksy_static)));
+ } else {
+ builder.setTitle(R.string.sync_with_contacts);
+ builder.setMessage(
+ getString(
+ R.string.sync_with_contacts_long,
+ getString(R.string.app_name)));
+ }
+ @StringRes int confirmButtonText;
+ if (requiresConsent) {
+ confirmButtonText = R.string.agree_and_continue;
+ } else {
+ confirmButtonText = R.string.next;
}
- builder.setCancelable(QuickConversationsService.isQuicksy());
+ builder.setPositiveButton(
+ confirmButtonText,
+ (dialog, which) -> {
+ if (requiresConsent) {
+ PreferenceManager.getDefaultSharedPreferences(
+ getApplicationContext())
+ .edit()
+ .putString(
+ PREF_KEY_CONTACT_INTEGRATION_CONSENT, "agreed")
+ .apply();
+ }
+ if (requestPermission.compareAndSet(false, true)) {
+ requestPermissions(
+ new String[] {Manifest.permission.READ_CONTACTS},
+ REQUEST_SYNC_CONTACTS);
+ }
+ });
+ if (requiresConsent) {
+ builder.setNegativeButton(R.string.decline, (dialog, which) -> PreferenceManager.getDefaultSharedPreferences(
+ getApplicationContext())
+ .edit()
+ .putString(
+ PREF_KEY_CONTACT_INTEGRATION_CONSENT, "declined")
+ .apply());
+ } else {
+ builder.setOnDismissListener(
+ dialog -> {
+ if (requestPermission.compareAndSet(false, true)) {
+ requestPermissions(
+ new String[] {
+ Manifest.permission.READ_CONTACTS
+ },
+ REQUEST_SYNC_CONTACTS);
+ }
+ });
+ }
+ builder.setCancelable(requiresConsent);
final AlertDialog dialog = builder.create();
- dialog.setCanceledOnTouchOutside(QuickConversationsService.isQuicksy());
- dialog.setOnShowListener(dialogInterface -> {
- final TextView tv = dialog.findViewById(android.R.id.message);
- if (tv != null) {
- tv.setMovementMethod(LinkMovementMethod.getInstance());
- }
- });
+ dialog.setCanceledOnTouchOutside(requiresConsent);
+ dialog.setOnShowListener(
+ dialogInterface -> {
+ final TextView tv = dialog.findViewById(android.R.id.message);
+ if (tv != null) {
+ tv.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+ });
dialog.show();
} else {
- requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
+ requestPermissions(
+ new String[] {Manifest.permission.READ_CONTACTS},
+ REQUEST_SYNC_CONTACTS);
}
}
}
@@ -919,8 +984,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
@Override
protected void onBackendConnected() {
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
+ if (QuickConversationsService.isContactListIntegration(this)
+ && (Build.VERSION.SDK_INT < Build.VERSION_CODES.M
+ || checkSelfPermission(Manifest.permission.READ_CONTACTS)
+ == PackageManager.PERMISSION_GRANTED)) {
xmppConnectionService.getQuickConversationsService().considerSyncBackground(false);
}
if (mPostponedActivityResult != null) {
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index 2ca05f819f13b82d168f0322148b13c8c008bb24..a51bcfc06c6671bdbc4aded78dea9e81a6dbed01 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -69,6 +69,7 @@ import java.util.List;
import java.util.PriorityQueue;
import java.util.concurrent.RejectedExecutionException;
+import eu.siacs.conversations.BuildConfig;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
@@ -438,6 +439,9 @@ public abstract class XmppActivity extends ActionBarActivity {
case R.id.action_settings:
startActivity(new Intent(this, SettingsActivity.class));
break;
+ case R.id.action_privacy_policy:
+ openPrivacyPolicy();
+ break;
case R.id.action_accounts:
AccountUtils.launchManageAccounts(this);
break;
@@ -454,6 +458,20 @@ public abstract class XmppActivity extends ActionBarActivity {
return super.onOptionsItemSelected(item);
}
+ private void openPrivacyPolicy() {
+ if (BuildConfig.PRIVACY_POLICY == null) {
+ return;
+ }
+ final var viewPolicyIntent = new Intent(Intent.ACTION_VIEW);
+ viewPolicyIntent.setData(Uri.parse(BuildConfig.PRIVACY_POLICY));
+ try {
+ startActivity(viewPolicyIntent);
+ } catch (final ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.no_application_found_to_open_link, Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+
public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) {
final Contact contact = conversation.getContact();
if (contact.showInRoster() || contact.isSelf()) {
diff --git a/src/main/java/eu/siacs/conversations/ui/text/FixedURLSpan.java b/src/main/java/eu/siacs/conversations/ui/text/FixedURLSpan.java
index 64debcf3e6be9a8d418118e15b7b2d9e029c53b1..f1165e6eadbec1b29e0d386c7e397017a6101f13 100644
--- a/src/main/java/eu/siacs/conversations/ui/text/FixedURLSpan.java
+++ b/src/main/java/eu/siacs/conversations/ui/text/FixedURLSpan.java
@@ -90,9 +90,7 @@ public class FixedURLSpan extends URLSpan {
}
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
- }
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
//intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
try {
context.startActivity(intent);
diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java
index 4f65df5f3dd5f995cc63a184bd915629d4396869..08c8d73886afb8f6b500a7ae9e49e13d75c94226 100644
--- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java
+++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java
@@ -68,6 +68,7 @@ public final class MimeUtils {
// by guessExtensionFromMimeType.
add("application/andrew-inset", "ez");
add("application/dsptype", "tsp");
+ add("application/json", "json");
add("application/epub+zip", "epub");
add("application/gpx+xml", "gpx");
add("application/hta", "hta");
diff --git a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java
index 9ff4925780945aec300c4edb6a298c9d1220fa93..98924a26214c2ec03a33f43c2987e08f155237ae 100644
--- a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java
@@ -10,34 +10,37 @@ import android.os.Build;
import android.provider.ContactsContract.Profile;
import android.provider.Settings;
+import com.google.common.base.Strings;
+
+import eu.siacs.conversations.services.QuickConversationsService;
+
public class PhoneHelper {
@SuppressLint("HardwareIds")
- public static String getAndroidId(Context context) {
+ public static String getAndroidId(final Context context) {
return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
}
- public static Uri getProfilePictureUri(Context context) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
- && context.checkSelfPermission(Manifest.permission.READ_CONTACTS)
- != PackageManager.PERMISSION_GRANTED) {
+ public static Uri getProfilePictureUri(final Context context) {
+ if (!QuickConversationsService.isContactListIntegration(context)
+ || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && context.checkSelfPermission(Manifest.permission.READ_CONTACTS)
+ != PackageManager.PERMISSION_GRANTED)) {
return null;
}
final String[] projection = new String[] {Profile._ID, Profile.PHOTO_URI};
- final Cursor cursor;
- try {
- cursor =
- context.getContentResolver()
- .query(Profile.CONTENT_URI, projection, null, null, null);
- } catch (Throwable e) {
- return null;
- }
- if (cursor == null) {
- return null;
+ try (final Cursor cursor =
+ context.getContentResolver()
+ .query(Profile.CONTENT_URI, projection, null, null, null)) {
+ if (cursor != null && cursor.moveToFirst()) {
+ final var photoUri = cursor.getString(1);
+ if (Strings.isNullOrEmpty(photoUri)) {
+ return null;
+ }
+ return Uri.parse(photoUri);
+ }
}
- final String uri = cursor.moveToFirst() ? cursor.getString(1) : null;
- cursor.close();
- return uri == null ? null : Uri.parse(uri);
+ return null;
}
public static boolean isEmulator() {
diff --git a/src/main/java/eu/siacs/conversations/utils/Resolver.java b/src/main/java/eu/siacs/conversations/utils/Resolver.java
index 3fdf3304f5579c29aca79e6864c5a4f57c300094..236a9b41c6ac78f85b1434cf9ffb54de605b60f9 100644
--- a/src/main/java/eu/siacs/conversations/utils/Resolver.java
+++ b/src/main/java/eu/siacs/conversations/utils/Resolver.java
@@ -277,15 +277,15 @@ public class Resolver {
threads[2].interrupt();
synchronized (results) {
Collections.sort(results);
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + results.toString());
- return new ArrayList<>(results);
+ Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + results);
+ return results;
}
} else {
threads[2].join();
synchronized (fallbackResults) {
Collections.sort(fallbackResults);
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + fallbackResults.toString());
- return new ArrayList<>(fallbackResults);
+ Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + fallbackResults);
+ return fallbackResults;
}
}
} catch (InterruptedException e) {
@@ -371,7 +371,7 @@ public class Resolver {
}
private static List resolveNoSrvRecords(DnsName dnsName, boolean withCnames) {
- List results = new ArrayList<>();
+ final List results = new ArrayList<>();
try {
ResolverResult aResult = resolveWithFallback(dnsName, A.class);
for (A a : aResult.getAnswersOrEmptySet()) {
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index 02726486e6dd4f82bd962611483a1ee843b2823b..fcc837c856d9446bc3c63b2e6244e67bb16bf49d 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -116,8 +116,50 @@ import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket;
+
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.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 {
private static final int PACKET_IQ = 0;
@@ -300,7 +342,8 @@ public class XmppConnection implements Runnable {
mXmppConnectionService.resetSendingToWaiting(account);
}
Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": connecting");
- features.encryptionEnabled = false;
+ this.loginInfo = null;
+ this.features.encryptionEnabled = false;
this.inSmacksSession = false;
this.quickStartInProgress = false;
this.isBound = false;
@@ -353,12 +396,13 @@ public class XmppConnection implements Runnable {
}
} else {
final String domain = account.getServer();
- final List results;
+ final List results = new ArrayList<>();
final boolean hardcoded = extended && !account.getHostname().isEmpty();
if (hardcoded) {
- results = Resolver.fromHardCoded(account.getHostname(), account.getPort());
+ results.addAll(
+ Resolver.fromHardCoded(account.getHostname(), account.getPort()));
} else {
- results = Resolver.resolve(domain);
+ results.addAll(Resolver.resolve(domain));
}
if (Thread.currentThread().isInterrupted()) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": Thread was interrupted");
@@ -388,12 +432,18 @@ public class XmppConnection implements Runnable {
final StreamId streamId = this.streamId;
final Resolver.Result resumeLocation = streamId == null ? null : streamId.location;
if (resumeLocation != null) {
- Log.d(Config.LOGTAG,account.getJid().asBareJid()+": injected resume location on position 0");
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": injected resume location on position 0");
results.add(0, resumeLocation);
}
final Resolver.Result seeOtherHost = this.seeOtherHostResolverResult;
if (seeOtherHost != null) {
- Log.d(Config.LOGTAG,account.getJid().asBareJid()+": injected see-other-host on position 0");
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": injected see-other-host on position 0");
results.add(0, seeOtherHost);
}
for (final Iterator iterator = results.iterator();
@@ -535,8 +585,7 @@ public class XmppConnection implements Runnable {
tagReader.setInputStream(socket.getInputStream());
tagWriter.beginDocument();
final boolean quickStart;
- if (socket instanceof SSLSocket) {
- final SSLSocket sslSocket = (SSLSocket) socket;
+ if (socket instanceof SSLSocket sslSocket) {
SSLSockets.log(account, sslSocket);
quickStart = establishStream(SSLSockets.version(sslSocket));
} else {
@@ -546,7 +595,16 @@ public class XmppConnection implements Runnable {
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
- final boolean success = tag != null && tag.isStart("stream", Namespace.STREAMS);
+ if (tag == null) {
+ return false;
+ }
+ final boolean success = tag.isStart("stream", Namespace.STREAMS);
+ if (success) {
+ final var from = tag.getAttribute("from");
+ if (from == null || !from.equals(account.getServer())) {
+ throw new StateChangingException(Account.State.HOST_UNKNOWN);
+ }
+ }
if (success && quickStart) {
this.quickStartInProgress = true;
}
@@ -603,13 +661,18 @@ public class XmppConnection implements Runnable {
processStreamFeatures(nextTag);
} else if (nextTag.isStart("proceed", Namespace.TLS)) {
switchOverToTls();
+ } else if (nextTag.isStart("failure", Namespace.TLS)) {
+ throw new StateChangingException(Account.State.TLS_ERROR);
+ } else if (account.isOptionSet(Account.OPTION_REGISTER)
+ && nextTag.isStart("iq", Namespace.JABBER_CLIENT)) {
+ processIq(nextTag);
+ } else if (!isSecure() || 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("failure", Namespace.TLS)) {
- throw new StateChangingException(Account.State.TLS_ERROR);
} else if (nextTag.isStart("failure")) {
final Element failure = tagReader.readElement(nextTag);
processFailure(failure);
@@ -617,22 +680,33 @@ public class XmppConnection implements Runnable {
// two step sasl2 - we don’t support this yet
throw new StateChangingException(Account.State.INCOMPATIBLE_CLIENT);
} else if (nextTag.isStart("challenge")) {
- if (isSecure() && this.loginInfo != null) {
- final Element challenge = tagReader.readElement(nextTag);
- processChallenge(challenge);
- } else {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": received 'challenge on an unsecure connection");
- throw new StateChangingException(Account.State.INCOMPATIBLE_CLIENT);
- }
+ final Element challenge = tagReader.readElement(nextTag);
+ processChallenge(challenge);
+ } else if (!LoginInfo.isSuccess(this.loginInfo)) {
+ throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
+ } else if (this.streamId != null
+ && nextTag.isStart("resumed", Namespace.STREAM_MANAGEMENT)) {
+ final Element resumed = tagReader.readElement(nextTag);
+ processResumed(resumed);
+ } else if (nextTag.isStart("failed", Namespace.STREAM_MANAGEMENT)) {
+ final Element failed = tagReader.readElement(nextTag);
+ processFailed(failed, true);
+ } else if (nextTag.isStart("iq", Namespace.JABBER_CLIENT)) {
+ processIq(nextTag);
+ } else if (!isBound) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": server sent unexpected"
+ + nextTag.identifier());
+ throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
+ } else if (nextTag.isStart("message", Namespace.JABBER_CLIENT)) {
+ processMessage(nextTag);
+ } else if (nextTag.isStart("presence", Namespace.JABBER_CLIENT)) {
+ processPresence(nextTag);
} else if (nextTag.isStart("enabled", Namespace.STREAM_MANAGEMENT)) {
final Element enabled = tagReader.readElement(nextTag);
processEnabled(enabled);
- } else if (nextTag.isStart("resumed", Namespace.STREAM_MANAGEMENT)) {
- final Element resumed = tagReader.readElement(nextTag);
- processResumed(resumed);
} else if (nextTag.isStart("r", Namespace.STREAM_MANAGEMENT)) {
tagReader.readElement(nextTag);
if (Config.EXTENDED_SM_LOGGING) {
@@ -687,15 +761,6 @@ public class XmppConnection implements Runnable {
if (acknowledgedMessages) {
mXmppConnectionService.updateConversationUi();
}
- } else if (nextTag.isStart("failed", Namespace.STREAM_MANAGEMENT)) {
- final Element failed = tagReader.readElement(nextTag);
- processFailed(failed, true);
- } else if (nextTag.isStart("iq", Namespace.JABBER_CLIENT)) {
- processIq(nextTag);
- } else if (nextTag.isStart("message", Namespace.JABBER_CLIENT)) {
- processMessage(nextTag);
- } else if (nextTag.isStart("presence", Namespace.JABBER_CLIENT)) {
- processPresence(nextTag);
} else {
Log.e(
Config.LOGTAG,
@@ -726,8 +791,14 @@ public class XmppConnection implements Runnable {
} else {
throw new AssertionError("Missing implementation for " + version);
}
+ final LoginInfo currentLoginInfo = this.loginInfo;
+ if (currentLoginInfo == null || LoginInfo.isSuccess(currentLoginInfo)) {
+ throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
+ }
try {
- response.setContent(this.loginInfo.saslMechanism.getResponse(challenge.getContent(), sslSocketOrNull(socket)));
+ response.setContent(
+ currentLoginInfo.saslMechanism.getResponse(
+ challenge.getContent(), sslSocketOrNull(socket)));
} catch (final SaslMechanism.AuthenticationException e) {
// TODO: Send auth abort tag.
Log.e(Config.LOGTAG, e.toString());
@@ -758,9 +829,9 @@ public class XmppConnection implements Runnable {
throw new AssertionError("Missing implementation for " + version);
}
try {
- currentSaslMechanism.getResponse(challenge, sslSocketOrNull(socket));
+ currentLoginInfo.success(challenge, sslSocketOrNull(socket));
} catch (final SaslMechanism.AuthenticationException e) {
- Log.e(Config.LOGTAG, String.valueOf(e));
+ Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": authentication failure ", e);
throw new StateChangingException(Account.State.UNAUTHORIZED);
}
Log.d(
@@ -822,7 +893,10 @@ public class XmppConnection implements Runnable {
if (resumed != null && streamId != null) {
if (this.boundStreamFeatures != null) {
this.streamFeatures = this.boundStreamFeatures;
- Log.d(Config.LOGTAG, "putting previous stream features back in place: " + XmlHelper.printElementNames(this.boundStreamFeatures));
+ Log.d(
+ Config.LOGTAG,
+ "putting previous stream features back in place: "
+ + XmlHelper.printElementNames(this.boundStreamFeatures));
}
processResumed(resumed);
} else if (failed != null) {
@@ -842,7 +916,7 @@ public class XmppConnection implements Runnable {
processEnabled(streamManagementEnabled);
waitForDisco = true;
} else {
- //if we did not enable stream management in bind do it now
+ // if we did not enable stream management in bind do it now
waitForDisco = enableStreamManagement();
}
final boolean negotiatedCarbons;
@@ -874,13 +948,22 @@ public class XmppConnection implements Runnable {
tokenMechanism = null;
}
if (tokenMechanism != null && !Strings.isNullOrEmpty(token)) {
- if (ChannelBinding.priority(tokenMechanism.channelBinding) >= ChannelBindingMechanism.getPriority(currentSaslMechanism)) {
+ if (ChannelBinding.priority(tokenMechanism.channelBinding)
+ >= ChannelBindingMechanism.getPriority(currentSaslMechanism)) {
this.account.setFastToken(tokenMechanism, token);
Log.d(
Config.LOGTAG,
- account.getJid().asBareJid() + ": storing hashed token " + tokenMechanism);
+ account.getJid().asBareJid()
+ + ": storing hashed token "
+ + tokenMechanism);
} else {
- Log.d(Config.LOGTAG,account.getJid().asBareJid()+": not accepting hashed token "+ tokenMechanism.name()+" for log in mechanism "+currentSaslMechanism.getMechanism());
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": not accepting hashed token "
+ + tokenMechanism.name()
+ + " for log in mechanism "
+ + currentSaslMechanism.getMechanism());
this.account.resetFastToken();
}
} else if (this.hashTokenRequest != null) {
@@ -1033,7 +1116,9 @@ public class XmppConnection implements Runnable {
} else {
Log.d(
Config.LOGTAG,
- account.getJid().asBareJid() + ": stream management enabled. resume at: " + streamId.location);
+ account.getJid().asBareJid()
+ + ": stream management enabled. resume at: "
+ + streamId.location);
}
this.streamId = streamId;
this.stanzasReceived = 0;
@@ -1079,8 +1164,7 @@ public class XmppConnection implements Runnable {
Config.LOGTAG,
account.getJid().asBareJid() + ": resending " + failedStanzas.size() + " stanzas");
for (final AbstractAcknowledgeableStanza packet : failedStanzas) {
- if (packet instanceof MessagePacket) {
- MessagePacket message = (MessagePacket) packet;
+ if (packet instanceof MessagePacket message) {
mXmppConnectionService.markMessage(
account,
message.getTo().asBareJid(),
@@ -1143,8 +1227,7 @@ public class XmppConnection implements Runnable {
+ mStanzaQueue.keyAt(i));
}
final AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i);
- if (stanza instanceof MessagePacket && acknowledgedListener != null) {
- final MessagePacket packet = (MessagePacket) stanza;
+ if (stanza instanceof MessagePacket packet && acknowledgedListener != null) {
final String id = packet.getId();
final Jid to = packet.getTo();
if (id != null && to != null) {
@@ -1161,20 +1244,13 @@ public class XmppConnection implements Runnable {
private @NonNull Element processPacket(final Tag currentTag, final int packetType)
throws IOException {
- final Element element;
- switch (packetType) {
- case PACKET_IQ:
- element = new IqPacket();
- break;
- case PACKET_MESSAGE:
- element = new MessagePacket();
- break;
- case PACKET_PRESENCE:
- element = new PresencePacket();
- break;
- default:
- throw new AssertionError("Should never encounter invalid type");
- }
+ final Element element =
+ switch (packetType) {
+ case PACKET_IQ -> new IqPacket();
+ case PACKET_MESSAGE -> new MessagePacket();
+ case PACKET_PRESENCE -> new PresencePacket();
+ default -> throw new AssertionError("Should never encounter invalid type");
+ };
element.setAttributes(currentTag.getAttributes());
Tag nextTag = tagReader.readTag();
if (nextTag == null) {
@@ -1228,57 +1304,77 @@ public class XmppConnection implements Runnable {
+ "'");
return;
}
- if (packet instanceof JinglePacket) {
+ if (Thread.currentThread().isInterrupted()) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid() + "Not processing iq. Thread was interrupted");
+ return;
+ }
+ if (packet instanceof JinglePacket jinglePacket && isBound) {
if (this.jingleListener != null) {
- this.jingleListener.onJinglePacketReceived(account, (JinglePacket) packet);
+ this.jingleListener.onJinglePacketReceived(account, jinglePacket);
+ }
+ } else {
+ final var callback = getIqPacketReceivedCallback(packet);
+ if (callback == null) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid().toString()
+ + ": no callback registered for IQ from "
+ + packet.getFrom());
+ return;
+ }
+ final ScheduledFuture timeoutFuture = callback.second;
+ try {
+ if (timeoutFuture == null || timeoutFuture.cancel(false)) {
+ callback.first.onIqPacketReceived(account, packet);
+ }
+ } catch (final StateChangingError error) {
+ throw new StateChangingException(error.state);
+ }
+ }
+ }
+
+ private Pair getIqPacketReceivedCallback(final IqPacket stanza)
+ throws StateChangingException {
+ final boolean isRequest =
+ stanza.getType() == IqPacket.TYPE.GET || stanza.getType() == IqPacket.TYPE.SET;
+ if (isRequest) {
+ if (isBound) {
+ return new Pair<>(this.unregisteredIqListener, null);
+ } else {
+ throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
}
} else {
- OnIqPacketReceived callback = null;
synchronized (this.packetCallbacks) {
- final Pair> packetCallbackDuple =
- packetCallbacks.get(packet.getId());
- if (packetCallbackDuple != null) {
- ScheduledFuture timeoutFuture = packetCallbackDuple.second.second;
- // Packets to the server should have responses from the server
- if (packetCallbackDuple.first.toServer(account)) {
- if (packet.fromServer(account)) {
- if (timeoutFuture == null || timeoutFuture.cancel(false)) {
- callback = packetCallbackDuple.second.first;
- }
- packetCallbacks.remove(packet.getId());
- } else {
- Log.e(
- Config.LOGTAG,
- account.getJid().asBareJid().toString()
- + ": ignoring spoofed iq packet");
- }
+ final var pair = packetCallbacks.get(stanza.getId());
+ if (pair == null) {
+ return null;
+ }
+ if (pair.first.toServer(account)) {
+ if (stanza.fromServer(account)) {
+ packetCallbacks.remove(stanza.getId());
+ return pair.second;
} else {
- if (packet.getFrom() != null
- && packet.getFrom().equals(packetCallbackDuple.first.getTo())) {
- if (timeoutFuture == null || timeoutFuture.cancel(false)) {
- callback = packetCallbackDuple.second.first;
- }
- packetCallbacks.remove(packet.getId());
- } else {
- Log.e(
- Config.LOGTAG,
- account.getJid().asBareJid().toString()
- + ": ignoring spoofed iq packet");
- }
+ Log.e(
+ Config.LOGTAG,
+ account.getJid().asBareJid().toString()
+ + ": ignoring spoofed iq packet");
+ }
+ } else {
+ if (stanza.getFrom() != null && stanza.getFrom().equals(pair.first.getTo())) {
+ packetCallbacks.remove(stanza.getId());
+ return pair.second;
+ } else {
+ Log.e(
+ Config.LOGTAG,
+ account.getJid().asBareJid().toString()
+ + ": ignoring spoofed iq packet");
}
- } else if (packet.getType() == IqPacket.TYPE.GET
- || packet.getType() == IqPacket.TYPE.SET) {
- callback = this.unregisteredIqListener;
- }
- }
- if (callback != null) {
- try {
- callback.onIqPacketReceived(account, packet);
- } catch (StateChangingError error) {
- throw new StateChangingException(error.state);
}
}
}
+ return null;
}
private void processMessage(final Tag currentTag) throws IOException {
@@ -1293,11 +1389,18 @@ public class XmppConnection implements Runnable {
+ "'");
return;
}
+ if (Thread.currentThread().isInterrupted()) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + "Not processing message. Thread was interrupted");
+ return;
+ }
this.messageListener.onMessagePacketReceived(account, packet);
}
private void processPresence(final Tag currentTag) throws IOException {
- PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE);
+ final PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE);
if (!packet.valid()) {
Log.e(
Config.LOGTAG,
@@ -1308,6 +1411,13 @@ public class XmppConnection implements Runnable {
+ "'");
return;
}
+ if (Thread.currentThread().isInterrupted()) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + "Not processing presence. Thread was interrupted");
+ return;
+ }
this.presenceListener.onPresencePacketReceived(account, packet);
}
@@ -1449,6 +1559,8 @@ public class XmppConnection implements Runnable {
&& isSecure) {
authenticate(SaslMechanism.Version.SASL);
} else if (this.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT)
+ && isSecure
+ && LoginInfo.isSuccess(loginInfo)
&& streamId != null
&& !inSmacksSession) {
if (Config.EXTENDED_SM_LOGGING) {
@@ -1463,7 +1575,9 @@ public class XmppConnection implements Runnable {
this.mWaitingForSmCatchup.set(true);
this.tagWriter.writeStanzaAsync(resume);
} else if (needsBinding) {
- if (this.streamFeatures.hasChild("bind", Namespace.BIND) && isSecure) {
+ if (this.streamFeatures.hasChild("bind", Namespace.BIND)
+ && isSecure
+ && LoginInfo.isSuccess(loginInfo)) {
sendBindRequest();
} else {
Log.d(
@@ -1474,7 +1588,6 @@ public class XmppConnection implements Runnable {
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
}
} else {
-
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
@@ -1510,10 +1623,12 @@ public class XmppConnection implements Runnable {
this.streamFeatures.findChild("sasl-channel-binding", Namespace.CHANNEL_BINDING);
final Collection channelBindings = ChannelBinding.of(cbElement);
final SaslMechanism.Factory factory = new SaslMechanism.Factory(account);
- final SaslMechanism saslMechanism = factory.of(mechanisms, channelBindings, version, SSLSockets.version(this.socket));
+ final SaslMechanism saslMechanism =
+ factory.of(mechanisms, channelBindings, version, SSLSockets.version(this.socket));
this.validate(saslMechanism, mechanisms);
final boolean quickStartAvailable;
- final String firstMessage = saslMechanism.getClientFirstMessage(sslSocketOrNull(this.socket));
+ final String firstMessage =
+ saslMechanism.getClientFirstMessage(sslSocketOrNull(this.socket));
final boolean usingFast = SaslMechanism.hashedToken(saslMechanism);
final Element authenticate;
if (version == SaslMechanism.Version.SASL) {
@@ -1522,7 +1637,7 @@ public class XmppConnection implements Runnable {
authenticate.setContent(firstMessage);
}
quickStartAvailable = false;
- this.loginInfo = new LoginInfo(saslMechanism,version,Collections.emptyList());
+ this.loginInfo = new LoginInfo(saslMechanism, version, Collections.emptyList());
} else if (version == SaslMechanism.Version.SASL_2) {
final Element inline = authElement.findChild("inline", Namespace.SASL_2);
final boolean sm = inline != null && inline.hasChild("sm", Namespace.STREAM_MANAGEMENT);
@@ -1530,7 +1645,8 @@ public class XmppConnection implements Runnable {
if (usingFast) {
hashTokenRequest = null;
} else {
- final Element fast = inline == null ? null : inline.findChild("fast", Namespace.FAST);
+ final Element fast =
+ inline == null ? null : inline.findChild("fast", Namespace.FAST);
final Collection fastMechanisms = SaslMechanism.mechanisms(fast);
hashTokenRequest =
HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket));
@@ -1551,9 +1667,11 @@ public class XmppConnection implements Runnable {
return;
}
}
- this.loginInfo = new LoginInfo(saslMechanism,version,bindFeatures);
+ this.loginInfo = new LoginInfo(saslMechanism, version, bindFeatures);
this.hashTokenRequest = hashTokenRequest;
- authenticate = generateAuthenticationRequest(firstMessage, usingFast, hashTokenRequest, bindFeatures, sm);
+ authenticate =
+ generateAuthenticationRequest(
+ firstMessage, usingFast, hashTokenRequest, bindFeatures, sm);
} else {
throw new AssertionError("Missing implementation for " + version);
}
@@ -1581,7 +1699,9 @@ public class XmppConnection implements Runnable {
return inline != null && inline.hasChild("fast", Namespace.FAST);
}
- private void validate(final @Nullable SaslMechanism saslMechanism, Collection mechanisms) throws StateChangingException {
+ private void validate(
+ final @Nullable SaslMechanism saslMechanism, Collection mechanisms)
+ throws StateChangingException {
if (saslMechanism == null) {
Log.d(
Config.LOGTAG,
@@ -1608,8 +1728,10 @@ public class XmppConnection implements Runnable {
}
}
- private Element generateAuthenticationRequest(final String firstMessage, final boolean usingFast) {
- return generateAuthenticationRequest(firstMessage, usingFast, null, Bind2.QUICKSTART_FEATURES, true);
+ private Element generateAuthenticationRequest(
+ final String firstMessage, final boolean usingFast) {
+ return generateAuthenticationRequest(
+ firstMessage, usingFast, null, Bind2.QUICKSTART_FEATURES, true);
}
private Element generateAuthenticationRequest(
@@ -1802,8 +1924,10 @@ public class XmppConnection implements Runnable {
resetAttemptCount(true);
resetStreamId();
clearIqCallbacks();
- this.stanzasSent = 0;
- mStanzaQueue.clear();
+ synchronized (this.mStanzaQueue) {
+ this.stanzasSent = 0;
+ this.mStanzaQueue.clear();
+ }
this.redirectionUrl = null;
synchronized (this.disco) {
disco.clear();
@@ -2261,10 +2385,18 @@ public class XmppConnection implements Runnable {
final String seeOtherHost = streamError.findChildContent("see-other-host");
final Resolver.Result currentResolverResult = this.currentResolverResult;
if (Strings.isNullOrEmpty(seeOtherHost) || currentResolverResult == null) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": stream error " + streamError);
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid() + ": stream error " + streamError);
throw new StateChangingException(Account.State.STREAM_ERROR);
}
- Log.d(Config.LOGTAG,account.getJid().asBareJid()+": see other host: "+seeOtherHost+" "+currentResolverResult);
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": see other host: "
+ + seeOtherHost
+ + " "
+ + currentResolverResult);
final Resolver.Result seeOtherResult = currentResolverResult.seeOtherHost(seeOtherHost);
if (seeOtherResult != null) {
this.seeOtherHostResolverResult = seeOtherResult;
@@ -2282,8 +2414,7 @@ public class XmppConnection implements Runnable {
synchronized (this.mStanzaQueue) {
for (int i = 0; i < mStanzaQueue.size(); ++i) {
final AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i);
- if (stanza instanceof MessagePacket) {
- final MessagePacket packet = (MessagePacket) stanza;
+ if (stanza instanceof MessagePacket packet) {
final String id = packet.getId();
final Jid to = packet.getTo();
mXmppConnectionService.markMessage(
@@ -2298,7 +2429,8 @@ public class XmppConnection implements Runnable {
final boolean secureConnection = sslVersion != SSLSockets.Version.NONE;
final SaslMechanism quickStartMechanism;
if (secureConnection) {
- quickStartMechanism = SaslMechanism.ensureAvailable(account.getQuickStartMechanism(), sslVersion);
+ quickStartMechanism =
+ SaslMechanism.ensureAvailable(account.getQuickStartMechanism(), sslVersion);
} else {
quickStartMechanism = null;
}
@@ -2307,10 +2439,16 @@ public class XmppConnection implements Runnable {
&& quickStartMechanism != null
&& account.isOptionSet(Account.OPTION_QUICKSTART_AVAILABLE)) {
mXmppConnectionService.restoredFromDatabaseLatch.await();
- this.loginInfo = new LoginInfo(quickStartMechanism, SaslMechanism.Version.SASL_2, Bind2.QUICKSTART_FEATURES);
+ this.loginInfo =
+ new LoginInfo(
+ quickStartMechanism,
+ SaslMechanism.Version.SASL_2,
+ Bind2.QUICKSTART_FEATURES);
final boolean usingFast = quickStartMechanism instanceof HashedToken;
final Element authenticate =
- generateAuthenticationRequest(quickStartMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)), usingFast);
+ generateAuthenticationRequest(
+ quickStartMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)),
+ usingFast);
authenticate.setAttribute("mechanism", quickStartMechanism.getMechanism());
sendStartStream(true, false);
synchronized (this.mStanzaQueue) {
@@ -2419,9 +2557,7 @@ public class XmppConnection implements Runnable {
+ " do not write stanza to unbound stream "
+ packet.toString());
}
- if (packet instanceof AbstractAcknowledgeableStanza) {
- AbstractAcknowledgeableStanza stanza = (AbstractAcknowledgeableStanza) packet;
-
+ if (packet instanceof AbstractAcknowledgeableStanza stanza) {
if (this.mStanzaQueue.size() != 0) {
int currentHighestKey = this.mStanzaQueue.keyAt(this.mStanzaQueue.size() - 1);
if (currentHighestKey != stanzasSent) {
@@ -2431,7 +2567,13 @@ public class XmppConnection implements Runnable {
++stanzasSent;
if (Config.EXTENDED_SM_LOGGING) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid()+": counting outbound "+packet.getName()+" as #" + stanzasSent);
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": counting outbound "
+ + packet.getName()
+ + " as #"
+ + stanzasSent);
}
this.mStanzaQueue.append(stanzasSent, stanza);
if (stanza instanceof MessagePacket && stanza.getId() != null && inSmacksSession) {
@@ -2614,7 +2756,7 @@ public class XmppConnection implements Runnable {
public int getTimeToNextAttempt(final boolean aggressive) {
final int interval;
if (aggressive) {
- interval = Math.min((int) (3 * Math.pow(1.3,attempt)), 60);
+ interval = Math.min((int) (3 * Math.pow(1.3, attempt)), 60);
} else {
final int additionalTime =
account.getLastErrorStatus() == Account.State.POLICY_VIOLATION ? 3 : 0;
@@ -2724,6 +2866,7 @@ public class XmppConnection implements Runnable {
public final SaslMechanism saslMechanism;
public final SaslMechanism.Version saslVersion;
public final List inlineBindFeatures;
+ public final AtomicBoolean success = new AtomicBoolean(false);
private LoginInfo(
final SaslMechanism saslMechanism,
@@ -2742,6 +2885,23 @@ public class XmppConnection implements Runnable {
public static SaslMechanism mechanism(final LoginInfo loginInfo) {
return loginInfo == null ? null : loginInfo.saslMechanism;
}
+
+ public void success(final String challenge, final SSLSocket sslSocket)
+ throws SaslMechanism.AuthenticationException {
+ final var response = this.saslMechanism.getResponse(challenge, sslSocket);
+ if (!Strings.isNullOrEmpty(response)) {
+ throw new SaslMechanism.AuthenticationException(
+ "processing success yielded another response");
+ }
+ if (this.success.compareAndSet(false, true)) {
+ return;
+ }
+ throw new SaslMechanism.AuthenticationException("Process 'success' twice");
+ }
+
+ public static boolean isSuccess(final LoginInfo loginInfo) {
+ return loginInfo != null && loginInfo.success.get();
+ }
}
private static class StreamId {
@@ -2815,11 +2975,6 @@ public class XmppConnection implements Runnable {
&& pepPublishOptions();
}
- public boolean avatarConversion() {
- return hasDiscoFeature(account.getJid().asBareJid(), Namespace.AVATAR_CONVERSION)
- && pepPublishOptions();
- }
-
public boolean blocking() {
return hasDiscoFeature(account.getDomain(), Namespace.BLOCKING);
}
@@ -2845,7 +3000,8 @@ public class XmppConnection implements Runnable {
public boolean sm() {
return streamId != null
|| (connection.streamFeatures != null
- && connection.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT));
+ && connection.streamFeatures.hasChild(
+ "sm", Namespace.STREAM_MANAGEMENT));
}
public boolean csi() {
@@ -2966,7 +3122,8 @@ public class XmppConnection implements Runnable {
}
public boolean bookmarks2() {
- return pepPublishOptions() && hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS2_COMPAT);
+ return pepPublishOptions()
+ && hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS2_COMPAT);
}
public boolean externalServiceDiscovery() {
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java
index 0f7247fdc8d3af2e35654512a47df79afb7af484..632a8f034b0e44f729a920a4c6f2e024abc215e0 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java
@@ -209,8 +209,12 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
this.transportSecurity =
new TransportSecurity(
xmppAxolotlMessage.getInnerKey(), xmppAxolotlMessage.getIV());
- jinglePacket.setSecurity(
- Iterables.getOnlyElement(contentMap.contents.keySet()), xmppAxolotlMessage);
+ final var contents = jinglePacket.getJingleContents();
+ final var rawContent =
+ contents.get(Iterables.getOnlyElement(contentMap.contents.keySet()));
+ if (rawContent != null) {
+ rawContent.setSecurity(xmppAxolotlMessage);
+ }
}
jinglePacket.setTo(id.with);
xmppConnectionService.sendIqPacket(
@@ -327,8 +331,10 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
return;
}
final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
+ final var contents = jinglePacket.getJingleContents();
+ final var rawContent = contents.get(Iterables.getOnlyElement(contentMap.contents.keySet()));
final var security =
- jinglePacket.getSecurity(Iterables.getOnlyElement(contentMap.contents.keySet()));
+ rawContent == null ? null : rawContent.getSecurity(jinglePacket.getFrom());
if (security != null) {
Log.d(Config.LOGTAG, "found security element!");
keyTransportMessage =
@@ -349,7 +355,6 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
if (transition(State.SESSION_INITIALIZED, () -> setRemoteContentMap(contentMap))) {
respondOk(jinglePacket);
- Log.d(Config.LOGTAG, jinglePacket.toString());
Log.d(
Config.LOGTAG,
"got file offer " + file + " jet=" + Objects.nonNull(keyTransportMessage));
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
index fbde212a3bd2516685dc5bc41900055984903acd..2ac11b44cf0e49860f49b719ab43452a7e576570 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
@@ -9,8 +9,11 @@ import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import eu.siacs.conversations.Config;
+import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
+import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.jingle.SessionDescription;
import java.util.Locale;
@@ -102,6 +105,37 @@ public class Content extends Element {
}
}
+ public void setSecurity(final XmppAxolotlMessage xmppAxolotlMessage) {
+ final String contentName = this.getContentName();
+ final Element security = new Element("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT);
+ security.setAttribute("name", contentName);
+ security.setAttribute("cipher", "urn:xmpp:ciphers:aes-128-gcm-nopadding");
+ security.setAttribute("type", AxolotlService.PEP_PREFIX);
+ security.addChild(xmppAxolotlMessage.toElement());
+ this.addChild(security);
+ }
+
+ public XmppAxolotlMessage getSecurity(final Jid from) {
+ final String contentName = this.getContentName();
+ for (final Element child : getChildren()) {
+ if ("security".equals(child.getName())
+ && Namespace.JINGLE_ENCRYPTED_TRANSPORT.equals(child.getNamespace())) {
+ final String name = child.getAttribute("name");
+ final String type = child.getAttribute("type");
+ final String cipher = child.getAttribute("cipher");
+ if (contentName.equals(name)
+ && AxolotlService.PEP_PREFIX.equals(type)
+ && "urn:xmpp:ciphers:aes-128-gcm-nopadding".equals(cipher)) {
+ final var encrypted = child.findChild("encrypted", AxolotlService.PEP_PREFIX);
+ if (encrypted != null) {
+ return XmppAxolotlMessage.fromElement(encrypted, from.asBareJid());
+ }
+ }
+ }
+ }
+ return null;
+ }
+
public void setTransport(GenericTransportInfo transportInfo) {
this.addChild(transportInfo);
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java
index 552046fb855e2106ed037997f9e2b09fab57b02c..82c5b155c1d9666fb52b2a6e60c71de5bf626df5 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java
@@ -1,7 +1,5 @@
package eu.siacs.conversations.xmpp.jingle.stanzas;
-import android.util.Log;
-
import androidx.annotation.NonNull;
import com.google.common.base.CaseFormat;
@@ -9,9 +7,6 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.crypto.axolotl.AxolotlService;
-import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
@@ -121,39 +116,6 @@ public class JinglePacket extends IqPacket {
jingle.addChild(child);
}
- public void setSecurity(final String name, final XmppAxolotlMessage xmppAxolotlMessage) {
- final Element security = new Element("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT);
- security.setAttribute("name", name);
- security.setAttribute("cipher", "urn:xmpp:ciphers:aes-128-gcm-nopadding");
- security.setAttribute("type", AxolotlService.PEP_PREFIX);
- security.addChild(xmppAxolotlMessage.toElement());
- addJingleChild(security);
- }
-
- public XmppAxolotlMessage getSecurity(final String nameNeedle) {
- final Element jingle = findChild("jingle", Namespace.JINGLE);
- if (jingle == null) {
- return null;
- }
- for (final Element child : jingle.getChildren()) {
- if ("security".equals(child.getName())
- && Namespace.JINGLE_ENCRYPTED_TRANSPORT.equals(child.getNamespace())) {
- final String name = child.getAttribute("name");
- final String type = child.getAttribute("type");
- final String cipher = child.getAttribute("cipher");
- if (nameNeedle.equals(name)
- && AxolotlService.PEP_PREFIX.equals(type)
- && "urn:xmpp:ciphers:aes-128-gcm-nopadding".equals(cipher)) {
- final var encrypted = child.findChild("encrypted", AxolotlService.PEP_PREFIX);
- if (encrypted != null) {
- return XmppAxolotlMessage.fromElement(encrypted, getFrom().asBareJid());
- }
- }
- }
- }
- return null;
- }
-
public String getSessionId() {
return findChild("jingle", Namespace.JINGLE).getAttribute("sid");
}
diff --git a/src/main/res/menu/fragment_conversations_overview.xml b/src/main/res/menu/fragment_conversations_overview.xml
index 451b302d8ff2258d7c1a3f517a7381157399fbaf..eb43a7322dbbc9b6f7698fe5ab8256eb0d386039 100644
--- a/src/main/res/menu/fragment_conversations_overview.xml
+++ b/src/main/res/menu/fragment_conversations_overview.xml
@@ -51,6 +51,11 @@
android:orderInCategory="90"
android:title="@string/action_account"
app:showAsAction="never" />
+
-
\ No newline at end of file
diff --git a/src/main/res/values-bg/strings.xml b/src/main/res/values-bg/strings.xml
index f8c8c886019dcb1760b5e1e7931baccbf833c238..e8e3c4667a41a0d6bff9c535fd6021da7155fceb 100644
--- a/src/main/res/values-bg/strings.xml
+++ b/src/main/res/values-bg/strings.xml
@@ -505,7 +505,10 @@
Дайте на %1$s разрешение за достъп до външната памет
Дайте на %1$s разрешение за достъп до камерата
Синхронизиране с контактите
- %1$s иска разрешение за достъп до адресната Ви книга, за да потърси съвпадения със списъка от контакти в XMPP.\nТова ще покаже пълните имена и аватари на контактите Ви.\n\n%1$s само ще прочете адресната книга и ще потърси съвпадения на това устройство – нищо няма да се качва на сървъра Ви.
+ %1$s иска разрешение за достъп до адресната Ви книга, за да потърси съвпадения със списъка от контакти в XMPP.
+\nТова ще покаже пълните имена и аватари на контактите Ви.
+\n
+\n%1$s само ще прочете адресната книга и ще потърси съвпадения на това устройство – нищо няма да се качва на сървъра Ви.
Известяване за всички съобщения
Известяване само при споменаване
Известията са изключени
diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml
index a6f8787cb1fc352dcbd631e73c7c1c5b9e7fb83a..c3af747ed9bbc61d12cdba07992baa130b2137c5 100644
--- a/src/main/res/values-cs/strings.xml
+++ b/src/main/res/values-cs/strings.xml
@@ -511,7 +511,10 @@
Povolit %1$s přístup k externímu úložišti
Povolit %1$s přístup ke kameře
Synchronizovat s kontakty
- %1$s požaduje přístup k Vašim kontaktům za účelem spárování s Vašimi XMPP kontakty.\nU kontaktů se pak zobrazí celé jméno a avatar.\n\n%1$s bude kontakty pouze číst a párovat místně v zařízení, aniž by došlo k nahrání těchto dat na server.
+ %1$s požaduje přístup k Vašim kontaktům za účelem spárování s Vašimi XMPP kontakty.
+\nU kontaktů se pak zobrazí celé jméno a avatar.
+\n
+\n%1$s bude kontakty pouze číst a párovat místně v zařízení, aniž by došlo k nahrání těchto dat na server.
Upozorňovat na všechny zprávy
Upozornit pouze, když mě někdo zmíní
Upozornění vypnuta
diff --git a/src/main/res/values-da-rDK/strings.xml b/src/main/res/values-da-rDK/strings.xml
index 215d1ca5ab7b33666384aec901790813de620781..3f75eadc081710eb9c783dbfd976db8ea476ab2c 100644
--- a/src/main/res/values-da-rDK/strings.xml
+++ b/src/main/res/values-da-rDK/strings.xml
@@ -514,7 +514,10 @@
Giv %1$s adgang til ekstern lagerplads
Giv %1$s adgang til kameraet
Synkroniser med kontakter
- %1$s ønsker tilladelse til at få adgang til din adressebog for at matche den med din XMPP kontaktliste.\nDette vil vise dine kontakters fulde navne og avatarer.\n\n%1$s læser kun din adressebog og matcher den lokalt uden at uploade noget til din server.
+ %1$s ønsker tilladelse til at få adgang til din adressebog for at matche den med din XMPP kontaktliste.
+\nDette vil vise dine kontakters fulde navne og avatarer.
+\n
+\n%1$s læser kun din adressebog og matcher den lokalt uden at uploade noget til din server.
Underret ved alle beskeder
Underret kun når nævnt
Notifikationer deaktiveret
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index 945062f64d23760458b918bc6d45d4dfb5fac358..ca278eec78c5d6aadda971119726241451d75710 100644
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -320,8 +320,8 @@
XMPP-Adresse in Zwischenablage kopiert
Fehlermeldung in Zwischenablage kopiert
Internetadresse
- Barcode scannen
- Barcode anzeigen
+ QR-Code scannen
+ QR-Code anzeigen
Sperrliste anzeigen
Kontodetails
Bestätigen
@@ -515,8 +515,10 @@
Text mit %s geteilt
%1$s den Zugriff auf den externen Speicher gewähren
%1$s den Zugriff auf die Kamera gewähren
- Mit Kontakten synchronisieren
- %1$s möchte die Erlaubnis, auf deine Kontakte zuzugreifen, um sie mit deiner XMPP-Kontaktliste abzugleichen.\nDadurch werden die vollständigen Namen und Profilbilder deiner Kontakte angezeigt.\n\n%1$s liest nur dein Adressbuch und gleicht es lokal ab, ohne dass etwas auf deinen Server hochgeladen wird.
+ Kontaktlistenintegration
+ %1$s verarbeitet deine Kontaktliste lokal auf deinem Gerät, um dir die Namen und Profilbilder von passenden Kontakten auf XMPP zu zeigen.
+\n
+\nKeine Daten der Kontaktliste verlassen jemals dein Gerät!
Bei allen Nachrichten benachrichtigen
Nur benachrichtigen, wenn ich erwähnt werde
Benachrichtigungen deaktiviert
@@ -594,7 +596,7 @@
Erzeuge neue OMEMO-Schlüssel. Alle deine Kontakte müssen sie erneut verifizieren. Verwende dies nur als letztes Mittel.
Ausgewählte Schlüssel löschen
Du musst verbunden sein, um deinen Profilbild zu veröffentlichen.
- Zeige Fehlermeldung
+ Fehlermeldung anzeigen
Fehlermeldung
Datensparmodus aktiv
Dein Betriebssystem verhindert, dass %1$s im Hintergrund auf das Internet zugreift. Um Benachrichtigungen erhalten zu können, solltest du %1$s den Zugang erlauben, wenn der Datensparmodus aktiv ist.\n%1$s wird dennoch versuchen, so viele Daten wie möglich einzusparen.
@@ -614,7 +616,7 @@
Neuen Geräten von nicht verifizierten Kontakten vertrauen, aber bei verifizierten Kontakten eine manuelle Bestätigung der neuen Geräte verlangen.
Blind vertraute OMEMO-Schlüssel bedeutet, dass es sich um eine andere Person handeln könnte oder dass jemand sie abgehört haben könnte.
Nicht vertraut
- Ungültiger Barcode
+ Ungültiger QR-Code
Cache-Ordner löschen (von der Kamera-App genutzt)
Lösche Cache
Lösche privaten Speicher
@@ -1014,10 +1016,14 @@
Du hast dich von diesem Konto abgemeldet
Anmelden
Benachrichtigung ausblenden
- Dein Kontakt verwendet nicht verifizierte Geräte. Scanne deren 2D-Barcode, um eine Verifizierung durchzuführen und aktive MITM-Angriffe zu verhindern.
+ Dein Kontakt verwendet nicht verifizierte Geräte. Scanne deren QR-Code, um eine Verifizierung durchzuführen und aktive MITM-Angriffe zu verhindern.
Abmelden
Abgemeldet
- Du verwendest nicht verifizierte Geräte. Scanne den 2D-Barcode auf deinen anderen Geräten, um eine Verifizierung durchzuführen und aktive MITM-Angriffe zu verhindern.
+ Du verwendest nicht verifizierte Geräte. Scanne den QR-Code auf deinen anderen Geräten, um eine Verifizierung durchzuführen und aktive MITM-Angriffe zu verhindern.
Spam melden
- Spam melden und Spammer blockieren
+ Spam melden und Spammer sperren
+ Willkommen bei Quicksy!
+ Datenschutzbestimmungen
+ Quicksy bittet dich um deine Zustimmung zur Verwendung deiner Daten
+ Kontaktlistenintegration ist nicht verfügbar
\ No newline at end of file
diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml
index 183ec8357d5273a8217275f784037642717d0adf..4c15a48de1f353f129932006a9cce8e730c35342 100644
--- a/src/main/res/values-el/strings.xml
+++ b/src/main/res/values-el/strings.xml
@@ -31,10 +31,7 @@
πριν από %d λεπτά
- %d μη αναγνωσμένη συζήτηση
-
-
- %d μη αναγνωσμένες συζητήσεις
-
αποστολή...
Αποκρυπτογράφηση μηνύματος. Παρακαλώ περιμένετε...
@@ -509,7 +506,10 @@
Απόδοση δικαιώματος στο %1$s για πρόσβαση στον εξωτερικό αποθηκευτικό χώρο
Απόδοση δικαιώματος στο %1$s για πρόσβαση στην φωτογραφική μηχανή
Συγχρονισμός με επαφές
- Το %1$s ζητάει το δικαίωμα να έχει πρόσβαση στο βιβλίο διευθύνσεων για να το ταιριάξει με την λίστα επαφών XMPP σας.\nΑυτή η ενέργεια θα εμφανίσει τα πλήρη ονόματα και τις εικόνες προφίλ των επαφών σας.\n\nΤο %1$s θα διαβάσει μόνο το βιβλίο διευθύνσεών σας και θα το ταιριάξει τοπικά χωρίς να μεταφορτώσει κανένα στοιχείο στον διακομιστή σας.
+ Το %1$s ζητάει το δικαίωμα να έχει πρόσβαση στο βιβλίο διευθύνσεων για να το ταιριάξει με την λίστα επαφών XMPP σας.
+\nΑυτή η ενέργεια θα εμφανίσει τα πλήρη ονόματα και τις εικόνες προφίλ των επαφών σας.
+\n
+\nΤο %1$s θα διαβάσει μόνο το βιβλίο διευθύνσεών σας και θα το ταιριάξει τοπικά χωρίς να μεταφορτώσει κανένα στοιχείο στον διακομιστή σας.
Ειδοποίηση για όλα τα μηνύματα
Ειδοποίηση μόνο όταν αναφέρεται το όνομά μου
Οι ειδοποιήσεις απενεργοποιήθηκαν
@@ -963,4 +963,4 @@
Έγγραφο απλού κειμένου
Δεν υποστηρίζονται εγγραφές λογαριασμών
Δεν βρέθηκε διεύθυνση XMPP
-
+
\ No newline at end of file
diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml
index 633ac545863a9a9676bf68145617f684e8874c7d..5b78f16b08aadb266abcb08e7407ca87a2f2f46b 100644
--- a/src/main/res/values-es/strings.xml
+++ b/src/main/res/values-es/strings.xml
@@ -321,7 +321,7 @@
Dirección XMPP copiada al portapapeles
Mensaje de error copiado al portapapeles
dirección web
- Escanear código QR
+ Escanear el código QR
Mostrar código QR
Mostrar contactos bloqueados
Detalles de la cuenta
@@ -518,8 +518,10 @@
Texto compartido con %s
Permitir a %1$s acceder al almacenamiento externo
Permitir a %1$s acceder a la cámara
- Sincronizar contactos
- %1$s quiere permiso para acceder a tu agenda de contactos y cruzarla con tu lista de contactos de XMPP.\nEsto permitirá mostrar el nombre completo y los avatares de tus contactos.\n\n%1$s solo leerá tu agenda de contactos y la cruzará localmente sin subir nada a tu servidor.
+ Integración de la lista de contactos
+ %1$s procesa tu lista de contactos localmente, en tu dispositivo, para mostrarte los nombres y fotos de perfil de los contactos coincidentes en XMPP.
+\n
+\n¡Ningún dato de la lista de contactos sale de tu dispositivo!
Notificar para todos los mensajes
Notificar solo cuando eres mencionado
Notificaciones deshabilitadas
@@ -1028,10 +1030,14 @@
Has salido de esta cuenta
Iniciar sesión
Ocultar la notificación
- Su contacto utiliza dispositivos no verificados. Escanea su código de barras en 2D para realizar la verificación e impedir ataques MITM activos.
+ Tu contacto utiliza dispositivos no verificados. Escanea su código QR para realizar la verificación e impedir ataques MITM activos.
Desconectarse
Desconectado
- Está utilizando dispositivos no verificados. Escanea el código de barras 2D en tus otros dispositivos para realizar la verificación e impedir los ataques MITM activos.
+ Está utilizando dispositivos no verificados. Escanea el código QR en tus otros dispositivos para realizar la verificación e impedir ataques MITM activos.
Informar de spam y bloquear al spammer
Informar sobre spam
+ ¡Bienvenido a Quicksy!
+ Quicksy pide tu consentimiento para utilizar tus datos
+ Política de privacidad
+ La lista de contactos no está disponible
\ No newline at end of file
diff --git a/src/main/res/values-fi/strings.xml b/src/main/res/values-fi/strings.xml
index 8645cad810a2e48af147dc2755cbb3cd102f3bd6..6c5a1523451cf1f669292b10c94bbe4219347d41 100644
--- a/src/main/res/values-fi/strings.xml
+++ b/src/main/res/values-fi/strings.xml
@@ -493,7 +493,10 @@
Salli %1$s:n käyttää ulkoista tallennustilaa
Salli %1$s:n käyttää kameraa
Synkronoi yhteystietojen kanssa
- %1$s haluaa pääsyn osoitekirjaasi yhdistääkseen sen XMPP-yhteystietojesi kanssa.\nTämä näyttää yhteystietojesi koko nimen ja kuvan.\n\n%1$s pelkästään lukee osoitekirjaasi ja vertailee niitä paikallisesti, lähettämättä mitään palvelimelle.
+ %1$s haluaa pääsyn osoitekirjaasi yhdistääkseen sen XMPP-yhteystietojesi kanssa.
+\nTämä näyttää yhteystietojesi koko nimen ja kuvan.
+\n
+\n%1$s pelkästään lukee osoitekirjaasi ja vertailee niitä paikallisesti, lähettämättä mitään palvelimelle.
Ilmoita kaikista uusista viesteistä
Ilmoita vain kun minut mainitaan
Ilmoitukset pois käytöstä
diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml
index c68f67a73cd38fa265f2b61c26f432b006043574..7889773e908c5b0f621726de200113be4bbac9bd 100644
--- a/src/main/res/values-gl/strings.xml
+++ b/src/main/res/values-gl/strings.xml
@@ -39,7 +39,7 @@
O alcume xa está en uso
Alcume non válido
Admin
- Dono
+ Dona
Moderador
Participante
Visitante
@@ -300,7 +300,7 @@
Poñer marca de \"autojoin\" ao entrar ou deixar unha MUC e reaccionar ás modificacións feitas desde outros clientes.
Copiouse a impresión dixital OMEMO ao portapapeis
Non podes acceder a esta conversa en grupo
- Esta conversa en grupo é so para membros
+ Este chat en grupo é so para membros
Restrición do recurso
Xa foi expulsado de esta conversa en grupo
A conversa en grupo foi apagada
@@ -322,8 +322,8 @@
Copiouse o enderezo XMPP ao portapapeis
Mensaxe do fallo copiado ao portapapeis
Dirección Web
- Escanear código de barras 2D
- Mostar código de barras 2D
+ Escanear código QR
+ Mostar código QR
Mostrar lista de bloqueo
Detalles da conta
Confirmar
@@ -518,7 +518,10 @@
Permitir que %1$s acceda ao almacenaxe externo
Permitir que %1$s acceda á cámara
Sincronice con todos os contactos
- %1$s quere ter permiso para acceder á túa libreta de enderezos para comparala coa lista de contactos XMPP.\nDeste xeito poderá mostrar o nome completo e avatares dos teus contactos.\n\n%1$s só utilizará de xeito local a túa lista de contactos, sen subila a ningún servidor.
+ %1$s quere ter permiso para acceder á túa libreta de enderezos para comparala coa lista de contactos XMPP.
+\nDeste xeito poderá mostrar o nome completo e avatares dos teus contactos.
+\n
+\n%1$s só utilizará de xeito local a túa lista de contactos, sen subila a ningún servidor.
Notificar todas as mensaxes
Notificar só cando é mencionada
Notificacións desactivadas
@@ -616,7 +619,7 @@
Confiar en dispositivos novos de contactos non verificados, pero solicitar confirmación manual de novos dispositivos para contactos verificados.
Chaves OMEMO de confianza cega, significa que podería ser calquera outra persoa ou algunha impostora.
Non confiables
- Código de barras 2D non válido
+ Código QR non válido
Baleirar o cartafol da caché (utilizado pola cámara)
Baleirar caché
Baleirar almacenaxe privada
@@ -748,7 +751,7 @@
Na pantalla \'Iniciar Conversa\' abrir teclado e pór o cursor no campo de busca
Avatar da conversa de grupo
O servidor non soporta o avatar na conversa de grupo
- Só o dono pode cambiar o avatar da conversa de grupo
+ Só a propietaria pode cambiar o avatar da conversa
Nome do contacto
Alcume
Nome
@@ -1017,10 +1020,14 @@
Pechaches a sesión desta conta
Acceder
Agochar notificación
- O teu contacto usa dispositivos non verificados. Escanea o seu código de barras 2D para verficalo e impedir ataques MITM.
+ O teu contacto usa dispositivos non verificados. Escanea o seu código QR para verficalo e impedir ataques MITM.
Saír da sesión
Sesión pechada
- Estás a usar dispositivos non verificados. Escanea os códigos de barras 2D nos teus outros dispositivos para verificalos e impedir ataques MITM.
+ Estás a usar dispositivos non verificados. Escanea os códigos QR nos teus outros dispositivos para verificalos e impedir ataques MITM.
Informar de spam e bloquear conta
Informar de spam
+ Benvida a Quicksy!
+ Quicksy solicita permiso para usar os teus datos
+ Política de privacidade
+ Non está dispoñible a integración coa libreta de enderezos
diff --git a/src/main/res/values-hu/strings.xml b/src/main/res/values-hu/strings.xml
index c9bea574807bcc388cd2aff4885534af491607b4..93a1d2c0cd35bb23f568722ccc0e828ca31f2414 100644
--- a/src/main/res/values-hu/strings.xml
+++ b/src/main/res/values-hu/strings.xml
@@ -31,10 +31,7 @@
%d perce
- %d olvasatlan beszélgetés
-
-
- %d olvasatlan beszélgetés
-
küldés…
Üzenet visszafejtése. Kérem várjon…
@@ -888,4 +885,4 @@
- %1$d résztvevő megtekintése
- %1$d résztvevő megtekintése
-
+
\ No newline at end of file
diff --git a/src/main/res/values-id/strings.xml b/src/main/res/values-id/strings.xml
index 47b9992d06fc8b40a79180910342376e33e8471a..3efb9a93d789b6c4755d3ebd2379bb8ae0411bd6 100644
--- a/src/main/res/values-id/strings.xml
+++ b/src/main/res/values-id/strings.xml
@@ -31,7 +31,6 @@
%d min lalu
- %d percakapan belum dibaca
-
mengirim...
Mendekripsi pesan. Mohon tunggu…
@@ -486,4 +485,4 @@
alamat XMPP
Buat channel publik...
Sibuk
-
+
\ No newline at end of file
diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml
index bf87cffbac37c5d8c153afda4257d76adab4a5b0..e55aef05b384e7387caa868686e36a45427d5c42 100644
--- a/src/main/res/values-it/strings.xml
+++ b/src/main/res/values-it/strings.xml
@@ -321,8 +321,8 @@
Indirizzo XMPP copiato negli appunti
Messaggio di errore copiato negli appunti
indirizzo web
- Scansiona codice a barre 2D
- Mostra codice a barre 2D
+ Scansiona codice QR
+ Mostra codice QR
Mostra la lista nera
Dettagli del profilo
Conferma
@@ -519,7 +519,10 @@
Dai a %1$s l\'accesso all\'archiviazione esterna
Dai a %1$s l\'accesso alla fotocamera
Sincronizza con i contatti
- %1$s vuole l\'autorizzazione ad accedere alla tua rubrica per confrontarla con la lista di contatti in XMPP.\nCiò mostrerà i nomi ed avatar dei contatti.\n\n%1$s leggerà solamente la rubrica e la confronterà localmente senza inviare nulla al tuo server.
+ %1$s vuole l\'autorizzazione ad accedere alla tua rubrica per confrontarla con la lista di contatti in XMPP.
+\nCiò mostrerà i nomi ed avatar dei contatti.
+\n
+\n%1$s leggerà solamente la rubrica e la confronterà localmente senza inviare nulla al tuo server.
Notifica per tutti i messaggi
Notifica solo quando menzionato
Notifiche disattivate
@@ -617,7 +620,7 @@
Fidati di nuovi dispositivi da contatti non verificati, ma chiedi una conferma manuale per nuovi dispositivi da contatti verificati.
Chiavi OMEMO accettate ciecamente, perciò potrebbero essere di qualcun altro o qualcuno potrebbe essersi intromesso.
Non fidato
- Codice a barre 2D non valido
+ Codice QR non valido
Svuota la cartella della cache (usata dall\'app fotocamera)
Svuota cache
Svuota archivio privato
@@ -1028,10 +1031,12 @@
Ti sei disconnesso da questo profilo
Accedi
Nascondi notifica
- Il tuo contatto usa dispositivi non verificati. Scansiona il suo codice a barre 2D per effettuare la verifica e impedire attacchi MITM attivi.
+ Il tuo contatto usa dispositivi non verificati. Scansiona il suo codice QR per effettuare la verifica e impedire attacchi MITM attivi.
Disconnetti
Disconnesso
- Stai usando dispositivi non verificati. Scansiona il codice a barre 2D nei tuoi altri dispositivi per effettuare la verifica e impedire attacchi MITM attivi.
+ Stai usando dispositivi non verificati. Scansiona il codice QR nei tuoi altri dispositivi per effettuare la verifica e impedire attacchi MITM attivi.
Segnala spam e blocca l\'utente
Segnala spam
+ Benvenuti su Quicksy!
+ Quicksy ti chiede il consenso per usare i tuoi dati
\ No newline at end of file
diff --git a/src/main/res/values-iw/strings.xml b/src/main/res/values-iw/strings.xml
index 5041fb565501a68ce7479b494650a125d8d0e325..e0b64e9490fe2edbe09fe6e6aaf30d60ac395d39 100644
--- a/src/main/res/values-iw/strings.xml
+++ b/src/main/res/values-iw/strings.xml
@@ -281,4 +281,4 @@
מקוון
ההודעה הועתקה
הראה מיקום
-
+
\ No newline at end of file
diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml
index b23c50b96efbf777a9ec9f62ab1f7aeac9cf437f..af50c6a77c788580e05c8889b2cc81e01bd3a403 100644
--- a/src/main/res/values-ja/strings.xml
+++ b/src/main/res/values-ja/strings.xml
@@ -7,7 +7,7 @@
会話を閉じる
連絡先の詳細
グループチャットの詳細
- 談話室の詳細
+ チャンネルの詳細
アカウントを追加
名前を編集
アドレス帳に追加
@@ -34,7 +34,7 @@
送信中…
メッセージを復号しています。しばらくお待ちください…
- OpenPGP 暗号化メッセージ
+ OpenPGPで暗号化されたメッセージ
ニックネームは既に使用されています
このニックネームは使えません
管理者
@@ -42,11 +42,11 @@
調停者
参加者
訪問者
- 連絡先名簿から %s を削除しますか? この連絡先との会話は削除されません。
- %s からあなたに送信されるメッセージをブロックしますか?
+ 連絡先リストから%sを削除しますか? この連絡先との会話は削除されません。
+ %sからあなたに送信されるメッセージをブロックしますか?
%s のブロックを解除し、あなたにメッセージを送信できるようにしますか?
- %s からの連絡をすべてブロックしますか?
- %s からすべての連絡先のブロックを解除しますか?
+ %sの連絡先をすべてブロックしますか?
+ %sのすべての連絡先のブロックを解除しますか?
連絡先をブロックしました
ブロックしました
%s のブックマークを削除しますか? このブックマークとの会話は削除されません。
@@ -68,14 +68,14 @@
保存
OK
%1$s がクラッシュしました
- あなたの XMPP アカウントを使用してスタックトレースの送信をすることで、 %1$s の継続的な開発を支援します。
+ あなたのXMPPアカウントを使用してスタックトレースを送信すると、 %1$sの実施中の開発の支援となります。
今すぐ送信
今後は表示しない
アカウントに接続できません
複数のアカウントに接続できません
タップしてアカウントを管理
ファイルを添付
- 連絡先が連絡先名簿にありません。名簿に追加しますか?
+ 連絡先が連絡先リストにありません。リストに追加しますか?
連絡先を追加
配信に失敗しました
送信用画像の準備中
@@ -105,8 +105,10 @@
依頼中…
待機中…
OpenPGP 鍵が見つかりません
- 連絡先が公開鍵を通知しないため、あなたのメッセージを暗号化することができません。\n\n連絡先に OpenPGP をセットアップするように依頼してください。
- OpenPGP 鍵が見つかりません
+ 連絡先が公開鍵を通知していないため、あなたのメッセージを暗号化することができません。
+\n
+\n連絡先にOpenPGPを設定するように依頼してください。
+ OpenPGPの鍵が見つかりません
連絡先が公開鍵を通知しないため、あなたのメッセージを暗号化することができません。\n\n連絡先に OpenPGP をセットアップするように依頼してください。
全般
ファイルを受取
@@ -122,10 +124,10 @@
新着メッセージの通知音
着信通話の呼出音
猶予期間
- 別のデバイスでの操作を検知した際に、通知を止める時間の長さ
+ 別のデバイスでの操作を検知した際に、通知を止める時間の長さ。
詳細
クラッシュレポートを送信しない
- スタックトレースを送信すると、 Conversations の開発を支援します
+ スタックトレースを送信すると、 開発の助けとなります
メッセージを確認
あなたがメッセージを受信して読んだときに、連絡先に知らせる
スクリーンショットを防ぐ
@@ -181,7 +183,7 @@
出席情報告知から OpenPGP 公開鍵を削除してもよろしいですか?\n連絡先はあなたに OpenPGP 暗号化メッセージを送信できなくなります。
OpenPGP 公開鍵を公開しました。
アカウントを有効化
- アカウントを削除すると会話履歴がすべて消去されます
+ アカウントを削除してよろしいですか?アカウントを削除すると会話履歴がすべて消去されます
音声を録音
XMPP アドレス
XMPP アドレスをブロック
@@ -291,10 +293,10 @@
その他
ブックマーク同期
OMEMO フィンガープリントをクリップボードにコピーしました
- このグループチャットから出禁にされています
+ このグループチャットへの参加はブロックされています
このグループチャットはメンバー制です
リソース制限
- このグループチャットから蹴り出されています
+ このグループチャットから退出させられました
このグループチャットは閉鎖されました
あなたはもうこのグループチャットに参加していません
技術的理由の為、あなたはこのグループチャットを離れました
@@ -508,7 +510,10 @@
%1$s に外部ストレージへのアクセス権を付与してください
%1$s にカメラへのアクセス権を付与
連絡先と同期
- %1$s はあなたのアドレス帳にアクセスして、あなたのXMPP 連絡先名簿と照合する権限を求めています。\nこれにより、連絡先のフルネームとアバターが表示されます。\n\n%1$s は、あなたのサーバーに何かをアップロードすることなく、あなたのアドレス帳を読み込んで照合するだけです。
+ %1$s はあなたのアドレス帳にアクセスして、あなたのXMPP 連絡先名簿と照合する権限を求めています。
+\nこれにより、連絡先のフルネームとアバターが表示されます。
+\n
+\n%1$s は、あなたのサーバーに何かをアップロードすることなく、あなたのアドレス帳を読み込んで照合するだけです。
すべてのメッセージで通知
メンションされたときにのみ通知
通知は無効
@@ -894,24 +899,24 @@
通話終了
応答
拒否
- デバイス発見
- 鳴動
+ デバイスを探索中
+ 呼び出し中
取込中
通話に接続できません
接続切断
撤回された通話
アプリの失敗
検証に問題
- 電話を切る
+ 通話を切る
継続中の通話
- 継続中の映像通話
- 通話再接続中
- ビデオ通話再接続中
- 通話するのに Tor を無効化
+ 継続中のビデオ通話
+ 通話に再接続中
+ ビデオ通話に再接続中
+ 通話を行う際にはTorを無効にしてください
着信通話
- 不在着信通話・%s
+ 不在着信・%s
発信通話
- 不在着信通話
+ 不在着信
- %2$sから%1$d件の不在着信
@@ -930,7 +935,7 @@
継続中の通話に戻る
カメラを切り替えできません
最上に留める
- 最上から留めるのをやめる
+ 最上部へのピン止めを外す
GPX 追跡
メッセージを修正できません
すべての会話
@@ -942,9 +947,9 @@
非暗号化
終了
音声メールを録音
- 音声再生
- 音声一時中断
- 連絡先を追加、作成またはグループチャットに参加、または談話室を発見する
+ 音声を再生
+ 音声を一時停止
+ 連絡先を追加、作成またはグループチャットに参加、またはチャンネルを発見する
- %1$d人の参加者を表示
@@ -954,7 +959,7 @@
配信に失敗
更なるオプション
アプリケーションが見つかりません
- 会話に招待
+ Conversationsに招待
招待を解析できません
サーバーは招待の作成をサポートしていません
この機能をサポートするアクティブなアカウントがありません
@@ -966,19 +971,33 @@
一時的な認証失敗
アバターを削除
Tor使用中のため通話できません
- ビデオ通話切替
+ ビデオ通話へ切替
このアカウントをログアウトしました
ルート
- ビデオ通話を却下する
- 着信通話 (%s) · %s
+ ビデオ通話を拒否
+ 着信中の通話 (%s) · %s
XMPPアカウント
グループ
選択したファイルは、旧式のファイル形式ので復元できません
グループを検索
- 発信通話 (%s) · %s
+ 発信中の通話 (%s) · %s
ログアウトしました
発信通話 · %s
オーディオブック
談話室の発見は<a href=https://search.jabber.network>search.jabber.network</a>というサービスを利用します.<br><br>利用するとIPアドレスと検索語はそのサービスに送信されます。詳細についてはそのサービスの<a href=https://search.jabber.network/privacy>個人情報保護方針</a>を参照してください。
自分で保存したバックアップしか復元しないでください!
+ XMPP経由でPushメッセージを端末に転送するユーザー指定のPushサーバー。
+ ログイン
+ 通知を表示しない
+ アカウントをサーバーから削除
+ ログアウト
+ なし(無効)
+ 拒否
+ Pushサーバー
+ グループチャットとして保存
+ UnifiedPushディストリビューター
+ スパムを報告
+ Pushメッセージを受信する際に経由するアカウント。
+ サーバーからアカウントを削除できませんでした
+ 概要
diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml
index 1871607695388218a600412425b8e5ad212915ab..b0242ecbe0b967c8e2859226a6df4376f297fa6c 100644
--- a/src/main/res/values-ko/strings.xml
+++ b/src/main/res/values-ko/strings.xml
@@ -396,4 +396,4 @@
중간
위치 표시
바쁨
-
+
\ No newline at end of file
diff --git a/src/main/res/values-nb-rNO/strings.xml b/src/main/res/values-nb-rNO/strings.xml
index 30561df9fd716d8560230ca69ea1091a236b78af..850b936cb4cec6581d952b3cf7632edc28256607 100644
--- a/src/main/res/values-nb-rNO/strings.xml
+++ b/src/main/res/values-nb-rNO/strings.xml
@@ -465,4 +465,4 @@
Gruppesludringsnavn
Opprett gruppesludring
Opptatt
-
+
\ No newline at end of file
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
index 07f3eae93ecd19958a1db6d476a9bd786a71f74b..019a00ab2c08486850d7a9bc8b442284fe27e509 100644
--- a/src/main/res/values-pl/strings.xml
+++ b/src/main/res/values-pl/strings.xml
@@ -524,7 +524,10 @@
Pozwól %1$s na dostęp do zewnętrznego magazynu
Pozwól %1$s na dostępu do aparatu
Synchronizuj z kontaktami
- %1$s potrzebuje dostępu do twojej książki adresowej aby dopasować ją z twoją listą kontaktów XMPP.\nDzięki temu wyświetlone zostaną pełne nazwy i awatary kontaktów.\n\n%1$s użyje książki adresowej wyłącznie do lokalnego dopasowania bez wysyłania czegokolwiek na serwer.
+ %1$s potrzebuje dostępu do twojej książki adresowej aby dopasować ją z twoją listą kontaktów XMPP.
+\nDzięki temu wyświetlone zostaną pełne nazwy i awatary kontaktów.
+\n
+\n%1$s użyje książki adresowej wyłącznie do lokalnego dopasowania bez wysyłania czegokolwiek na serwer.
Powiadom o wszystkich wiadomościach
Powiadamiaj tylko w przypadku wzmianki o mnie
Powiadomienia wyłączone
diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml
index 0cb31fa7e49076cba9902bb0a8b94f8218f50056..480aa7ff9e13ab22572b04f2a7881aac453da53f 100644
--- a/src/main/res/values-pt-rBR/strings.xml
+++ b/src/main/res/values-pt-rBR/strings.xml
@@ -31,13 +31,8 @@
%d minutos atrás
- %d conversa não lida
-
-
- %d conversas não lidas
-
-
- %d conversas não lidas
-
enviando...
Descriptografando a mensagem. Por favor, aguarde...
@@ -520,7 +515,9 @@
Permita o acesso do %1$s ao armazenamento externo
Permita o acesso do %1$s à câmera
Sincronizar com os contatos
- %1$s gostaria de obter a permissão para acessar seu livro de endereços e fazer a correspondência entre ele e a sua lista de contatos do XMPP. Isso permitirá exibir os nomes completos e avatares dos seus contatos.\n\n%1$s fará a leitura e a correspondência do seu livro de endereços localmente, sem enviar os seus contatos para o servidor em uso.
+ %1$s gostaria de obter a permissão para acessar seu livro de endereços e fazer a correspondência entre ele e a sua lista de contatos do XMPP. Isso permitirá exibir os nomes completos e avatares dos seus contatos.
+\n
+\n%1$s fará a leitura e a correspondência do seu livro de endereços localmente, sem enviar os seus contatos para o servidor em uso.
Notificar em todas as mensagens
Notificar somente quando for mencionado
Notificações desabilitadas
@@ -1005,5 +1002,4 @@
As chamadas estão desabilitadas ao usar Tor
Mudar para vídeo
Recusar requisição de mudança para vídeo
-
-
+
\ No newline at end of file
diff --git a/src/main/res/values-pt/strings.xml b/src/main/res/values-pt/strings.xml
index 86e78718cbdb98f628411ba8c813fec03f0bbf43..4bc390c139db8d75ced0ffa48c08134199be90d5 100644
--- a/src/main/res/values-pt/strings.xml
+++ b/src/main/res/values-pt/strings.xml
@@ -410,4 +410,4 @@
Médio
Exibir localização
Ocupado
-
+
\ No newline at end of file
diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml
index 97121e8b3cc051b28f02b39715d134295d48564b..a7e2ebf9e897dea5a9a5a1c4cc73b1449dd2969e 100644
--- a/src/main/res/values-ro-rRO/strings.xml
+++ b/src/main/res/values-ro-rRO/strings.xml
@@ -323,8 +323,8 @@
Adresă XMPP copiată în memorie
Mesaj de eroare copiat în memorie
adresă web
- Scanează cod de bare 2D
- Arată cod de bare 2D
+ Scanează cod QR
+ Arată cod QR
Listă contacte blocate
Detalii cont
Confirmă
@@ -625,7 +625,7 @@
Ai încredere în toate dispozitivele noi ale contactelor care nu au fost verificate anterior, dar cere confirmarea manuală pentru dispozitivele noi ale contactelor verificate.
Încredere oarbă în aceste chei OMEMO, aceasta înseamnă că ar putea fi altcineva sau cineva și-a strecurat propriile chei.
De neîncredere
- Cod de bare 2D invalid
+ Cod QR invalid
Curățare dosar temporar (folosit de aplicația cameră foto)
Curăța memoria temporară
Curăță stocarea privată
@@ -1036,10 +1036,14 @@
V-ați deconectat de la acest cont
Conectați-vă
Ascunde notificare
- Persoana de contact utilizează dispozitive neverificate. Scanați codul de bare 2D al acestora pentru a efectua verificarea și a împiedica atacurile MITM active.
+ Persoana de contact utilizează dispozitive neverificate. Scanați codul QR al acestora pentru a efectua verificarea și a împiedica atacurile MITM active.
Deconectare
Deconectat
- Folosiți dispozitive neverificate. Scanați codul de bare 2D pe celelalte dispozitive pentru a efectua verificarea și a împiedica atacurile MITM active.
+ Folosiți dispozitive neverificate. Scanați codul QR pe celelalte dispozitive pentru a efectua verificarea și a împiedica atacurile MITM active.
Raportează spam și blochează spamerul
Raportează spam
+ Bine ați venit la Quicksy!
+ Quicksy vă solicită consimțământul pentru a utiliza datele dumneavoastră
+ Integrarea agendei nu este disponibilă
+ Politica de confidențialitate
\ No newline at end of file
diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml
index d6e0c971456645849b06fcc634cf4e64973a15d4..285426a525a13ad4d01e3d82a912af26d11ba612 100644
--- a/src/main/res/values-ru/strings.xml
+++ b/src/main/res/values-ru/strings.xml
@@ -55,7 +55,7 @@
Вы хотите удалить %s из избранного? Беседы, связанные с данной закладкой, будут сохранены.
Создать новую учётную запись на сервере
Изменить пароль на сервере
- Поделиться с
+ Поделиться с…
Начать беседу
Пригласить контакт
Пригласить
@@ -88,7 +88,9 @@
Очистить историю
Вы хотите удалить все сообщения в этой беседе?\n\nВнимание: Данная операция не повлияет на сообщения, хранящиеся на других устройствах или серверах.
Удалить файл
- Вы уверены, что хотите удалить этот файл?\n\nПредупреждение: Данная операция не удалит копии этого файла, хранящиеся на других устройствах или серверах.
+ Вы уверены, что хотите удалить этот файл?
+\n
+\nПредупреждение: Данная операция не удалит копии этого файла, хранящиеся на других устройствах или серверах.
Закрыть эту беседу
Выберите устройство
Нешифрованное сообщение
@@ -97,7 +99,7 @@
OMEMO-зашифр. сообщение
v\\OMEMO-зашифр. сообщение
OpenPGP зашифр. сообщение
- Имя уже используется
+ Используется новое имя
Отправить в незашифрованном виде
Расшифровка не удалась. Вероятно, что у вас нет надлежащего ключа.
Установите OpenKeychain
@@ -128,7 +130,7 @@
Время, на которое уведомления будут отключены, когда вы пользуетесь аккаунтом на другом устройстве.
Дополнительно
Не отправлять отчёты об ошибках
- Отправляя отчеты об ошибках, вы помогаете разработке этого приложения
+ Отправляя отчёты об ошибках, вы помогаете разработке Quicksy
Отчёты о получении
Позволяет вашим контактам видеть, когда вы получили и прочитали их сообщения
Запретить скриншоты
@@ -314,8 +316,8 @@
XMPP-адрес скопирован в буфер обмена
Сообщение об ошибке скопировано в буфер обмена
веб-адрес
- Сканировать 2D штрихкод
- Показать 2D штрихкод
+ Сканировать QR-код
+ Показать QR-код
Показать чёрный список
Сведения об учётной записи
Подтвердить
@@ -512,7 +514,10 @@
Предоставить %1$s разрешение на использование внешнего накопителя
Предоставить %1$s разрешение на использование камеры
Синхронизировать с контактами
- %1$s нужно разрешение на доступ к контактам, чтобы соотнести их с вашими XMPP-контактами.\nЭто позволит отобразить полные имена и аватары контактов.\n\n%1$s сделает это локально, без отправки чего-либо на ваш сервер.
+ %1$s нужно разрешение на доступ к контактам, чтобы соотнести их с вашими XMPP-контактами.
+\nЭто позволит отобразить полные имена и аватары контактов.
+\n
+\n%1$s сделает это локально, без отправки чего-либо на ваш сервер.
Все сообщения
Уведомлять только при упоминании
Уведомления выключены
@@ -523,7 +528,9 @@
Только большие изображения
Оптимизации энергопотребления разрешены
Ваше устройство использует агрессивную оптимизацию энергопотребления %1$s, что может привести к задержке уведомлений и даже потере сообщений.\nРекомендуем её отключить.
- Ваше устройство использует агрессивную оптимизацию энергопотребления %1$s, что может привести к задержке уведомлений и даже потере сообщений.\nСейчас появится предложение её отключить.
+ Ваше устройство использует агрессивную оптимизацию энергопотребления %1$s, что может привести к задержке уведомлений и даже потере сообщений.
+\n
+\nСейчас появится предложение её отключить.
Запретить
Выбранная область слишком большая
(Нет активированных учётных записей)
@@ -532,7 +539,7 @@
Отправить исправленное сообщение
Вы уже пометили отпечаток этого человека как доверенный. Выбрав \"Готово\", вы только подтвердите, что %s является участником конференции.
Вы отключили эту учётную запись
- Ошибка безопасности: недействительный доступ к файлу
+ Ошибка безопасности: недействительный доступ к файлу!
Не найдено приложения для передачи URI
Отправить URI…
Согласиться и продолжить
@@ -543,7 +550,7 @@
Использовать свой провайдер
Выберите имя пользователя
Управлять доступностью вручную
- Устанавливать свою доступность при редактировании статусного сообщения
+ Устанавливать свою доступность при редактировании статусного сообщения.
Статусное собщение
Свободен для общения
В сети
@@ -610,10 +617,10 @@
Автоматически доверять всем новым устройствам контактов, которые не были подтверждены ранее, но запрашивать ручное подтверждение каждый раз, когда подтвержденный контакт добавляет новое устройство.
Принятие OMEMO-ключей вслепую. Это означает, что собеседник может оказаться недоверенным лицом.
Недоверенный
- Некорректный 2D штрихкод
+ Некорректный QR-код
Очистить кэш (используется камерой)
Очистить кэш
- Очистить приватное хранилище.
+ Очистить приватное хранилище
Очистить закрытое хранилище, где хранятся файлы (Файлы можно заново скачать с сервера)
Открывать ссылки из надёжного источника
Вы подтверждаете OMEMO-ключи %1$s после нажатия на ссылку. Это безопасно только если вы перешли по ссылке из доверенного источника, где только %2$s мог разместить эту ссылку.
@@ -621,7 +628,8 @@
Показывать неактивные
Скрыть неактивные
Прекратить доверять устройству
- Вы действительно хотите удалить устройство из доверенных?\Устройство и сообщения, полученные с этого устройства, будут помечаться как недоверенные.
+ Вы действительно хотите удалить устройство из доверенных?
+\nЭто устройство и сообщения, полученные с него, будут помечаться как недоверенные.
- %d секунда
- %d секунды
@@ -664,7 +672,7 @@
Не загружаем сообщения, в соответствии с локальным сроком хранения.
Сжимание видео
Соответствующие беседы закрыты.
- Контакт заблокирован
+ Контакт заблокирован.
Уведомления от неизвестных контактов
Уведомлять о сообщениях и звонках от незнакомых контактов.
Получено сообщение от неизвестного контакта
@@ -825,7 +833,7 @@
Пожалуйста, попробуйте еще раз через %s
У вас есть ограничение скорости
Слишком много попыток
- Вы используете устаревшую версию приложения
+ Вы используете устаревшую версию этого приложения.
Обновить
Этот номер телефона в данный момент авторизирован на другом устройстве.
Пожалуйста, введите ваше имя, чтобы другие люди, у которых нет вас в списке контактов, знали кто вы.
@@ -863,7 +871,7 @@
Этот канал уже существует
Вы присоединились к существующему каналу
Не удалось сохранить настройки канала
- Разрешить всем редактировать тему.
+ Разрешить всем редактировать тему
Разрешить всем приглашать других
Кто угодно может редактировать тему.
Владельцы могут редактировать тему.
@@ -991,7 +999,7 @@
Переподключение
Адрес XMPP не найден
Учётная запись XMPP
- Вы покинули эту беседу из-за технических причин
+ Вы покинули данный групповой чат по техническим причинам
Добавить дополнительные треки\?
Переподключение к звонку
Переподключение к видеовызову
@@ -1008,7 +1016,7 @@
Синхронизировать закладки
Устанавливать флаг \"автоприсоединение\" при входе в- и выходе из MUC, и реагировать на изменения от других клиентов.
Поиск по групповым беседам
- Загрузка провалена: неверный файл
+ Загрузка неудачна: Неизвестный файл
Перейти на видеовызов\?
Исходящий вызов (%s) · %s
Входящий вызов (%s) · %s
@@ -1038,10 +1046,10 @@
Вы вышли из этой учётной записи
Войти
Скрыть уведомление
- Ваш контакт использует неподтверждённые устройства. Отсканируйте его штрих-код для проверки и предотвращения атаки посредника.
+ Ваш контакт использует неподтверждённые устройства. Отсканируйте его QR-код для проверки и предотвращения атаки посредника.
Выйти
Деавторизован
- Вы используете неподтверждённые устройства. Отсканируйте штрих-код на подтверждённом устройстве для проверки и предотвращения атаки посредника.
+ Вы используете неподтверждённые устройства. Отсканируйте QR-код на подтверждённом устройстве для проверки и предотвращения атаки посредника.
Пожаловаться на спам и заблокировать
Пожаловаться на спам
\ No newline at end of file
diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml
index 29939384d39d48ddf727454defed9c1651887b81..3b252cc4851ed9a056672f17de50c4a5d901648b 100644
--- a/src/main/res/values-sr/strings.xml
+++ b/src/main/res/values-sr/strings.xml
@@ -31,13 +31,8 @@
пре %d минута
- %d непрочитана порука
-
-
- %d непрочитане поруке
-
-
- %d непрочитаних порука
-
шаљем…
Дешифрујем поруку, сачекајте…
@@ -682,4 +677,4 @@
Тихе поруке
Видео компресија
Заузет
-
+
\ No newline at end of file
diff --git a/src/main/res/values-szl/strings.xml b/src/main/res/values-szl/strings.xml
index 5312cccbca1a6436bfad07662d9d3a8ba6b76ff5..15b35c02b1d7f479e4c00cef40e866d25d5e9a9b 100644
--- a/src/main/res/values-szl/strings.xml
+++ b/src/main/res/values-szl/strings.xml
@@ -31,13 +31,8 @@
%d minut tymu
- %d niyprzeczytano kōnwersacyjo
-
-
- %d niyprzeczytane kōnwersacyje
-
-
- %d niyprzeczytanych kōnwersacyji
-
wysyłanie…
Ôdszyfrowowanie wiadōmości. To weźnie ino chwila…
@@ -1008,4 +1003,4 @@
Dokumynt ze samym tekstym
Registracyjo kōnt niy je spiyrano
Żodno adresa XMPP niyznojdziōno
-
+
\ No newline at end of file
diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml
index 256b90b521b9b7a7ed7bf82f686e23fb20c2cf69..60b4a1b726632517bba706162068c7c02d8a9f57 100644
--- a/src/main/res/values-uk/strings.xml
+++ b/src/main/res/values-uk/strings.xml
@@ -507,7 +507,7 @@
Зображення надіслано %s
Зображення надіслано %s
Текст надіслано %s
- Синхронізувати контакти
+ Інтеграція зі списком контактів
Сповіщати про всі повідомлення
Сповіщати, лише якщо згадують
Сповіщення вимкнено
@@ -600,7 +600,7 @@
Автоматично довіряти всім новим пристроям співрозмовників, які ще не пройшли перевірки, запитувати підтвердження вручну щоразу, як перевірений контакт додає свій новий пристрій.
Ключі OMEMO, яким Ви довіряєте наосліп, тобто співрозмовник може бути не тим, кому Ви довіряєте.
Недовірений
- Недійсний QR-код
+ Недійсний QR-код
Очистити теку з кешем (використовується застосунком Камера)
Очистити кеш
Очистити приватне сховище
@@ -989,10 +989,9 @@
Оберіть сервер для доставки push-повідомлень через XMPP на Ваш пристрій.
Застосунок для передачі зображення не надав достатніх дозволів.
Виклики вимкнені при використанні Tor
- %1$s потребує дозволу на доступ до контактів, щоб порівняти їх з Вашими XMPP-контактами.
-\nТаким чином можна буде показувати піктограми і повні імена користувачів.
+ %1$s обробляє Ваш список контактів локально, на Вашому пристрої, щоб показати імена та зображення профілю для відповідних контактів у XMPP.
\n
-\n%1$s лише прочитає Вашу адресну книгу і зіставлятиме інформацію про контакти локально, нічого не завантажуючи на сервер.
+\nУсі дані списку контактів залишаються на Вашому пристрої!
Пропущені виклики
Ваша операційна система обмежує для %1$s доступ до Інтернету у фоновому режимі. Щоб отримувати сповіщення про нові повідомлення, Вам потрібно дозволити %1$s необмежений доступ, коли заощадження трафіку увімкнено.
\n%1$s намагатиметься по можливості економити трафік.
@@ -1074,4 +1073,8 @@
Ви вийшли
Повідомити про спам і заблокувати спамера
Повідомити про спам
+ Вітаємо у Quicksy!
+ Quicksy просить згоду на використання Ваших даних
+ Політика конфіденційності
+ Інтеграція зі списком контактів недоступна
diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml
index c28babf65b505181fa4fd4058718fab8b6be0bbd..c9136ed9637822b20165c0477a8fd2b3c5c30e9e 100644
--- a/src/main/res/values-vi/strings.xml
+++ b/src/main/res/values-vi/strings.xml
@@ -507,7 +507,10 @@
Cấp quyền truy cập bộ nhớ cho %1$s
Cấp quyền truy cập máy ảnh cho %1$s
Đồng bộ với danh bạ
- %1$s muốn quyền truy cập sổ địa chỉ của bạn để nối nó với danh sách liên hệ XMPP của bạn.\nViệc này sẽ hiển thị họ tên và ảnh đại diện của các liên hệ của bạn.\n\n%1$s sẽ chỉ đọc sổ địa chỉ của bạn và nối nó một cách cục bộ mà không tải gì cả lên máy chủ của bạn.
+ %1$s muốn quyền truy cập sổ địa chỉ của bạn để nối nó với danh sách liên hệ XMPP của bạn.
+\nViệc này sẽ hiển thị họ tên và ảnh đại diện của các liên hệ của bạn.
+\n
+\n%1$s sẽ chỉ đọc sổ địa chỉ của bạn và nối nó một cách cục bộ mà không tải gì cả lên máy chủ của bạn.
Thông báo tất cả tin nhắn
Chỉ thông báo khi được nhắc đến
Đã tắt thông báo
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index 0890e3f5a128505016f1f4c69437fe74e9cb3751..f5400abbc08300c3d4a293d09eb6f7cf19609544 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -554,7 +554,7 @@
此字段是必需的
更正消息
发送更正后的消息
- 您已经验证了此用户的指纹。选择“完成”确认 %s 是此群聊的一员。
+ 您已信任此人的指纹。选择“完成”即表示您确认 %s 是此群聊的一员。
您已禁用了此账号
安全错误:文件访问无效!
未找到可以分享 URI 的应用
@@ -1032,4 +1032,8 @@
您正在使用未经验证的设备。扫描您其他设备的二维码进行验证并阻止主动式中间人攻击。
报告垃圾消息并屏蔽垃圾消息发送者
报告垃圾消息
+ 欢迎使用 Quicksy!
+ Quicksy 请求您同意使用您的数据
+ 隐私政策
+ 通讯录集成不可用
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 86552c0cf7bfd0743a4dae64b9a2d0697b0c4a34..157e29a2d71e4b3bd8fa25a5cd433d1c63585860 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -518,9 +518,10 @@
Text shared with %s
Grant %1$s access to external storage
Grant %1$s access to the camera
- Synchronize with contacts
- %1$s wants permission to access your address book to match it with your Jabber contact list.\nThis will display your contacts’ full names and avatars.\n\n%1$s will only read your address book and match it locally without uploading anything to your server.
-
We will not store a copy of those phone numbers.\n\nFor more information read our privacy policy.
You will now be asked to grant permission to access your contacts.]]>
+ Quicksy asks for your consent to use your data
+ Contact list integration
+ %1$s processes your contact list locally, on your device, to show you the names and profile pictures for matching contacts on the Jabber network.\n\nNo contact list data ever leaves your device!
+
Find more information in our Privacy Policy.]]>
Notify on all messages
Notify only when mentioned
Notifications disabled
@@ -546,8 +547,8 @@
No app found to share URI
Share URI with…
Join the Conversation
- Have some Quick Conversations
-
You sign up with your phone number and Quicksy will automatically—based on the phone numbers in your address book—suggest possible contacts to you.
Quicksy stores your contacts’ phone numbers to make suggestions about possible contacts who are already on Quicksy.
By signing up you agree to our Privacy Policy and our Terms & Conditions.]]>
+ Welcome to Quicksy!
+ · Quicksy shares and stores images, audio recordings, videos and other media to deliver them to the intended recipients. Files will be stored for up to 30 days.
Find more information in our Privacy Policy.]]>
Agree and continue
A guide is set up for account creation on ChatterboxTown.\nYou will be able to communicate with users of other providers by giving them your full Jabber ID.
Your full Jabber ID will be: %s
@@ -1030,4 +1031,6 @@
You are using unverified devices. Scan the QR Code on your other devices to perform verification and impede active MITM attacks.
Report spam
Report spam and block spammer
+ Privacy policy
+ Contact list integration is not available
diff --git a/src/playstore/AndroidManifest.xml b/src/playstore/AndroidManifest.xml
index 402d957f48c83d0bc620353eb3af4e8fb5cb003f..07ac0123486062b91707bb50a29a83b5980b7c49 100644
--- a/src/playstore/AndroidManifest.xml
+++ b/src/playstore/AndroidManifest.xml
@@ -1,6 +1,10 @@
+
+
+
+
+
+
+
Zeitspanne, in der Quicksy still bleibt, nachdem es Aktivitäten auf einem anderen Gerät erkannt hat
- Mit dem Einsenden von Absturzberichten hilfst du bei der Weiterentwicklung von Quicksy
+ Durch das Einsenden von Absturzberichten hilfst du bei der Weiterentwicklung von Quicksy
Informiere deine Kontakte, wann du Quicksy nutzt
Um weiterhin Benachrichtigungen zu erhalten, auch wenn der Bildschirm ausgeschaltet ist, musst du Quicksy zur Liste der geschützten Apps hinzufügen.
Quicksy Profilbild
diff --git a/src/quicksy/res/values-ja/strings.xml b/src/quicksy/res/values-ja/strings.xml
index d846cd3dfe8f5d5cc92bfb668777c6893756acb2..fb77f0f19df2edeb7ee129d97e3df33d0d67b369 100644
--- a/src/quicksy/res/values-ja/strings.xml
+++ b/src/quicksy/res/values-ja/strings.xml
@@ -1,7 +1,7 @@
別のデバイスで活動を見た後、Quicksy を静かにする時間の長さ
- スタックトレースを送信することで、あなたは Quicksy の継続的な開発を支援しています
+ スタックトレースを送信すると、 Quicksyの開発の助けとなります
Quicksy を使用するときに、すべての連絡先に知らせましょう
画面がオフになっている場合でも通知を受信し続けるには、保護されたアプリのリストに Quicksy を追加する必要があります。
Quicksy プロフィール写真
@@ -9,4 +9,4 @@
サーバーの同一性を確認できません。
未知のセキュリティエラー。
サーバーへの接続中にタイムアウトが発生しました。
-
+
\ No newline at end of file
diff --git a/src/quicksy/res/values-ru/strings.xml b/src/quicksy/res/values-ru/strings.xml
index c24ce397397c312b29db8c921cbe2e75e444fff4..698c3bd29c6851fa82b900d023d58605f83a58a9 100644
--- a/src/quicksy/res/values-ru/strings.xml
+++ b/src/quicksy/res/values-ru/strings.xml
@@ -4,7 +4,7 @@
Отправляя отчёты об ошибках, вы помогаете в разработке Quicksy
Извещать собеседников, когда вы пользуетесь Quicksy
Чтобы продолжать получать уведомления, даже если экран выключен, вам необходимо добавить Quicksy в список защищенных приложений.
- Аватар профиля Quicksy
+ Аватар Quicksy
Quicksy недоступен в Вашем регионе.
Не удалось подтвердить сервер.
Неизвестная ошибка безопасности.