diff --git a/.woodpecker.yml b/.woodpecker.yml index 2de87b7074700157d3e41ede0cec2198884abc3c..affaf71106c7209b45473f9a549d38958b1fa5ac 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -1,4 +1,4 @@ -pipeline: +steps: build: image: codeberg.org/freeyourgadget/android-fdroid-tools:latest commands: diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ea5fd8dd65b24aa694a79533fba60e92aed3425..a105ca8b64518f3655232fbcbda1442ae2482769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +### Version 2.12.12 + +* Support Private DNS (DNS over TLS) +* Support themed launcher icon +* Fix rare permission issue when sharing files on Android 11+ + +### Version 2.12.11 + +* Bump libwebrtc dependency to M117 and bump libvpx +* Go back to AAC for voice messages +* Support per app language settings + +### Version 2.12.10 + +* support per conversation notification settings +* use opus for voice messages on Android 10 + +### Version 2.12.9 + +* Introduce new backup file format + +### Version 2.12.8 + +* Disable opening backup files (.ceb) from file manager + +### Version 2.12.7 + +* Remove channel discovery feature from Google Play version + ### Version 2.12.6 * Fix 'q' falsely being recognized as cyrillic diff --git a/build.gradle b/build.gradle index 7d517e3d549328fc30209bf703ef82817ce0c399..853116c7c5950ffd8c61cdcd31ab3dbde9615f53 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:8.2.0-rc01' } } @@ -37,17 +37,6 @@ configurations { } dependencies { - constraints { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") { - because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib") - } - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") { - because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib") - } - } - - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - androidTestImplementation 'tools.fastlane:screengrab:2.1.1' androidTestImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.3.0' @@ -56,9 +45,11 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' implementation "androidx.core:core:1.10.1" + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' + implementation 'androidx.viewpager:viewpager:1.0.0' - playstoreImplementation('com.google.firebase:firebase-messaging:23.1.2') { + playstoreImplementation('com.google.firebase:firebase-messaging:23.3.0') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' @@ -73,10 +64,10 @@ dependencies { implementation 'androidx.exifinterface:exifinterface:1.3.6' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' - implementation 'com.google.android.material:material:1.8.0' + implementation 'com.google.android.material:material:1.10.0' - implementation "androidx.emoji2:emoji2:1.2.0" - freeImplementation "androidx.emoji2:emoji2-bundled:1.2.0" + implementation "androidx.emoji2:emoji2:1.4.0" + freeImplementation "androidx.emoji2:emoji2-bundled:1.4.0" implementation 'org.bouncycastle:bcmail-jdk15on:1.64' //zxing stopped supporting Java 7 so we have to stick with 3.3.3 @@ -104,8 +95,8 @@ dependencies { implementation "com.squareup.retrofit2:converter-gson:2.9.0" implementation "com.squareup.okhttp3:okhttp:4.10.0" - implementation 'com.google.guava:guava:31.1-android' - implementation 'io.michaelrocks:libphonenumber-android:8.12.49' + implementation 'com.google.guava:guava:32.1.3-android' + implementation 'io.michaelrocks:libphonenumber-android:8.13.17' implementation 'io.github.nishkarsh:android-permissions:2.1.6' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.documentfile:documentfile:1.0.1' @@ -131,11 +122,11 @@ ext { android { namespace 'eu.siacs.conversations' - compileSdkVersion 33 + compileSdkVersion 34 defaultConfig { minSdkVersion 21 - targetSdkVersion 33 + targetSdkVersion 34 versionCode 42025 + tags.size() versionName grgit.describe(always: true) applicationId "eu.siacs.conversations" @@ -163,8 +154,8 @@ android { compileOptions { coreLibraryDesugaringEnabled true - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } flavorDimensions("mode", "distribution") @@ -296,7 +287,10 @@ android { } } lint { - disable 'MissingTranslation', 'InvalidPackage', 'AppCompatResource', 'ExtraTranslation' + disable 'MissingTranslation', 'InvalidPackage', 'AppCompatResource' + } + buildFeatures { + buildConfig true } android.applicationVariants.all { variant -> diff --git a/docs/user/migrating_to_new_device.md b/docs/user/migrating_to_new_device.md index 401a153867a4bd4286b6091e23d90a2a9267d373..e7d50a1ce3ea58e9c573a7c08bfb251a27aec686 100644 --- a/docs/user/migrating_to_new_device.md +++ b/docs/user/migrating_to_new_device.md @@ -22,12 +22,11 @@ This tutorial explains how you can transfer your Conversations data from an old ## 3. Import the backup (new device) 1. Install Conversations on your new device. 2. Open Conversations for the first time. -3. Tap on "Use other server" -4. Tap on the three dot menu in the upper right corner and tap on "Import backup" -5. If your backup files are not listed, tap on the cloud symbol in the upper right corner to choose the files from the where you saved them. -6. Enter your account password to decrypt the backup. -7. Remember to activate your account (head back to "manage accounts", see step 1.2). -8. Check if chats work. +3. Tap on the three dot menu in the upper right corner and tap on "Import backup" +4. If your backup files are not listed, tap on the cloud symbol in the upper right corner to choose the files from where you saved them. +5. Enter your account password to decrypt the backup. +6. Remember to activate your account (head back to "manage accounts", see step 1.2). +7. Check if chats work. Once confirmed that the new device is running fine you can just uninstall the app from the old device. diff --git a/fastlane/metadata/android/de-DE/changelogs/42061.txt b/fastlane/metadata/android/de-DE/changelogs/42061.txt new file mode 100644 index 0000000000000000000000000000000000000000..9673c61d81dc44634def6487dd3001c9f4e0b641 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/42061.txt @@ -0,0 +1 @@ +* Channelsuchfunktion aus der Google Play-Version entfernt diff --git a/fastlane/metadata/android/de-DE/changelogs/42062.txt b/fastlane/metadata/android/de-DE/changelogs/42062.txt new file mode 100644 index 0000000000000000000000000000000000000000..7b9eff49c210e7182be0df776699d5bdc48200ac --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/42062.txt @@ -0,0 +1 @@ +* Öffnen von Sicherungsdateien (.ceb) im Dateimanager deaktiviert diff --git a/fastlane/metadata/android/de-DE/changelogs/42065.txt b/fastlane/metadata/android/de-DE/changelogs/42065.txt new file mode 100644 index 0000000000000000000000000000000000000000..79d8f5738c2b40c86de1bcdb9dda9ed38dc72aae --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/42065.txt @@ -0,0 +1 @@ +* Einführung eines neuen Formats für Sicherungsdateien diff --git a/fastlane/metadata/android/de-DE/changelogs/42068.txt b/fastlane/metadata/android/de-DE/changelogs/42068.txt new file mode 100644 index 0000000000000000000000000000000000000000..8204a5fe55bdc2785ee47ea4f9d60b2e79a36cca --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/42068.txt @@ -0,0 +1,2 @@ +* Unterstützung der Benachrichtigungseinstellungen pro Unterhaltung +* Verwendung von Opus für Sprachnachrichten unter Android 10 diff --git a/fastlane/metadata/android/de-DE/changelogs/42072.txt b/fastlane/metadata/android/de-DE/changelogs/42072.txt new file mode 100644 index 0000000000000000000000000000000000000000..bc65048282234402dbc2590d8f159dcf1ef2eac3 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/42072.txt @@ -0,0 +1,3 @@ +* Änderung der libwebrtc-Abhängigkeit auf M117 und Änderung von libvpx +* Rückkehr zu AAC für Sprachnachrichten +* Unterstützung von Spracheinstellungen innerhalb einer App diff --git a/fastlane/metadata/android/de-DE/changelogs/42074.txt b/fastlane/metadata/android/de-DE/changelogs/42074.txt new file mode 100644 index 0000000000000000000000000000000000000000..ad343db6642552c9efa59390ee55e19bb36f82e7 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/42074.txt @@ -0,0 +1,3 @@ +* Unterstützung von Private DNS (DNS über TLS) +* Unterstützung für designbasiertes Startsymbol +* Behebt ein seltenes Berechtigungsproblem beim Teilen von Dateien unter Android 11+ diff --git a/fastlane/metadata/android/de-DE/changelogs/4207704.txt b/fastlane/metadata/android/de-DE/changelogs/4207704.txt new file mode 100644 index 0000000000000000000000000000000000000000..61579ecd3d410588b618fb78ebaafe89b5d21942 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4207704.txt @@ -0,0 +1,3 @@ +* Unterstützung von Private DNS (DNS über TLS) +* Unterstützung von designbezogenem Startsymbol +* Behebt ein seltenes Berechtigungsproblem beim Teilen von Dateien unter Android 11+ diff --git a/fastlane/metadata/android/en-US/changelogs/42061.txt b/fastlane/metadata/android/en-US/changelogs/42061.txt new file mode 100644 index 0000000000000000000000000000000000000000..0475d110d6d020a22c026f1d14a70d8e100b5a3f --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/42061.txt @@ -0,0 +1 @@ +* Remove channel discovery feature from Google Play version diff --git a/fastlane/metadata/android/en-US/changelogs/42062.txt b/fastlane/metadata/android/en-US/changelogs/42062.txt new file mode 100644 index 0000000000000000000000000000000000000000..833c320e12ddb517addfd191952291d206021129 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/42062.txt @@ -0,0 +1 @@ +* Disable opening backup files (.ceb) from file manager diff --git a/fastlane/metadata/android/en-US/changelogs/42065.txt b/fastlane/metadata/android/en-US/changelogs/42065.txt new file mode 100644 index 0000000000000000000000000000000000000000..9b314f571ffaf37ba9a6c5874c68bbd69abc063d --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/42065.txt @@ -0,0 +1 @@ +* Introduce new backup file format diff --git a/fastlane/metadata/android/en-US/changelogs/42068.txt b/fastlane/metadata/android/en-US/changelogs/42068.txt new file mode 100644 index 0000000000000000000000000000000000000000..1ddfe9ea585761469540b5bd807c804dc6ca2180 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/42068.txt @@ -0,0 +1,2 @@ +* support per conversation notification settings +* use opus for voice messages on Android 10 diff --git a/fastlane/metadata/android/en-US/changelogs/42072.txt b/fastlane/metadata/android/en-US/changelogs/42072.txt new file mode 100644 index 0000000000000000000000000000000000000000..6a84b948963764d57c9b9a886a2d9e9bffa1b993 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/42072.txt @@ -0,0 +1,3 @@ +* Bump libwebrtc dependency to M117 and bump libvpx +* Go back to AAC for voice messages +* Support per app language settings diff --git a/fastlane/metadata/android/en-US/changelogs/4207704.txt b/fastlane/metadata/android/en-US/changelogs/4207704.txt new file mode 100644 index 0000000000000000000000000000000000000000..6b19df3eba90fc207c46cd74a0dfbd045f1c16ad --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4207704.txt @@ -0,0 +1,3 @@ +* Support Private DNS (DNS over TLS) +* Support themed launcher icon +* Fix rare permission issue when sharing files on Android 11+ diff --git a/fastlane/metadata/android/es-ES/changelogs/349.txt b/fastlane/metadata/android/es-ES/changelogs/349.txt new file mode 100644 index 0000000000000000000000000000000000000000..8f84c2432a8f5572a3cde016fbfbe72ce2e4f5e5 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/349.txt @@ -0,0 +1,4 @@ +* Introducir configuración experta para realizar el descubrimiento de canales en el servidor local en lugar de search.jabber.network +* Habilitar las marcas de verificación de entrega por defecto y eliminar la configuración +* Habilitar «Enviar botón indica estado» por defecto y eliminar la configuración +* Mover los ajustes de copia de seguridad y servicio en primer plano a la pantalla principal diff --git a/fastlane/metadata/android/es-ES/changelogs/351.txt b/fastlane/metadata/android/es-ES/changelogs/351.txt new file mode 100644 index 0000000000000000000000000000000000000000..a89b01aee6505af54f92c76e7183b287ff047058 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/351.txt @@ -0,0 +1,3 @@ +* Corrección de la transferencia de archivos Jingle IBB +* Corrección de correcciones repetidas que llenaban la base de datos. +* Transición a Last Message Correction v1.1 diff --git a/fastlane/metadata/android/es-ES/changelogs/353.txt b/fastlane/metadata/android/es-ES/changelogs/353.txt new file mode 100644 index 0000000000000000000000000000000000000000..e0f55a6b1419aa1f252b29f9b6d77217e992d0d4 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/353.txt @@ -0,0 +1,4 @@ +* Permitir a los usuarios establecer su propio apodo +* reanudar la descarga de archivos encriptados OMEMO +* Los canales ahora usan '#' como símbolo en el avatar +* Quicksy utiliza «siempre» como cifrado OMEMO por defecto (oculta el icono del candado) diff --git a/fastlane/metadata/android/es-ES/changelogs/360.txt b/fastlane/metadata/android/es-ES/changelogs/360.txt new file mode 100644 index 0000000000000000000000000000000000000000..169ae4b3e3447b52deee2519509385bbb6d2d37c --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/360.txt @@ -0,0 +1 @@ +* Soporte para los parámetros URI de XMPP «?register» y «?register;preauth» diff --git a/fastlane/metadata/android/es-ES/changelogs/362.txt b/fastlane/metadata/android/es-ES/changelogs/362.txt new file mode 100644 index 0000000000000000000000000000000000000000..bd88ea0f41134419511f0d10e29e3b3679536bd4 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/362.txt @@ -0,0 +1 @@ +* Soporte para el cambio automático de tema en Android 10 diff --git a/fastlane/metadata/android/es-ES/changelogs/364.txt b/fastlane/metadata/android/es-ES/changelogs/364.txt new file mode 100644 index 0000000000000000000000000000000000000000..cc2be8a4dad152baced746fcdc50f465d97fcf16 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/364.txt @@ -0,0 +1,2 @@ +* Proporcionar vista previa de PDF en Android 5+ +* Utilizar IVs de 12 bytes para OMEMO diff --git a/fastlane/metadata/android/es-ES/changelogs/367.txt b/fastlane/metadata/android/es-ES/changelogs/367.txt new file mode 100644 index 0000000000000000000000000000000000000000..e95c61e6b2ff254a1aa09fd6446b785084bc33b5 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/367.txt @@ -0,0 +1,2 @@ +* Corregir la selección de avatar en algunos dispositivos Android 10 +* Corregir la transferencia de archivos más grandes diff --git a/fastlane/metadata/android/es-ES/changelogs/379.txt b/fastlane/metadata/android/es-ES/changelogs/379.txt new file mode 100644 index 0000000000000000000000000000000000000000..74870d83fe0cf358107b5d7b2818cc267fd586db --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/379.txt @@ -0,0 +1 @@ +* Llamadas de audio/vídeo (requiere soporte de servidor en forma de servidores STUN y TURN detectables mediante XEP-0215) diff --git a/fastlane/metadata/android/es-ES/changelogs/381.txt b/fastlane/metadata/android/es-ES/changelogs/381.txt new file mode 100644 index 0000000000000000000000000000000000000000..44512a40d64adf1675711671b03d556e60e3dabe --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/381.txt @@ -0,0 +1,2 @@ +* Respuesta audible (marcación, llamada iniciada, llamada finalizada) para llamadas de voz. +* Solucionado el problema de reintento de videollamada fallida diff --git a/fastlane/metadata/android/es-ES/changelogs/382.txt b/fastlane/metadata/android/es-ES/changelogs/382.txt new file mode 100644 index 0000000000000000000000000000000000000000..8421f8e55454e43a3b8074617321d9977531705b --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/382.txt @@ -0,0 +1,2 @@ +* Añadir botón para cambiar de cámara durante la videollamada +* Corregidas las llamadas de voz en tablets diff --git a/fastlane/metadata/android/es-ES/changelogs/383.txt b/fastlane/metadata/android/es-ES/changelogs/383.txt new file mode 100644 index 0000000000000000000000000000000000000000..189cd9c75b4fc0083de3338301f4218336cffd3b --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/383.txt @@ -0,0 +1,3 @@ +* Mover el icono de llamada a la izquierda para mantener otros iconos de la barra de herramientas en un lugar coherente. +* Mostrar la duración de la llamada durante las llamadas de audio +* Desempate en las llamadas A/V (dos personas que se llaman al mismo tiempo) diff --git a/fastlane/metadata/android/es-ES/changelogs/387.txt b/fastlane/metadata/android/es-ES/changelogs/387.txt new file mode 100644 index 0000000000000000000000000000000000000000..28af6206bf871756e3bca6286d84b961fbdb6f60 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/387.txt @@ -0,0 +1,2 @@ +* Reestructuración de la interfaz de inicio de sesión con certificado +* Añadir la posibilidad de anclar chats en la parte superior (añadir a favoritos) diff --git a/fastlane/metadata/android/es-ES/changelogs/388.txt b/fastlane/metadata/android/es-ES/changelogs/388.txt new file mode 100644 index 0000000000000000000000000000000000000000..cd381e62a9957bc0196d55ce1c10ab2eda8d0835 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/388.txt @@ -0,0 +1,3 @@ +* Reducir el eco durante las llamadas en algunos dispositivos +* Arreglar el inicio de sesión cuando las contraseñas contienen caracteres especiales +* Reproducir tonos de marcado y ocupado en el altavoz durante las videollamadas diff --git a/fastlane/metadata/android/es-ES/changelogs/390.txt b/fastlane/metadata/android/es-ES/changelogs/390.txt new file mode 100644 index 0000000000000000000000000000000000000000..7f1ba8d5ec605d2cad149f4caee7f71ba67df2a8 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/390.txt @@ -0,0 +1 @@ +* Ofrecer la grabación de un mensaje de voz cuando la persona que llama está ocupada diff --git a/fastlane/metadata/android/es-ES/changelogs/393.txt b/fastlane/metadata/android/es-ES/changelogs/393.txt new file mode 100644 index 0000000000000000000000000000000000000000..e6f08c87a32510cf43ba9e8362bfdad56c4aaaf3 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/393.txt @@ -0,0 +1,3 @@ +* Mostrar botón de ayuda si falla la llamada A/V +* Corregidos algunos fallos molestos +* Corregidas conexiones Jingle (transferencia de archivos + llamadas) con JIDs sin ocultar diff --git a/fastlane/metadata/android/es-ES/changelogs/394.txt b/fastlane/metadata/android/es-ES/changelogs/394.txt new file mode 100644 index 0000000000000000000000000000000000000000..72b50cdfb4264f4c4a291a2aa09510d812c35a9c --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/394.txt @@ -0,0 +1,2 @@ +* Se ha corregido el problema de las notificaciones que no aparecían en determinadas circunstancias. +* Se han solucionado problemas de compatibilidad y bloqueos relacionados con las llamadas A/V diff --git a/fastlane/metadata/android/es-ES/changelogs/395.txt b/fastlane/metadata/android/es-ES/changelogs/395.txt new file mode 100644 index 0000000000000000000000000000000000000000..7fd679913e3f9dcccc5fed68b11971f9dfa75335 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/395.txt @@ -0,0 +1,3 @@ +* Añadir 'Volver al chat' a la pantalla de llamada de audio +* Mejorar los atajos del teclado +* corrección de errores diff --git a/fastlane/metadata/android/es-ES/changelogs/397.txt b/fastlane/metadata/android/es-ES/changelogs/397.txt new file mode 100644 index 0000000000000000000000000000000000000000..5bf18c91d6c5d3b91bda415e7043625ebffbf7fe --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/397.txt @@ -0,0 +1,3 @@ +* Gestión de archivos GPX +* Mejorar el rendimiento de la restauración de las copias de seguridad +* Corrección de errores diff --git a/fastlane/metadata/android/es-ES/changelogs/398.txt b/fastlane/metadata/android/es-ES/changelogs/398.txt new file mode 100644 index 0000000000000000000000000000000000000000..b665a46bd3dc59ec372ad347b29d6f5002027281 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/398.txt @@ -0,0 +1,4 @@ +* Buscar conversaciones individuales +* Notificar al usuario si falla la entrega del mensaje +* Recordar los nombres de usuario (nicks) de los usuarios de Quicksy en los reinicios +* Añadir el botón para iniciar Orbot (Tor) desde la notificación si es necesario diff --git a/fastlane/metadata/android/es-ES/changelogs/401.txt b/fastlane/metadata/android/es-ES/changelogs/401.txt new file mode 100644 index 0000000000000000000000000000000000000000..f0818cbe911f2f3dd451c2798ab441368b0fa4ad --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/401.txt @@ -0,0 +1,2 @@ +* búsqueda fija en Android <= 5 +* optimizar el consumo de la memoria diff --git a/fastlane/metadata/android/es-ES/changelogs/402.txt b/fastlane/metadata/android/es-ES/changelogs/402.txt new file mode 100644 index 0000000000000000000000000000000000000000..079ced1977ffe9308f4cd533accd412cb884603a --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/402.txt @@ -0,0 +1,3 @@ +* Ofrece la generación de invitaciones fáciles en los servidores compatibles +* Mostrar GIFs enviados desde Movim +* Almacenar avatares en caché diff --git a/fastlane/metadata/android/es-ES/changelogs/403.txt b/fastlane/metadata/android/es-ES/changelogs/403.txt new file mode 100644 index 0000000000000000000000000000000000000000..3a4c6b698e98d84e924b4ce83b0b9ce58bca89bf --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/403.txt @@ -0,0 +1,3 @@ +* Corregidos problemas de conectividad cuando diferentes cuentas utilizaban diferentes mecanismos SCRAM. +* Añadir soporte para SCRAM-SHA-512 +* Permitir la transferencia de archivos P2P (Jingle) con autocontacto diff --git a/fastlane/metadata/android/es-ES/changelogs/404.txt b/fastlane/metadata/android/es-ES/changelogs/404.txt new file mode 100644 index 0000000000000000000000000000000000000000..c594e57cd305fb16177c03439d0917421c984a61 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/404.txt @@ -0,0 +1 @@ +* Pequeñas mejoras de estabilidad en las llamadas A/V diff --git a/fastlane/metadata/android/es-ES/changelogs/405.txt b/fastlane/metadata/android/es-ES/changelogs/405.txt new file mode 100644 index 0000000000000000000000000000000000000000..a341b6579445782c07dca1cc9511d9279357a621 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/405.txt @@ -0,0 +1 @@ +* Quicksy: Recibir automáticamente SMS de verificación diff --git a/fastlane/metadata/android/es-ES/changelogs/407.txt b/fastlane/metadata/android/es-ES/changelogs/407.txt new file mode 100644 index 0000000000000000000000000000000000000000..3958fd6efe69ad6e01b37a467fbe282aeae89fa9 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/407.txt @@ -0,0 +1,3 @@ +* Mostrar botón de llamada para contactos desconectados si previamente anunciaron soporte +* El botón Atrás ya no finaliza la llamada cuando está conectada. +* Corrección de errores diff --git a/fastlane/metadata/android/es-ES/changelogs/42000.txt b/fastlane/metadata/android/es-ES/changelogs/42000.txt new file mode 100644 index 0000000000000000000000000000000000000000..8ad2c54721419e91dee5587588580ce3402b91c8 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42000.txt @@ -0,0 +1,4 @@ +* Posibilidad de seleccionar el tono de la llamada entrante +* Corrección de la identificación de claves OpenPGP para OpenKeychain 5.6+. +* Verificación correcta de los certificados TLS punycode +* Mejora de la estabilidad del establecimiento de sesiones RTP (llamadas) diff --git a/fastlane/metadata/android/es-ES/changelogs/42006.txt b/fastlane/metadata/android/es-ES/changelogs/42006.txt new file mode 100644 index 0000000000000000000000000000000000000000..eb1ea92f4caac20aa4c3cb13c7f8aa13d8a15cbc --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42006.txt @@ -0,0 +1,2 @@ +* Verificar llamadas A/V con sesiones OMEMO preexistentes +* Mejorar la compatibilidad con implementaciones WebRTC no libwebrtc diff --git a/fastlane/metadata/android/es-ES/changelogs/42010.txt b/fastlane/metadata/android/es-ES/changelogs/42010.txt new file mode 100644 index 0000000000000000000000000000000000000000..41e1148ba2f83e18901a7553493b7d6e300747d6 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42010.txt @@ -0,0 +1,2 @@ +* Varias correcciones de errores en torno a la compatibilidad con Tor +* Mejora de la compatibilidad de llamadas con Dino diff --git a/fastlane/metadata/android/es-ES/changelogs/42012.txt b/fastlane/metadata/android/es-ES/changelogs/42012.txt new file mode 100644 index 0000000000000000000000000000000000000000..814a09be749803dfdd9b895f37c84d6e1008474c --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42012.txt @@ -0,0 +1 @@ +* Corrección de la carga/descarga HTTP para usuarios que no confían en las CA del sistema diff --git a/fastlane/metadata/android/es-ES/changelogs/42013.txt b/fastlane/metadata/android/es-ES/changelogs/42013.txt new file mode 100644 index 0000000000000000000000000000000000000000..e31ca2cab40b487e0fde4371c5f2368b3e48aa2a --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42013.txt @@ -0,0 +1 @@ +* Solucionados los problemas de 'No Conectividad' en Android 7.1 diff --git a/fastlane/metadata/android/es-ES/changelogs/42014.txt b/fastlane/metadata/android/es-ES/changelogs/42014.txt new file mode 100644 index 0000000000000000000000000000000000000000..a06654b642949531a46f8911640eb10951a0baaa --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42014.txt @@ -0,0 +1,2 @@ +* Verificar siempre el nombre del dominio. No sobrescribir al usuario +* Soporta pre autenticación de roster diff --git a/fastlane/metadata/android/es-ES/changelogs/42015.txt b/fastlane/metadata/android/es-ES/changelogs/42015.txt new file mode 100644 index 0000000000000000000000000000000000000000..ad8b35003e180e5a29de5342cd9160041de7be5f --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42015.txt @@ -0,0 +1 @@ +* pequeñas mejoras en A/V diff --git a/fastlane/metadata/android/es-ES/changelogs/42018.txt b/fastlane/metadata/android/es-ES/changelogs/42018.txt new file mode 100644 index 0000000000000000000000000000000000000000..25ec35a9bbdca35cf8b8b7bdeac90d3fd3560bdf --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42018.txt @@ -0,0 +1,3 @@ +* Mostrar barras negras cuando el vídeo remoto no coincide con la relación del aspecto de la pantalla. +* Mejorar el rendimiento de la búsqueda +* Añadir configuración para evitar capturas de pantalla diff --git a/fastlane/metadata/android/es-ES/changelogs/42022.txt b/fastlane/metadata/android/es-ES/changelogs/42022.txt new file mode 100644 index 0000000000000000000000000000000000000000..db5c79bc70617f5436e4f2863533dc52cbf7ccbf --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42022.txt @@ -0,0 +1,2 @@ +* Corrección de un problema que impedía comprimir algunos vídeos. +* Corrección de un fallo poco frecuente al abrir una notificación diff --git a/fastlane/metadata/android/es-ES/changelogs/42023.txt b/fastlane/metadata/android/es-ES/changelogs/42023.txt new file mode 100644 index 0000000000000000000000000000000000000000..0a1dd4ce35fb1ab7d0702918a4a6300df30a20c4 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42023.txt @@ -0,0 +1,2 @@ +* Corrección de fallos en la representación de algunas citas +* Corrección del fallo en la pantalla de bienvenida diff --git a/fastlane/metadata/android/es-ES/changelogs/42037.txt b/fastlane/metadata/android/es-ES/changelogs/42037.txt new file mode 100644 index 0000000000000000000000000000000000000000..b10095829a6ea900b935c5dd5e1046ddd9ebc865 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42037.txt @@ -0,0 +1,11 @@ +Versión 2.10.9 +* Pedir permisos Bluetooth al hacer llamadas A/V (Puede rechazar esto si no utiliza auriculares Bluetooth). +* Corrección de error al llamar a Movim +* Corregir avatar incorrecto que se muestra para los chats de grupo +* Preguntar siempre por las optimizaciones de batería +* Establecer sólo local bandera en 'x cuentas conectadas' notificaciones +* Corrección de la interacción con Google Maps Share Location Plugin +* Eliminar nota a pie de página con respecto a la cuota del servidor +* Almacenar archivos en la ubicación adecuada para Android 11 +* Intento de reconectar llamada tras cambio de red +* Mostrar el JID de la persona que llama y el JID de la cuenta en la pantalla de llamada entrante diff --git a/fastlane/metadata/android/es-ES/changelogs/42038.txt b/fastlane/metadata/android/es-ES/changelogs/42038.txt new file mode 100644 index 0000000000000000000000000000000000000000..0864d1d84de0dc282b087f519b63120fa95b030e --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42038.txt @@ -0,0 +1,2 @@ +* Corrección de errores menores +* Restaurar la capacidad de llamar a través de JMP y otros servicios (versión Playstore) diff --git a/fastlane/metadata/android/es-ES/changelogs/42041.txt b/fastlane/metadata/android/es-ES/changelogs/42041.txt new file mode 100644 index 0000000000000000000000000000000000000000..03c4e761ad434ce69cf9884a9edc226f792c6f59 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42041.txt @@ -0,0 +1,5 @@ +* Implementación del perfil SASL extensible, Bind 2.0 y Fast para reconexiones más rápidas. +* Implementación de Channel Binding +* Añadir la posibilidad de cambiar de llamada de audio a videollamada +* Añadir la posibilidad de eliminar el propio avatar +* Notificación de llamadas perdidas diff --git a/fastlane/metadata/android/es-ES/changelogs/42042.txt b/fastlane/metadata/android/es-ES/changelogs/42042.txt new file mode 100644 index 0000000000000000000000000000000000000000..288d6502a142a3914c927b0e15ff582a7a92f9db --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42042.txt @@ -0,0 +1,2 @@ +* Corrección del bucle de reenvío en servidores que sólo admiten sm:2 +* Mostrar "Cambiar a vídeo" sólo si la otra parte admite vídeo diff --git a/fastlane/metadata/android/es-ES/changelogs/42043.txt b/fastlane/metadata/android/es-ES/changelogs/42043.txt new file mode 100644 index 0000000000000000000000000000000000000000..131ea29115ca05802e526b71fb01c7a124fbb5dc --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42043.txt @@ -0,0 +1 @@ +* Corregida una regresión en la transferencia de archivos P2P diff --git a/fastlane/metadata/android/es-ES/changelogs/42044.txt b/fastlane/metadata/android/es-ES/changelogs/42044.txt new file mode 100644 index 0000000000000000000000000000000000000000..1b98bb2aaf4c6880e71016a4017db1a642ff1bc7 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42044.txt @@ -0,0 +1,3 @@ +* Corrección del reenvío de mensajes al utilizar SASL2 +* Corregir vídeo negro entre algunos dispositivos +* Arreglar fallo en contraseñas vacías diff --git a/fastlane/metadata/android/es-ES/changelogs/42046.txt b/fastlane/metadata/android/es-ES/changelogs/42046.txt new file mode 100644 index 0000000000000000000000000000000000000000..f1576f520239008da619c692ac069c38df904416 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42046.txt @@ -0,0 +1 @@ +* Integrar el Distribuidor UnifiedPush para facilitar los mensajes push a otras aplicaciones habilitadas para UnifiedPush como Tusky y Fedilab diff --git a/fastlane/metadata/android/es-ES/changelogs/42047.txt b/fastlane/metadata/android/es-ES/changelogs/42047.txt new file mode 100644 index 0000000000000000000000000000000000000000..8f3909b76ee4c9d95b11c185fe91332e7c09623b --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42047.txt @@ -0,0 +1 @@ +* Corrección de fallos en el distribuidor de UnifiedPush diff --git a/fastlane/metadata/android/es-ES/changelogs/42050.txt b/fastlane/metadata/android/es-ES/changelogs/42050.txt new file mode 100644 index 0000000000000000000000000000000000000000..fff8b62cb7cb55da3834f7fe55ca61db3aa70200 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42050.txt @@ -0,0 +1 @@ +* Aumentar el radio de las esquinas en las fotos de perfil diff --git a/fastlane/metadata/android/es-ES/changelogs/42059.txt b/fastlane/metadata/android/es-ES/changelogs/42059.txt new file mode 100644 index 0000000000000000000000000000000000000000..3a6ce0d00dd34fcbf40053e944df12cd736f32d7 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42059.txt @@ -0,0 +1,2 @@ +* Actualizar Target SDK a 33 de nuevo +* Corrección de problemas en servidores que soportan SASL2 sin Stream Management en línea diff --git a/fastlane/metadata/android/es-ES/changelogs/42060.txt b/fastlane/metadata/android/es-ES/changelogs/42060.txt new file mode 100644 index 0000000000000000000000000000000000000000..624c64e920e853c842572078205347bca0e0103e --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42060.txt @@ -0,0 +1 @@ +* Arreglar 'q' falsamente siendo reconocido como cirílico diff --git a/fastlane/metadata/android/es-ES/changelogs/42061.txt b/fastlane/metadata/android/es-ES/changelogs/42061.txt new file mode 100644 index 0000000000000000000000000000000000000000..da7a92ab9f014e0270b622fac5fc6ba02d50bc2f --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42061.txt @@ -0,0 +1 @@ +* Eliminar la función de descubrimiento de canales de la versión de Google Play diff --git a/fastlane/metadata/android/es-ES/changelogs/42062.txt b/fastlane/metadata/android/es-ES/changelogs/42062.txt new file mode 100644 index 0000000000000000000000000000000000000000..c1729884a7d61928ee850bd5ae87f8f239c4b10b --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42062.txt @@ -0,0 +1 @@ +* Desactivar la apertura de archivos de copia de seguridad (.ceb) desde el gestor de archivos diff --git a/fastlane/metadata/android/es-ES/changelogs/42065.txt b/fastlane/metadata/android/es-ES/changelogs/42065.txt new file mode 100644 index 0000000000000000000000000000000000000000..eceb051e140fe8510faa6ac6783d1095d5b2ec29 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42065.txt @@ -0,0 +1 @@ +* Introducir un nuevo formato de archivo de copia de seguridad diff --git a/fastlane/metadata/android/es-ES/changelogs/42068.txt b/fastlane/metadata/android/es-ES/changelogs/42068.txt new file mode 100644 index 0000000000000000000000000000000000000000..b651cc178a5f9c2db637dd310f86423c04b05e72 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42068.txt @@ -0,0 +1,2 @@ +* soporte para los ajustes de la notificación de la conversación +* usar opus para mensajes de voz en Android 10 diff --git a/fastlane/metadata/android/es-ES/changelogs/42072.txt b/fastlane/metadata/android/es-ES/changelogs/42072.txt new file mode 100644 index 0000000000000000000000000000000000000000..a4b7e971d43b55c2c136c4853a42925520c39ef3 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/42072.txt @@ -0,0 +1,3 @@ +* Aumenta la dependencia de libwebrtc a M117 y aumenta la de libvpx. +* Volver a AAC para mensajes de voz +* Soporta ajustes de idioma por aplicación diff --git a/fastlane/metadata/android/es-ES/changelogs/4207704.txt b/fastlane/metadata/android/es-ES/changelogs/4207704.txt new file mode 100644 index 0000000000000000000000000000000000000000..9396b343d9d84332bf9b194a38c5e543006cd82d --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/4207704.txt @@ -0,0 +1,3 @@ +* Soporta DNS Privado (DNS sobre TLS) +* Icono temático del lanzador +* Corrección de un problema de permisos poco frecuente al compartir archivos en Android 11+ diff --git a/fastlane/metadata/android/gl-ES/changelogs/349.txt b/fastlane/metadata/android/gl-ES/changelogs/349.txt new file mode 100644 index 0000000000000000000000000000000000000000..ce9204ef38ffdd8e454a46727697ad6d15a13595 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/349.txt @@ -0,0 +1,4 @@ +* Introdución do axuste de experta para realizar o descubrimento de canle no servidor local e non buscar en search.jabber.network +* Activadas as marcas de comprobación de entrega por defecto e eliminación do axuste +* Activar por defecto 'O botón enviar indica estado' e eliminar o axuste +* Mover os axustes Copia de Apoio e Servizo en primeiro plano á pantalla principal diff --git a/fastlane/metadata/android/gl-ES/changelogs/351.txt b/fastlane/metadata/android/gl-ES/changelogs/351.txt new file mode 100644 index 0000000000000000000000000000000000000000..8fabff2f1612516f2af7dd656d45bc8cbc417f79 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/351.txt @@ -0,0 +1,3 @@ +* fixes for Jingle IBB file transfer +* fixes for repeated corrections filling up the database +* switched to Last Message Correction v1.1 diff --git a/fastlane/metadata/android/gl-ES/changelogs/353.txt b/fastlane/metadata/android/gl-ES/changelogs/353.txt new file mode 100644 index 0000000000000000000000000000000000000000..63b829a805b5815fd806f397d60951d58e0a8ee8 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/353.txt @@ -0,0 +1,4 @@ +* let users set their own nick name +* resume download of OMEMO encrypted files +* Channels now use '#' as symbol in avatar +* Quicksy uses 'always' as OMEMO encryption default (hides lock icon) diff --git a/fastlane/metadata/android/gl-ES/changelogs/360.txt b/fastlane/metadata/android/gl-ES/changelogs/360.txt new file mode 100644 index 0000000000000000000000000000000000000000..87b92f0339c3bf13debb2a379f5bb23d9514e21c --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/360.txt @@ -0,0 +1 @@ +* Support for ?register and ?register;preauth XMPP uri parameters diff --git a/fastlane/metadata/android/gl-ES/changelogs/362.txt b/fastlane/metadata/android/gl-ES/changelogs/362.txt new file mode 100644 index 0000000000000000000000000000000000000000..f4bbc2ab12c20689eddff86bc7a015f859a827b9 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/362.txt @@ -0,0 +1 @@ +* Support automatic theme switching on Android 10 diff --git a/fastlane/metadata/android/gl-ES/changelogs/364.txt b/fastlane/metadata/android/gl-ES/changelogs/364.txt new file mode 100644 index 0000000000000000000000000000000000000000..ed2c80678b53cd8f43be1a848ce82ddae251bc17 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/364.txt @@ -0,0 +1,2 @@ +* Provide PDF preview on Android 5+ +* Use 12 byte IVs for OMEMO diff --git a/fastlane/metadata/android/gl-ES/changelogs/367.txt b/fastlane/metadata/android/gl-ES/changelogs/367.txt new file mode 100644 index 0000000000000000000000000000000000000000..2f42bd95f0437ba06548c9ca1f96b2ecef37fbe8 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/367.txt @@ -0,0 +1,2 @@ +* Fix avatar selection on some Android 10 devices +* Fix file transfer for larger files diff --git a/fastlane/metadata/android/gl-ES/changelogs/379.txt b/fastlane/metadata/android/gl-ES/changelogs/379.txt new file mode 100644 index 0000000000000000000000000000000000000000..a99adee16ff86f6e22e2783b76a2e6c020c6ca31 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/379.txt @@ -0,0 +1 @@ +* Audio/Video calls (Requires server support in form of STUN and TURN servers discoverable via XEP-0215) diff --git a/fastlane/metadata/android/gl-ES/changelogs/381.txt b/fastlane/metadata/android/gl-ES/changelogs/381.txt new file mode 100644 index 0000000000000000000000000000000000000000..a2df5e8284a184b0f9118c1babb28c1de13974fe --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/381.txt @@ -0,0 +1,2 @@ +* Audible feedback (dialing, call started, call ended) for voice calls. +* Fixed issue with retrying failed video call diff --git a/fastlane/metadata/android/gl-ES/changelogs/382.txt b/fastlane/metadata/android/gl-ES/changelogs/382.txt new file mode 100644 index 0000000000000000000000000000000000000000..64e23e14dba8d7b40a032cf98cc9a05c518a8a8c --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/382.txt @@ -0,0 +1,2 @@ +* Add button to switch camera during video call +* Fixed voice calls on tablets diff --git a/fastlane/metadata/android/gl-ES/changelogs/383.txt b/fastlane/metadata/android/gl-ES/changelogs/383.txt new file mode 100644 index 0000000000000000000000000000000000000000..19c9a0116cbaeddb650527a1eda90c827fc275b9 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/383.txt @@ -0,0 +1,3 @@ +* Move call icon to the left in order to keep other toolbar icons in a consistent place +* Show call duration during audio calls +* Tie breaking for A/V calls (the same two people calling each other at the same time) diff --git a/fastlane/metadata/android/gl-ES/changelogs/387.txt b/fastlane/metadata/android/gl-ES/changelogs/387.txt new file mode 100644 index 0000000000000000000000000000000000000000..2710be0a51d34ff4590979d4280c1689d1cf6a06 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/387.txt @@ -0,0 +1,2 @@ +* Rework Login with certificate UI +* Add ability to pin chats on top (add to favorites) diff --git a/fastlane/metadata/android/gl-ES/changelogs/388.txt b/fastlane/metadata/android/gl-ES/changelogs/388.txt new file mode 100644 index 0000000000000000000000000000000000000000..6a4909652b7d98666345b9fdaf4ecc2d5c7a541e --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/388.txt @@ -0,0 +1,3 @@ +* Reduce echo during calls on some devices +* Fix login when passwords contains special characters +* Play dial and busy tones on speaker during video calls diff --git a/fastlane/metadata/android/gl-ES/changelogs/390.txt b/fastlane/metadata/android/gl-ES/changelogs/390.txt new file mode 100644 index 0000000000000000000000000000000000000000..56ed78885c7507d59fd09c00164c3f5ab8ada9f7 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/390.txt @@ -0,0 +1 @@ +* Offer to record voice message when callee is busy diff --git a/fastlane/metadata/android/gl-ES/changelogs/393.txt b/fastlane/metadata/android/gl-ES/changelogs/393.txt new file mode 100644 index 0000000000000000000000000000000000000000..82250ee876e5e20b12cfebde773f40ad2abf6081 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/393.txt @@ -0,0 +1,3 @@ +* Show help button if A/V call fails +* Fixed some annoying crashes +* Fixed Jingle connections (file transfer + calls) with bare JIDs diff --git a/fastlane/metadata/android/gl-ES/changelogs/394.txt b/fastlane/metadata/android/gl-ES/changelogs/394.txt new file mode 100644 index 0000000000000000000000000000000000000000..b04adbd560e99ee0c9a54ff11a19c3de3ff7acfb --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/394.txt @@ -0,0 +1,2 @@ +* Fixed notifications not showing up under certain conditions +* Fixed compatibility issues and crashes related to A/V calls diff --git a/fastlane/metadata/android/gl-ES/changelogs/395.txt b/fastlane/metadata/android/gl-ES/changelogs/395.txt new file mode 100644 index 0000000000000000000000000000000000000000..76a654338bfaccf71806f33f4cee49f02bafde8e --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/395.txt @@ -0,0 +1,3 @@ +* add 'Return to chat' to audio call screen +* Improve keyboard shortcuts +* bug fixes diff --git a/fastlane/metadata/android/gl-ES/changelogs/397.txt b/fastlane/metadata/android/gl-ES/changelogs/397.txt new file mode 100644 index 0000000000000000000000000000000000000000..207b36708e299ad427d1b4d76bc22b97526c5ebe --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/397.txt @@ -0,0 +1,3 @@ +* Handle GPX files +* Improve performance for backup restore +* bug fixes diff --git a/fastlane/metadata/android/gl-ES/changelogs/398.txt b/fastlane/metadata/android/gl-ES/changelogs/398.txt new file mode 100644 index 0000000000000000000000000000000000000000..95280ea889d1fc973a4cc0b00c3a7445ef37346e --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/398.txt @@ -0,0 +1,4 @@ +* Search individual conversations +* Notify user if message delivery fails +* Remember display names (nicks) from Quicksy users across restarts +* Add button to start Orbot (Tor) from notification if necessary diff --git a/fastlane/metadata/android/gl-ES/changelogs/401.txt b/fastlane/metadata/android/gl-ES/changelogs/401.txt new file mode 100644 index 0000000000000000000000000000000000000000..907063eb6f74ca23df10f1e7d485698aa5b9614d --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/401.txt @@ -0,0 +1,2 @@ +* fixed search on Android <= 5 +* optimize memory consumption diff --git a/fastlane/metadata/android/gl-ES/changelogs/402.txt b/fastlane/metadata/android/gl-ES/changelogs/402.txt new file mode 100644 index 0000000000000000000000000000000000000000..53f461756c45cdbda27a5cbc95197f7b96bd0fe0 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/402.txt @@ -0,0 +1,3 @@ +* Offer Easy Invite generation on supporting servers +* Display GIFs send from Movim +* store avatars in cache diff --git a/fastlane/metadata/android/gl-ES/changelogs/403.txt b/fastlane/metadata/android/gl-ES/changelogs/403.txt new file mode 100644 index 0000000000000000000000000000000000000000..99d62ca487171a27a8cb5ab0d20a2a17722da0c8 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/403.txt @@ -0,0 +1,3 @@ +* Fixed connectivity issues when different accounts used different SCRAM mechanisms +* Add support for SCRAM-SHA-512 +* Allow P2P (Jingle) file transfer with self contact diff --git a/fastlane/metadata/android/gl-ES/changelogs/404.txt b/fastlane/metadata/android/gl-ES/changelogs/404.txt new file mode 100644 index 0000000000000000000000000000000000000000..d4f2e7b6d4a92a0f0d6d013560e56cf48c9078ca --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/404.txt @@ -0,0 +1 @@ +* minor stability improvements for A/V calls diff --git a/fastlane/metadata/android/gl-ES/changelogs/405.txt b/fastlane/metadata/android/gl-ES/changelogs/405.txt new file mode 100644 index 0000000000000000000000000000000000000000..e858b6cd1922a866e87237e2417e53e1ad39d633 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/405.txt @@ -0,0 +1 @@ +* Quicksy: Automatically receive verification SMS diff --git a/fastlane/metadata/android/gl-ES/changelogs/407.txt b/fastlane/metadata/android/gl-ES/changelogs/407.txt new file mode 100644 index 0000000000000000000000000000000000000000..e746bc7d7438b083387e87ace59d4bbf0cfa6a30 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/407.txt @@ -0,0 +1,3 @@ +* Show call button for offline contacts if they previously announced support +* Back button no longer ends call when call is connected +* bug fixes diff --git a/fastlane/metadata/android/gl-ES/changelogs/42000.txt b/fastlane/metadata/android/gl-ES/changelogs/42000.txt new file mode 100644 index 0000000000000000000000000000000000000000..1ecfe204dc4a6a3e3fa301d78ef175633f28d856 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42000.txt @@ -0,0 +1,4 @@ +* Ability to select incoming call ringtone +* Fix OpenPGP key id discovery for OpenKeychain 5.6+ +* Properly verify punycode TLS certificates +* Improve stability of RTP session establishment (calling) diff --git a/fastlane/metadata/android/gl-ES/changelogs/42006.txt b/fastlane/metadata/android/gl-ES/changelogs/42006.txt new file mode 100644 index 0000000000000000000000000000000000000000..91e2b904f440c7377cdc1beae78b97487761d980 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42006.txt @@ -0,0 +1,2 @@ +* Verify A/V calls with preexisting OMEMO sessions +* Improve compatibility with non libwebrtc WebRTC implementations diff --git a/fastlane/metadata/android/gl-ES/changelogs/42010.txt b/fastlane/metadata/android/gl-ES/changelogs/42010.txt new file mode 100644 index 0000000000000000000000000000000000000000..3a1c234c1464eb51d58d7bad833f65885f702eb9 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42010.txt @@ -0,0 +1,2 @@ +* Various bug fixes around Tor support +* Improve call compatibility with Dino diff --git a/fastlane/metadata/android/gl-ES/changelogs/42012.txt b/fastlane/metadata/android/gl-ES/changelogs/42012.txt new file mode 100644 index 0000000000000000000000000000000000000000..967fae96468b8d13e83ae29d7165beff569f4d17 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42012.txt @@ -0,0 +1 @@ +* fix HTTP up/download for users that don’t trust system CAs diff --git a/fastlane/metadata/android/gl-ES/changelogs/42013.txt b/fastlane/metadata/android/gl-ES/changelogs/42013.txt new file mode 100644 index 0000000000000000000000000000000000000000..8749f0a0f170182a39c2dfe82f88794658e9c24d --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42013.txt @@ -0,0 +1 @@ +* Fixed 'No Connectivity' issues on Android 7.1 diff --git a/fastlane/metadata/android/gl-ES/changelogs/42014.txt b/fastlane/metadata/android/gl-ES/changelogs/42014.txt new file mode 100644 index 0000000000000000000000000000000000000000..8ae96511ea3fc6432caf828ba9eaefa4f85eebec --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42014.txt @@ -0,0 +1,2 @@ +* Always verify domain name. No user overwrite +* Support roster pre authentication diff --git a/fastlane/metadata/android/gl-ES/changelogs/42015.txt b/fastlane/metadata/android/gl-ES/changelogs/42015.txt new file mode 100644 index 0000000000000000000000000000000000000000..1980efb2a15acda2ff39d71aed3fe350b4c77aca --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42015.txt @@ -0,0 +1 @@ +* minor A/V improvements diff --git a/fastlane/metadata/android/gl-ES/changelogs/42018.txt b/fastlane/metadata/android/gl-ES/changelogs/42018.txt new file mode 100644 index 0000000000000000000000000000000000000000..8f4d66caab71deced9f02d1258690ef06aecc2ca --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42018.txt @@ -0,0 +1,3 @@ +* Show black bars when remote video does not match aspect ratio of screen +* Improve search performance +* Add setting to prevent screenshots diff --git a/fastlane/metadata/android/gl-ES/changelogs/42022.txt b/fastlane/metadata/android/gl-ES/changelogs/42022.txt new file mode 100644 index 0000000000000000000000000000000000000000..eaaa190faabe6799d5fc22f80d7b6a5ccbd87805 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42022.txt @@ -0,0 +1,2 @@ +* Fix issue with some videos not being compressed +* Fix rare crash when opening notification diff --git a/fastlane/metadata/android/gl-ES/changelogs/42023.txt b/fastlane/metadata/android/gl-ES/changelogs/42023.txt new file mode 100644 index 0000000000000000000000000000000000000000..ed3c253807388b2b27f7716c86c88070232c3a28 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42023.txt @@ -0,0 +1,2 @@ +* Fix crash when rendering some quotes +* Fix crash in welcome screen diff --git a/fastlane/metadata/android/gl-ES/changelogs/42037.txt b/fastlane/metadata/android/gl-ES/changelogs/42037.txt new file mode 100644 index 0000000000000000000000000000000000000000..375905aa8b402b893a18b8cc89c58f4e88a22bad --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42037.txt @@ -0,0 +1,11 @@ +Version 2.10.9 +* Ask for Bluetooth permissions when making A/V calls (You can reject this if you don’t use Bluetooth headsets) +* Fix bug when calling Movim +* Fix wrong avatar being shown for group chats +* Always ask for battery optimizations opt-out +* Set local only flag on 'x connected accounts' notifications +* Fix interaction with Google Maps Share Location Plugin +* Remove footnote with regards to server fee +* Store files in location appropriate for Android 11 +* Attempt to reconnect call after network switch +* Show caller JID and account JID in incoming call screen diff --git a/fastlane/metadata/android/gl-ES/changelogs/42038.txt b/fastlane/metadata/android/gl-ES/changelogs/42038.txt new file mode 100644 index 0000000000000000000000000000000000000000..da3c42237f1a1b58be3ee50209e60a08c3c684b9 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42038.txt @@ -0,0 +1,2 @@ +* Minor bug fixes +* Restore ability to call out via JMP and other services (Playstore version) diff --git a/fastlane/metadata/android/gl-ES/changelogs/42041.txt b/fastlane/metadata/android/gl-ES/changelogs/42041.txt new file mode 100644 index 0000000000000000000000000000000000000000..302e9719b1b0c4db9306a087930f7e0911b7948c --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42041.txt @@ -0,0 +1,5 @@ +* Implement Extensible SASL Profile, Bind 2.0 and Fast for faster reconnects +* Implement Channel Binding +* Add ability to switch from audio call to video call +* Add ability to delete own avatar +* Add notification for missed calls diff --git a/fastlane/metadata/android/gl-ES/changelogs/42042.txt b/fastlane/metadata/android/gl-ES/changelogs/42042.txt new file mode 100644 index 0000000000000000000000000000000000000000..520578662112fe5ba9c6616f1fa9a276f8244bea --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42042.txt @@ -0,0 +1,2 @@ +* Arranxo do reenvío contínuo en servidores que só teñen soporte sm:2 +* Mostrar 'Cambiar a vídeo' só se a outra parte tamén soporta chamada de vídeo diff --git a/fastlane/metadata/android/gl-ES/changelogs/42043.txt b/fastlane/metadata/android/gl-ES/changelogs/42043.txt new file mode 100644 index 0000000000000000000000000000000000000000..91937ba45724bc36d37ced816965abd74dbb820b --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42043.txt @@ -0,0 +1 @@ +* Arranxo da regresión na transferencia de ficheiros con P2P diff --git a/fastlane/metadata/android/gl-ES/changelogs/42044.txt b/fastlane/metadata/android/gl-ES/changelogs/42044.txt new file mode 100644 index 0000000000000000000000000000000000000000..b6df297940c146f8de2cc4c880daa832f2bc0715 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42044.txt @@ -0,0 +1,3 @@ +* Arranxo do reenvío usando SASL2 +* Arranxo dos vídeos en negro nalgúns dispositivos +* Arranxo do fallo ao usar un contrasinal baleiro diff --git a/fastlane/metadata/android/gl-ES/changelogs/42046.txt b/fastlane/metadata/android/gl-ES/changelogs/42046.txt new file mode 100644 index 0000000000000000000000000000000000000000..ec1c1ca76555a5e0e588b9f196f3a0918d9c937e --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42046.txt @@ -0,0 +1 @@ +* Integrar UnifiedPush Distributor para facilitar a entrega de mensaxes push a outras apps con UnifiedPush activado como Tusky e Fedilab diff --git a/fastlane/metadata/android/gl-ES/changelogs/42047.txt b/fastlane/metadata/android/gl-ES/changelogs/42047.txt new file mode 100644 index 0000000000000000000000000000000000000000..359f0e9580c1121c4e98e0c960777c0f07e0620e --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42047.txt @@ -0,0 +1 @@ +* Arranxar o fallo en UnifiedPush Distributor diff --git a/fastlane/metadata/android/gl-ES/changelogs/42050.txt b/fastlane/metadata/android/gl-ES/changelogs/42050.txt new file mode 100644 index 0000000000000000000000000000000000000000..40a774b211dfe83db26d5fb8c65b2c09ec7f0a39 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42050.txt @@ -0,0 +1 @@ +* Aumentar o radio dos cantos nas imaxes de perfil diff --git a/fastlane/metadata/android/gl-ES/changelogs/42059.txt b/fastlane/metadata/android/gl-ES/changelogs/42059.txt new file mode 100644 index 0000000000000000000000000000000000000000..44356c7e73d3b50a3215806dd0b597900177dbb5 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42059.txt @@ -0,0 +1,2 @@ +* Establecer o Target SDK ao 33 de novo +* Arranxar problemas cos servidores con soporte SASL2 sen Stream Management en liña diff --git a/fastlane/metadata/android/gl-ES/changelogs/42060.txt b/fastlane/metadata/android/gl-ES/changelogs/42060.txt new file mode 100644 index 0000000000000000000000000000000000000000..b79b6c0a714af6a1ea9145cea92dec3f4462294f --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42060.txt @@ -0,0 +1 @@ +* Arranxa o problema de considerar o 'q' como cirílico diff --git a/fastlane/metadata/android/gl-ES/changelogs/42061.txt b/fastlane/metadata/android/gl-ES/changelogs/42061.txt new file mode 100644 index 0000000000000000000000000000000000000000..3173f43f4aca134ab2ae34bbeb8d25e0d764a3dd --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42061.txt @@ -0,0 +1 @@ +* Retira, da versión Google Play, a ferramenta de descubrimento de canles diff --git a/fastlane/metadata/android/gl-ES/changelogs/42062.txt b/fastlane/metadata/android/gl-ES/changelogs/42062.txt new file mode 100644 index 0000000000000000000000000000000000000000..4d9358f7ddde8f1675174852e547deb5de5a6e0f --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42062.txt @@ -0,0 +1 @@ +* Desactiva a apertura de ficheiros de copia de apoio (.ceb) desde o xestor de ficheiros diff --git a/fastlane/metadata/android/gl-ES/changelogs/42065.txt b/fastlane/metadata/android/gl-ES/changelogs/42065.txt new file mode 100644 index 0000000000000000000000000000000000000000..ce952a3170622c0b3739c56bc59587db73637cf2 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42065.txt @@ -0,0 +1 @@ +* Presenta o novo formato para as copias de apoio diff --git a/fastlane/metadata/android/gl-ES/changelogs/42068.txt b/fastlane/metadata/android/gl-ES/changelogs/42068.txt new file mode 100644 index 0000000000000000000000000000000000000000..49d898d51040ab19a7ae407d0a333488e1b7aa84 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42068.txt @@ -0,0 +1,2 @@ +* soporte para os axustes das notificacións por conversa +* usar opus para as mensaxes de voz en Android 10 diff --git a/fastlane/metadata/android/gl-ES/changelogs/42072.txt b/fastlane/metadata/android/gl-ES/changelogs/42072.txt new file mode 100644 index 0000000000000000000000000000000000000000..534c20ed146aae62638e4837b2cc66583dfe40cc --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42072.txt @@ -0,0 +1,3 @@ +*Subir a dependencia libwebrtc a M117 e tamén libvpx +* Volver a AAC para as mensaxes de voz +* Soporte para indicar na app os axustes do idioma diff --git a/fastlane/metadata/android/gl-ES/changelogs/42074.txt b/fastlane/metadata/android/gl-ES/changelogs/42074.txt new file mode 100644 index 0000000000000000000000000000000000000000..6729e251debe980faad8b2eb7e00ca0ef529eeff --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/42074.txt @@ -0,0 +1,3 @@ +* Soport para DNS Privado (DNS sobre TLS) +* Soporte para personalizar a icona de inicio +* Arranxamos un raro problema de permisos ao compartir ficheiros en Android 11+ diff --git a/fastlane/metadata/android/it-IT/changelogs/349.txt b/fastlane/metadata/android/it-IT/changelogs/349.txt new file mode 100644 index 0000000000000000000000000000000000000000..170eda9514a6486dfe78629b4faa593b01400afd --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/349.txt @@ -0,0 +1,4 @@ +* Introdotta l'impostazione per esperti per eseguire la ricerca dei canali sul server locale invece che su search.jabber.network +* Attivati i segni di spunta per la consegna in modo predefinito e rimossa l'impostazione +* Attivato "Il pulsante di invio indica lo stato" in modo predefinito e rimossa l'impostazione +* Spostate le impostazioni del servizio di backup e di primo piano nella schermata principale diff --git a/fastlane/metadata/android/it-IT/changelogs/351.txt b/fastlane/metadata/android/it-IT/changelogs/351.txt new file mode 100644 index 0000000000000000000000000000000000000000..3c3b94459e94e31e9a29b3664d20c178c3a8805c --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/351.txt @@ -0,0 +1,3 @@ +* Correzioni per il trasferimento di file Jingle IBB +* Risolte le correzioni ripetute che riempivano il database +* Transizione a Last Message Correction v1.1 diff --git a/fastlane/metadata/android/it-IT/changelogs/353.txt b/fastlane/metadata/android/it-IT/changelogs/353.txt new file mode 100644 index 0000000000000000000000000000000000000000..dc3a5160ddbf8cf5d19e91bc7f89ddad6a4552f2 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/353.txt @@ -0,0 +1,4 @@ +* Consente agli utenti di impostare il proprio nick name +* Riprende il download dei file criptati OMEMO +* I canali ora usano '#' come simbolo nell'avatar. +* Quicksy imposta "sempre" come crittografia OMEMO in modo predefinito (nasconde l'icona del lucchetto) diff --git a/fastlane/metadata/android/it-IT/changelogs/360.txt b/fastlane/metadata/android/it-IT/changelogs/360.txt new file mode 100644 index 0000000000000000000000000000000000000000..82fbedd2dee11ffd5ef9b49dd0b1404b02661e8f --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/360.txt @@ -0,0 +1 @@ +* Supporto per i parametri uri XMPP ?register e ?register;preauth diff --git a/fastlane/metadata/android/it-IT/changelogs/362.txt b/fastlane/metadata/android/it-IT/changelogs/362.txt new file mode 100644 index 0000000000000000000000000000000000000000..c276d02c26ef7244edc80e69b62e9a4e069bdf0f --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/362.txt @@ -0,0 +1 @@ +* Supporto del cambio automatico dei temi su Android 10 diff --git a/fastlane/metadata/android/it-IT/changelogs/364.txt b/fastlane/metadata/android/it-IT/changelogs/364.txt new file mode 100644 index 0000000000000000000000000000000000000000..7b461a350291d49fa5a65f8ab420cf7312d5afcd --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/364.txt @@ -0,0 +1,2 @@ +* Fornisce l'anteprima PDF su Android 5+ +* Utilizzo di IVs a 12 byte per OMEMO diff --git a/fastlane/metadata/android/it-IT/changelogs/367.txt b/fastlane/metadata/android/it-IT/changelogs/367.txt new file mode 100644 index 0000000000000000000000000000000000000000..89239a58fe2fc262c55fb613f2702891cdfaf5f4 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/367.txt @@ -0,0 +1,2 @@ +* Corretta la selezione dell'avatar su alcuni dispositivi Android 10 +* Corretto il trasferimento di file più grandi diff --git a/fastlane/metadata/android/it-IT/changelogs/379.txt b/fastlane/metadata/android/it-IT/changelogs/379.txt new file mode 100644 index 0000000000000000000000000000000000000000..fe27f8ec61a8aa0719eaaf8efeef59bfecd432b8 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/379.txt @@ -0,0 +1 @@ +* Chiamate audio/video (richiede il supporto di server sotto forma di server STUN e TURN rilevabili tramite XEP-0215) diff --git a/fastlane/metadata/android/it-IT/changelogs/381.txt b/fastlane/metadata/android/it-IT/changelogs/381.txt new file mode 100644 index 0000000000000000000000000000000000000000..39bd79c54c006778cacfa3275841046387bec20e --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/381.txt @@ -0,0 +1,2 @@ +* Feedback acustico (composizione, inizio e fine chiamata) per le chiamate vocali. +* Risolto un problema con la ripetizione di una videochiamata fallita diff --git a/fastlane/metadata/android/it-IT/changelogs/382.txt b/fastlane/metadata/android/it-IT/changelogs/382.txt new file mode 100644 index 0000000000000000000000000000000000000000..8a8bd6863d0b480807f8f05e44a17c52e6db8ef5 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/382.txt @@ -0,0 +1,2 @@ +* Aggiunto un pulsante per cambiare telecamera durante la videochiamata +* Corrette le chiamate vocali sui tablet diff --git a/fastlane/metadata/android/it-IT/changelogs/383.txt b/fastlane/metadata/android/it-IT/changelogs/383.txt new file mode 100644 index 0000000000000000000000000000000000000000..32d16941b78d5be2c04e47871a52ba26164b3241 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/383.txt @@ -0,0 +1,3 @@ +* Spostata l'icona della chiamata a sinistra per mantenere le altre icone della barra degli strumenti in una posizione coerente +* Mostra la durata della chiamata durante le chiamate audio +* Interruzione della parità per le chiamate A/V (due persone che si chiamano contemporaneamente) diff --git a/fastlane/metadata/android/it-IT/changelogs/387.txt b/fastlane/metadata/android/it-IT/changelogs/387.txt new file mode 100644 index 0000000000000000000000000000000000000000..e78cea033667e9da2e1be0bb64b885c15979e14f --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/387.txt @@ -0,0 +1,2 @@ +* Ristrutturata l'interfaccia utente per l'accesso con certificato +* Aggiunta la possibilità di fissare le chat in alto (aggiungi ai preferiti) diff --git a/fastlane/metadata/android/it-IT/changelogs/388.txt b/fastlane/metadata/android/it-IT/changelogs/388.txt new file mode 100644 index 0000000000000000000000000000000000000000..20b0c18fa3ad1f8da5f7684b70f4e8df9a4eb559 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/388.txt @@ -0,0 +1,3 @@ +* Riduzione dell'eco durante le chiamate su alcuni dispositivi +* Corretto l'accesso quando le password contengono caratteri speciali +* Riproduzione dei toni di chiamata e di occupato sull'altoparlante durante le videochiamate diff --git a/fastlane/metadata/android/it-IT/changelogs/390.txt b/fastlane/metadata/android/it-IT/changelogs/390.txt new file mode 100644 index 0000000000000000000000000000000000000000..35b00a97da4c0103f2c9b523be4dfe30418181f5 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/390.txt @@ -0,0 +1 @@ +* Offerta di registrazione del messaggio vocale quando il chiamante è occupato diff --git a/fastlane/metadata/android/it-IT/changelogs/393.txt b/fastlane/metadata/android/it-IT/changelogs/393.txt new file mode 100644 index 0000000000000000000000000000000000000000..3238e891050de426ceebbbe5da89e95b2474cb0d --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/393.txt @@ -0,0 +1,3 @@ +* Mostra il pulsante di aiuto se la chiamata A/V fallisce +* Risolti alcuni fastidiosi arresti anomali +* Corrette le connessioni Jingle (trasferimento di file + chiamate) con JID nudi diff --git a/fastlane/metadata/android/it-IT/changelogs/394.txt b/fastlane/metadata/android/it-IT/changelogs/394.txt new file mode 100644 index 0000000000000000000000000000000000000000..d94866c6d1201982d5e2b41fd8116a7f5bb96f77 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/394.txt @@ -0,0 +1,2 @@ +* Corrette le notifiche che non vengono visualizzate in determinate condizioni +* Corretti i problemi di compatibilità e gli arresti anomali relativi alle chiamate A/V diff --git a/fastlane/metadata/android/it-IT/changelogs/395.txt b/fastlane/metadata/android/it-IT/changelogs/395.txt new file mode 100644 index 0000000000000000000000000000000000000000..264021fd38d219bbdef70224e79159f304978423 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/395.txt @@ -0,0 +1,3 @@ +* Aggiunta la funzione "Torna alla chat" alla schermata delle chiamate audio +* Migliorate le scorciatoie da tastiera +* Correzioni di errori diff --git a/fastlane/metadata/android/it-IT/changelogs/397.txt b/fastlane/metadata/android/it-IT/changelogs/397.txt new file mode 100644 index 0000000000000000000000000000000000000000..295266e01c103a518fc8b6854a6627b62f65cdc6 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/397.txt @@ -0,0 +1,3 @@ +* Gestisce i file GPX +* Migliorate le prestazioni per il ripristino dei backup +* Correzioni di errori diff --git a/fastlane/metadata/android/it-IT/changelogs/398.txt b/fastlane/metadata/android/it-IT/changelogs/398.txt new file mode 100644 index 0000000000000000000000000000000000000000..5efa2686edfa832875e0f6de47649354eeb379f2 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/398.txt @@ -0,0 +1,4 @@ +* Ricerca di conversazioni individuali +* Notifica all'utente se la consegna del messaggio fallisce +* Ricorda i nomi visualizzati (nick) degli utenti di Quicksy durante i vari riavvii +* Aggiunto un pulsante per avviare Orbot (Tor) dalla notifica, se necessario diff --git a/fastlane/metadata/android/it-IT/changelogs/401.txt b/fastlane/metadata/android/it-IT/changelogs/401.txt new file mode 100644 index 0000000000000000000000000000000000000000..fbcf70c2af3d19256d4a3c465361c83f3bf63ddf --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/401.txt @@ -0,0 +1,2 @@ +* Corretta la ricerca su Android <= 5 +* Ottimizzato il consumo di memoria diff --git a/fastlane/metadata/android/it-IT/changelogs/402.txt b/fastlane/metadata/android/it-IT/changelogs/402.txt new file mode 100644 index 0000000000000000000000000000000000000000..e4a1a9fa0585c06aac38f76384de110c0715a4fa --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/402.txt @@ -0,0 +1,3 @@ +* Offre la generazione di inviti facili sui server di supporto +* Visualizzazione delle GIF inviate da Movim +* Memorizzazione degli avatar nella cache diff --git a/fastlane/metadata/android/it-IT/changelogs/403.txt b/fastlane/metadata/android/it-IT/changelogs/403.txt new file mode 100644 index 0000000000000000000000000000000000000000..c0ee371d8761988e8f63010c6499775b203d74fc --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/403.txt @@ -0,0 +1,3 @@ +* Corretti i problemi di connettività quando profili diversi usavano meccanismi SCRAM diversi +* Aggiunto il supporto per SCRAM-SHA-512 +* Consente il trasferimento di file P2P (Jingle) con l'auto contatto diff --git a/fastlane/metadata/android/it-IT/changelogs/404.txt b/fastlane/metadata/android/it-IT/changelogs/404.txt new file mode 100644 index 0000000000000000000000000000000000000000..6346ddb12a623bed7e9f66814ff081f39654b166 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/404.txt @@ -0,0 +1 @@ +* Miglioramenti di stabilità minori per le chiamate A/V diff --git a/fastlane/metadata/android/it-IT/changelogs/405.txt b/fastlane/metadata/android/it-IT/changelogs/405.txt new file mode 100644 index 0000000000000000000000000000000000000000..7325ea2a99abb167a00fb38356e77242a068a0df --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/405.txt @@ -0,0 +1 @@ +* Quicksy: ricevi automaticamente SMS di verifica diff --git a/fastlane/metadata/android/it-IT/changelogs/407.txt b/fastlane/metadata/android/it-IT/changelogs/407.txt new file mode 100644 index 0000000000000000000000000000000000000000..2ba4dd4fd515b00ea9e29fcaff96f002fb00feca --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/407.txt @@ -0,0 +1,3 @@ +* Mostra il pulsante di chiamata per i contatti offline se hanno precedentemente annunciato il supporto +* Il pulsante Indietro non termina più la chiamata quando questa è connessa +* Correzioni di errori diff --git a/fastlane/metadata/android/it-IT/changelogs/42000.txt b/fastlane/metadata/android/it-IT/changelogs/42000.txt new file mode 100644 index 0000000000000000000000000000000000000000..5697c2ed2622514146a5cc336151f94c7d0718a1 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42000.txt @@ -0,0 +1,4 @@ +* Possibilità di selezionare la suoneria delle chiamate in arrivo +* Correzione del rilevamento dell'id della chiave OpenPGP per OpenKeychain 5.6+ +* Verifica corretta dei certificati TLS con codice punycode +* Miglioramento della stabilità della creazione di sessioni RTP (chiamate) diff --git a/fastlane/metadata/android/it-IT/changelogs/42006.txt b/fastlane/metadata/android/it-IT/changelogs/42006.txt new file mode 100644 index 0000000000000000000000000000000000000000..dda32ef6a16258f3657a0a9287d8bec85cabd58f --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42006.txt @@ -0,0 +1,2 @@ +* Verifica delle chiamate A/V con le sessioni OMEMO preesistenti +* Miglioramento della compatibilità con le implementazioni WebRTC non libwebrtc diff --git a/fastlane/metadata/android/it-IT/changelogs/42010.txt b/fastlane/metadata/android/it-IT/changelogs/42010.txt new file mode 100644 index 0000000000000000000000000000000000000000..2140a3a44dff9f41bc514c09b1a70e7d25578dbd --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42010.txt @@ -0,0 +1,2 @@ +* Correzione di vari errori relativi al supporto di Tor +* Migliorata la compatibilità delle chiamate con Dino diff --git a/fastlane/metadata/android/it-IT/changelogs/42012.txt b/fastlane/metadata/android/it-IT/changelogs/42012.txt new file mode 100644 index 0000000000000000000000000000000000000000..9da7d48df0a65e16786166c2c86414cae6f414c0 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42012.txt @@ -0,0 +1 @@ +* Corretto l'up/download HTTP per gli utenti che non si fidano delle CA di sistema diff --git a/fastlane/metadata/android/it-IT/changelogs/42013.txt b/fastlane/metadata/android/it-IT/changelogs/42013.txt new file mode 100644 index 0000000000000000000000000000000000000000..f58811956fec8b1536153ffc72f4e7735aeda21d --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42013.txt @@ -0,0 +1 @@ +* Risolti i problemi di "assenza di connettività" su Android 7.1 diff --git a/fastlane/metadata/android/it-IT/changelogs/42014.txt b/fastlane/metadata/android/it-IT/changelogs/42014.txt new file mode 100644 index 0000000000000000000000000000000000000000..95c2355ff0aa616a4b9cfddf506ea190233782fb --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42014.txt @@ -0,0 +1,2 @@ +* Verifica sempre il nome del dominio. Nessuna sovrascrittura dell'utente +* Supporto della pre-autenticazione del roster diff --git a/fastlane/metadata/android/it-IT/changelogs/42015.txt b/fastlane/metadata/android/it-IT/changelogs/42015.txt new file mode 100644 index 0000000000000000000000000000000000000000..b296f82ca230f67b0355f19e8ec4d69fd23c4cc8 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42015.txt @@ -0,0 +1 @@ +* Miglioramenti A/V minori diff --git a/fastlane/metadata/android/it-IT/changelogs/42018.txt b/fastlane/metadata/android/it-IT/changelogs/42018.txt new file mode 100644 index 0000000000000000000000000000000000000000..a6eac7a8f49622e9fa397c9f528d70c806422249 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42018.txt @@ -0,0 +1,3 @@ +* Mostra barre nere quando il video remoto non corrisponde alle proporzioni dello schermo +* Migliorate le prestazioni della ricerca +* Aggiunta un'impostazione per impedire gli screenshot diff --git a/fastlane/metadata/android/it-IT/changelogs/42022.txt b/fastlane/metadata/android/it-IT/changelogs/42022.txt new file mode 100644 index 0000000000000000000000000000000000000000..1ff22387578b103a3e0415679f90fef86be53b15 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42022.txt @@ -0,0 +1,2 @@ +* Corretto il problema di alcuni video che non vengono compressi +* Corretti rari arresti anomali all'apertura delle notifiche diff --git a/fastlane/metadata/android/it-IT/changelogs/42023.txt b/fastlane/metadata/android/it-IT/changelogs/42023.txt new file mode 100644 index 0000000000000000000000000000000000000000..a75ef6f397fe6123452c63f3140f56ba195ada70 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42023.txt @@ -0,0 +1,2 @@ +* Corretto l'arresto anomalo durante il rendering di alcune citazioni +* Corretto l'arresto anomalo nella schermata di benvenuto diff --git a/fastlane/metadata/android/it-IT/changelogs/42037.txt b/fastlane/metadata/android/it-IT/changelogs/42037.txt new file mode 100644 index 0000000000000000000000000000000000000000..9b2cf0e25607e8ff4b941a99aec5bdb9d8ffd277 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42037.txt @@ -0,0 +1,11 @@ +2.10.9 +* Permessi Bluetooth per chiamate A/V (puoi rifiutare) +* Correto bug chiamando Movim +* Corretti avatar nelle chat di gruppo +* Chiedi sempre opt-out di ottimizzazione batteria +* Flag solo locale su notifiche "x profili connessi" +* Corretta interazione con plugin di condivisione posizione Google Maps +* Rimossa nota del costo del server +* Archivia i file nel posto giusto su Android 11 +* Ricollega chiamata dopo il cambio di rete +* Mostra JID chiamante e JID profilo nelle chiamate in entrata diff --git a/fastlane/metadata/android/it-IT/changelogs/42038.txt b/fastlane/metadata/android/it-IT/changelogs/42038.txt new file mode 100644 index 0000000000000000000000000000000000000000..04af5aca6fbdc952ea0a618c6c2a56fc3db0745a --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42038.txt @@ -0,0 +1,2 @@ +* Correzione di errori minori +* Ripristino della possibilità di richiamare tramite JMP e altri servizi (versione Playstore) diff --git a/fastlane/metadata/android/it-IT/changelogs/42041.txt b/fastlane/metadata/android/it-IT/changelogs/42041.txt new file mode 100644 index 0000000000000000000000000000000000000000..2331de300da498b122cc4dbcc9a528dad9e6b26d --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42041.txt @@ -0,0 +1,5 @@ +* Implementato il profilo SASL estensibile, Bind 2.0 e Fast per riconnettersi più velocemente +* Implementato il Channel Binding +* Aggiunta la possibilità di passare da una chiamata audio a una videochiamata +* Aggiunta la possibilità di cancellare il proprio avatar +* Aggiunta la notifica per le chiamate perse diff --git a/fastlane/metadata/android/it-IT/changelogs/42042.txt b/fastlane/metadata/android/it-IT/changelogs/42042.txt new file mode 100644 index 0000000000000000000000000000000000000000..3bc5bb2b70dd452bc23d94b53004b1207e202ef7 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42042.txt @@ -0,0 +1,2 @@ +* Corretto un loop di reinvio sui server che supportano solo sm:2 +* Mostra 'Passa al video' solo se l'altra parte supporta il video diff --git a/fastlane/metadata/android/it-IT/changelogs/42043.txt b/fastlane/metadata/android/it-IT/changelogs/42043.txt new file mode 100644 index 0000000000000000000000000000000000000000..95b94c630dc69cb9d7b5e4bb611f62680d8b820a --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42043.txt @@ -0,0 +1 @@ +* Corretta regressione nel trasferimento di file P2P diff --git a/fastlane/metadata/android/it-IT/changelogs/42044.txt b/fastlane/metadata/android/it-IT/changelogs/42044.txt new file mode 100644 index 0000000000000000000000000000000000000000..8354cbf1e03db92b374c9b30cae0937839087a37 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42044.txt @@ -0,0 +1,3 @@ +* Corretto il reinvio dei messaggi quando si usa SASL2 +* Corretto il video nero tra alcuni dispositivi +* Corretto l'arresto anomalo delle password vuote diff --git a/fastlane/metadata/android/it-IT/changelogs/42046.txt b/fastlane/metadata/android/it-IT/changelogs/42046.txt new file mode 100644 index 0000000000000000000000000000000000000000..e8a8d3d2e49fe37055d9671bf46d124476f8933e --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42046.txt @@ -0,0 +1 @@ +* Integra il distributore UnifiedPush per facilitare i messaggi push ad altre app abilitate a UnifiedPush come Tusky e Fedilab diff --git a/fastlane/metadata/android/it-IT/changelogs/42047.txt b/fastlane/metadata/android/it-IT/changelogs/42047.txt new file mode 100644 index 0000000000000000000000000000000000000000..abc0c9e6c66ca67612917f93c4a60c147bfe8456 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42047.txt @@ -0,0 +1 @@ +* Corretto l'arresto anomalo del distributore UnifiedPush diff --git a/fastlane/metadata/android/it-IT/changelogs/42050.txt b/fastlane/metadata/android/it-IT/changelogs/42050.txt new file mode 100644 index 0000000000000000000000000000000000000000..071ad6d7534350c9f0c22323db24c204a7caeeb4 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42050.txt @@ -0,0 +1 @@ +* Aumenta il raggio degli angoli nelle immagini del profilo diff --git a/fastlane/metadata/android/it-IT/changelogs/42059.txt b/fastlane/metadata/android/it-IT/changelogs/42059.txt new file mode 100644 index 0000000000000000000000000000000000000000..405597cd00fceb5fbcc611278985a60a2e6fa51d --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42059.txt @@ -0,0 +1,2 @@ +* Aggiornato l'SDK di destinazione di nuovo alla versione 33 +* Corretti problemi sui server che supportano SASL2 senza gestione inline dei flussi diff --git a/fastlane/metadata/android/it-IT/changelogs/42060.txt b/fastlane/metadata/android/it-IT/changelogs/42060.txt new file mode 100644 index 0000000000000000000000000000000000000000..14a8a6a1f82cf24161f7778fe66a7b235be0235f --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42060.txt @@ -0,0 +1 @@ +* Corretta la 'q' che viene erroneamente riconosciuta come cirillico diff --git a/fastlane/metadata/android/it-IT/changelogs/42061.txt b/fastlane/metadata/android/it-IT/changelogs/42061.txt new file mode 100644 index 0000000000000000000000000000000000000000..00af956c5ccf17643c3e6d267eeb69dad6216c17 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42061.txt @@ -0,0 +1 @@ +* Rimossa la funzione di scoperta dei canali dalla versione di Google Play diff --git a/fastlane/metadata/android/it-IT/changelogs/42062.txt b/fastlane/metadata/android/it-IT/changelogs/42062.txt new file mode 100644 index 0000000000000000000000000000000000000000..c2d024121d086c598a5f19f58184d89156b534b3 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42062.txt @@ -0,0 +1 @@ +* Disattiva l'apertura dei file di backup (.ceb) dal file manager diff --git a/fastlane/metadata/android/it-IT/changelogs/42065.txt b/fastlane/metadata/android/it-IT/changelogs/42065.txt new file mode 100644 index 0000000000000000000000000000000000000000..ae3fca64077c71db68f79f6d4944050ad6a4eb50 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42065.txt @@ -0,0 +1 @@ +* Introdotto un nuovo formato di file di backup diff --git a/fastlane/metadata/android/it-IT/changelogs/42068.txt b/fastlane/metadata/android/it-IT/changelogs/42068.txt new file mode 100644 index 0000000000000000000000000000000000000000..801dbb01578aff474fae3159ace1113c4b96debb --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42068.txt @@ -0,0 +1,2 @@ +* supporta impostazioni di notifica per singola conversazione +* usa opus per i messaggi vocali su Android 10 diff --git a/fastlane/metadata/android/it-IT/changelogs/42072.txt b/fastlane/metadata/android/it-IT/changelogs/42072.txt new file mode 100644 index 0000000000000000000000000000000000000000..c497edc325e98c1bede18502a01056efd5d2a8dc --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42072.txt @@ -0,0 +1,3 @@ +* Aggiornata la dipendenza libwebrtc a M117 e libvpx +* Ritorno a AAC per i messaggi vocali +* Supporta impostazioni di lingua per app diff --git a/fastlane/metadata/android/it-IT/changelogs/42074.txt b/fastlane/metadata/android/it-IT/changelogs/42074.txt new file mode 100644 index 0000000000000000000000000000000000000000..6757151188e06f72f244c27db0fd37ab36cbb310 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/42074.txt @@ -0,0 +1,3 @@ +* Supporto per DNS Privato (DNS over TLS) +* Supporto per icona del launcher a tema +* Risolto un raro problema di autorizzazione durante la condivisione di file su Android 11+ diff --git a/fastlane/metadata/android/it-IT/changelogs/4207704.txt b/fastlane/metadata/android/it-IT/changelogs/4207704.txt new file mode 100644 index 0000000000000000000000000000000000000000..6757151188e06f72f244c27db0fd37ab36cbb310 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/4207704.txt @@ -0,0 +1,3 @@ +* Supporto per DNS Privato (DNS over TLS) +* Supporto per icona del launcher a tema +* Risolto un raro problema di autorizzazione durante la condivisione di file su Android 11+ diff --git a/fastlane/metadata/android/ro/changelogs/349.txt b/fastlane/metadata/android/ro/changelogs/349.txt new file mode 100644 index 0000000000000000000000000000000000000000..0f1ea550124881f0d99b49f527ff006b08abea0c --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/349.txt @@ -0,0 +1,4 @@ +* Introducerea setărilor pentru experți pentru a efectua descoperirea canalelor pe serverul local în loc de search.jabber.network +* Activarea marcajelor de verificare a livrării în mod implicit și eliminarea setării +* Activarea "Butonul de trimitere indică starea" în mod implicit și eliminarea setării +*Mutarea setărilor Serviciului de rezervă și ale Serviciului de prim-plan în ecranul principal diff --git a/fastlane/metadata/android/ro/changelogs/351.txt b/fastlane/metadata/android/ro/changelogs/351.txt new file mode 100644 index 0000000000000000000000000000000000000000..629d86109f4a2b2e72d48fb678561e7b331168e1 --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/351.txt @@ -0,0 +1,3 @@ +* reparații pentru transferul de fișiere Jingle IBB +* reparații pentru corecțiile repetate care umplu baza de date +* schimbarea la Corectarea Ultimului Mesaj v1.1 diff --git a/fastlane/metadata/android/ro/changelogs/353.txt b/fastlane/metadata/android/ro/changelogs/353.txt new file mode 100644 index 0000000000000000000000000000000000000000..1c788dab60b724137029037ab61bd30fa835ee3b --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/353.txt @@ -0,0 +1,4 @@ +* utilizatorii pot să își seteze propria poreclă +* continuarea descărcării de fișiere criptate OMEMO +* Canalele folosesc '#' ca simbol în avatar +* Quicksy folosește 'mereu' ca și criptare implicită OMEMO (ascunde iconița lacăt) diff --git a/fastlane/metadata/android/ro/changelogs/360.txt b/fastlane/metadata/android/ro/changelogs/360.txt new file mode 100644 index 0000000000000000000000000000000000000000..7949e0d14c244836d87924f93d9fb618bfc3259c --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/360.txt @@ -0,0 +1 @@ +* Suport pentru parametrii uri ?register și ?register;preauth XMPP diff --git a/fastlane/metadata/android/ro/changelogs/362.txt b/fastlane/metadata/android/ro/changelogs/362.txt new file mode 100644 index 0000000000000000000000000000000000000000..46a802c73364e933440b093e71d3784cf789bf02 --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/362.txt @@ -0,0 +1 @@ +* Suport pentru comutarea automată a temei pe Android 10 diff --git a/fastlane/metadata/android/ro/changelogs/364.txt b/fastlane/metadata/android/ro/changelogs/364.txt new file mode 100644 index 0000000000000000000000000000000000000000..95ea86302b62f3e533e2e9740c424d17bb0b2bd8 --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/364.txt @@ -0,0 +1,2 @@ +* Furnizarea de previzualizări PDF pe Android 5+ +* Folosirea IV-urilor de 12 biți pentru OMEMO diff --git a/fastlane/metadata/android/ro/changelogs/367.txt b/fastlane/metadata/android/ro/changelogs/367.txt new file mode 100644 index 0000000000000000000000000000000000000000..9575f811dc50979dde71903f5ab0f2adabcae78a --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/367.txt @@ -0,0 +1,2 @@ +* Repararea selecției de avatar pe unele dispozitive ce rulează Android 10 +* Repararea transferului de fișiere pentru fișiere mari diff --git a/fastlane/metadata/android/ro/changelogs/379.txt b/fastlane/metadata/android/ro/changelogs/379.txt new file mode 100644 index 0000000000000000000000000000000000000000..7bbc1c965a9fbee463bd48f50db24ca8b6c75dff --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/379.txt @@ -0,0 +1 @@ +* Apeluri Audio/Video (Necesită suport pe server în formă de servere STUN și TURN descoperibile prin XEP-0125) diff --git a/fastlane/metadata/android/ro/changelogs/381.txt b/fastlane/metadata/android/ro/changelogs/381.txt new file mode 100644 index 0000000000000000000000000000000000000000..9d8e76ba31dfc3e6c28ec3c30098099517501e67 --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/381.txt @@ -0,0 +1,2 @@ +* Feedback auditoriu (apelare, apel început, apel terminat) pentru apeluri vocale +* Problemă rezolvată cu reîncercarea apelului video eșuat diff --git a/fastlane/metadata/android/ro/changelogs/382.txt b/fastlane/metadata/android/ro/changelogs/382.txt new file mode 100644 index 0000000000000000000000000000000000000000..f9eb35219302767fb59eb89faa4ac2782ce6d320 --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/382.txt @@ -0,0 +1,2 @@ +* Adăugarea butonului pentru a schimba camera în timpul apelului video +* Repararea apelurilor voce pe tablete diff --git a/fastlane/metadata/android/ro/changelogs/383.txt b/fastlane/metadata/android/ro/changelogs/383.txt new file mode 100644 index 0000000000000000000000000000000000000000..ee79d607eee5612a284692c497e01f97207bca42 --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/383.txt @@ -0,0 +1,3 @@ +* Mutarea iconiței de apel către stânga pentru a ține celelalte iconițe din bara de instrumente într-un loc consistent +* Afișarea durației apelurilor în timpul apelurilor audio +* Ruperea egalității pentru apeluri audio/video (aceleași două persoane care se sună între ele în același timp) diff --git a/fastlane/metadata/android/ro/changelogs/387.txt b/fastlane/metadata/android/ro/changelogs/387.txt new file mode 100644 index 0000000000000000000000000000000000000000..4faeea6b11ac64346b3bda7a06fba7a2d53244c2 --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/387.txt @@ -0,0 +1,2 @@ +* Refacerea logării cu UI pentru certificate +* Adăugarea abilității de a fixa conversații sus (adăugarea la favorite) diff --git a/fastlane/metadata/android/ro/changelogs/388.txt b/fastlane/metadata/android/ro/changelogs/388.txt new file mode 100644 index 0000000000000000000000000000000000000000..0fbd0d92e6b283114a8664080974f39000560fdd --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/388.txt @@ -0,0 +1,3 @@ +* Reducerea ecoului în timpul apelurilor pe unele dispozitive +* Repararea logării când parolele conțin caractere speciale +* Redarea tonurilor de apel și ocupat pe difuzor în timpul apelurilor video diff --git a/fastlane/metadata/android/ro/changelogs/390.txt b/fastlane/metadata/android/ro/changelogs/390.txt new file mode 100644 index 0000000000000000000000000000000000000000..8ae2da5cbdf371ce61f24e461d09ecc3f0dd0663 --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/390.txt @@ -0,0 +1 @@ +* Oferirea de a înregistra mesaj vocal când persoana apelată este ocupată diff --git a/fastlane/metadata/android/ro/changelogs/393.txt b/fastlane/metadata/android/ro/changelogs/393.txt new file mode 100644 index 0000000000000000000000000000000000000000..712b8e420ba4e78f2491f40287267d9c14840de6 --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/393.txt @@ -0,0 +1,3 @@ +* Afișarea butonului de ajutor dacă apelul audio/video eșuează +* Repararea unor crash-uri enervante +* Repararea conexiunilor Jingle (transfer fișiere + apeluri) cu JID-uri goale diff --git a/fastlane/metadata/android/uk/changelogs/349.txt b/fastlane/metadata/android/uk/changelogs/349.txt new file mode 100644 index 0000000000000000000000000000000000000000..03f0b8764ceeb74aa077f147e855280336bce62e --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/349.txt @@ -0,0 +1,4 @@ +* Додано Експертні налаштування для пошуку каналів на локальному сервері замість search.jabber.network +* Позначки про доставку увімкнено за замовчуванням, а налаштування видалено +* «Кнопка надсилання показує стан» увімкнено за замовчуванням, а налаштування видалено +* Налаштування резервного копіювання і процесу на передньому плані перенесено на основний екран diff --git a/fastlane/metadata/android/uk/changelogs/351.txt b/fastlane/metadata/android/uk/changelogs/351.txt new file mode 100644 index 0000000000000000000000000000000000000000..4b92092b90954745a610ca4be3b345bfdb674e23 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/351.txt @@ -0,0 +1,3 @@ +* Виправлення обміну файлами Jingle IBB +* Повторювані виправлення правопису більше не заповнюють базу даних +* Перехід на Last Message Correction v1.1 diff --git a/fastlane/metadata/android/uk/changelogs/353.txt b/fastlane/metadata/android/uk/changelogs/353.txt new file mode 100644 index 0000000000000000000000000000000000000000..e0ab4b1daf398e664585812154ce42183f84c49e --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/353.txt @@ -0,0 +1,4 @@ +* Користувачі можуть встановлювати своє прізвисько (нікнейм) +* Відновлювати завантаження файлів, зашифрованих OMEMO +* Канали тепер позначаються символом «#» на піктограмі +* Quicksy за замовчуванням використовує «завжди» для шифрування OMEMO (приховує значок замка) diff --git a/fastlane/metadata/android/uk/changelogs/360.txt b/fastlane/metadata/android/uk/changelogs/360.txt new file mode 100644 index 0000000000000000000000000000000000000000..6685cdb3a9ee792af1f7e870c2b26088997f018a --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/360.txt @@ -0,0 +1 @@ +* Підтримка параметрів XMPP URI ?register та ?register;preauth diff --git a/fastlane/metadata/android/uk/changelogs/362.txt b/fastlane/metadata/android/uk/changelogs/362.txt new file mode 100644 index 0000000000000000000000000000000000000000..742bbaab63499cafdd4ba81e25d55354fdd92395 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/362.txt @@ -0,0 +1 @@ +* Підтримка автоматичного перемикання теми на Android 10 diff --git a/fastlane/metadata/android/uk/changelogs/364.txt b/fastlane/metadata/android/uk/changelogs/364.txt new file mode 100644 index 0000000000000000000000000000000000000000..acf1518acc4178a9aed1150a6c1bb3a3211cf96b --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/364.txt @@ -0,0 +1,2 @@ +* Попередній перегляд PDF на Android 5 і новіших +* Використання 12-байтових IV для OMEMO diff --git a/fastlane/metadata/android/uk/changelogs/367.txt b/fastlane/metadata/android/uk/changelogs/367.txt new file mode 100644 index 0000000000000000000000000000000000000000..4e697be13f077bb41714522ba476de35a0216ca0 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/367.txt @@ -0,0 +1,2 @@ +* Виправлено вибір піктограми користувача на деяких пристроях з Android 10 +* Виправлення обміну файлами для великих файлів diff --git a/fastlane/metadata/android/uk/changelogs/379.txt b/fastlane/metadata/android/uk/changelogs/379.txt new file mode 100644 index 0000000000000000000000000000000000000000..0143a133a05519739990e3df914493e1c8f27320 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/379.txt @@ -0,0 +1 @@ +* Голосові та відеовиклики (необхідна підтримка сервера у вигляді серверів STUN і TURN, доступних для виявлення через XEP-0215) diff --git a/fastlane/metadata/android/uk/changelogs/381.txt b/fastlane/metadata/android/uk/changelogs/381.txt new file mode 100644 index 0000000000000000000000000000000000000000..c59e5669c4bd8b92aca135f5f91158ed94e8b1da --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/381.txt @@ -0,0 +1,2 @@ +* Зворотний зв'язок (звуки «набір номера», «початок дзвінка», «завершення дзвінка») для голосових викликів +* Виправлено проблему з повторною спробою невдалого відеовиклику diff --git a/fastlane/metadata/android/uk/changelogs/382.txt b/fastlane/metadata/android/uk/changelogs/382.txt new file mode 100644 index 0000000000000000000000000000000000000000..49a02dfbb0f02a2e6c84c3c367e7cc0a1d87360f --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/382.txt @@ -0,0 +1,2 @@ +* Додано кнопку перемикання камери під час відеовиклику +* Виправлення для голосових дзвінків на планшетах diff --git a/fastlane/metadata/android/uk/changelogs/383.txt b/fastlane/metadata/android/uk/changelogs/383.txt new file mode 100644 index 0000000000000000000000000000000000000000..66d879d4b95121f4a51c79be5a5e9236f9c942a4 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/383.txt @@ -0,0 +1,3 @@ +* Значок дзвінка переміщено ліворуч, щоб інші значки панелі інструментів залишалися на відповідних місцях +* Показувати тривалість розмови під час голосових викликів +* Визначення переваги в голосових та відеовикликах (двоє людей телефонують один одному одночасно) diff --git a/fastlane/metadata/android/uk/changelogs/387.txt b/fastlane/metadata/android/uk/changelogs/387.txt new file mode 100644 index 0000000000000000000000000000000000000000..a26a7642f4887fc760b435b664eed3f4cac7407a --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/387.txt @@ -0,0 +1,2 @@ +* Перероблено інтерфейс входу з сертифікатом +* Додано можливість закріплювати чати (додати до вибраного) diff --git a/fastlane/metadata/android/uk/changelogs/388.txt b/fastlane/metadata/android/uk/changelogs/388.txt new file mode 100644 index 0000000000000000000000000000000000000000..6e5f7899a6e486f80f17e214d857521c5d6850e3 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/388.txt @@ -0,0 +1,3 @@ +* Зменшено відлуння під час викликів на деяких пристроях +* Виправлено вхід з паролями, що містять спеціальні символи +* Сигнали набору номера та зайнятості відтворюються через динамік під час відеовикликів diff --git a/fastlane/metadata/android/uk/changelogs/390.txt b/fastlane/metadata/android/uk/changelogs/390.txt new file mode 100644 index 0000000000000000000000000000000000000000..f12a56fda86b2d620bb8cc5b8627018af609b217 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/390.txt @@ -0,0 +1 @@ +* Можливість записати голосове повідомлення, коли абонент зайнятий diff --git a/fastlane/metadata/android/uk/changelogs/393.txt b/fastlane/metadata/android/uk/changelogs/393.txt new file mode 100644 index 0000000000000000000000000000000000000000..c4bd20e66965303f9e7baa4751b9a93a12894b70 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/393.txt @@ -0,0 +1,3 @@ +* Показувати кнопку «Довідка» у випадку невдалого голосового чи відеовиклику +* Виправлено деякі неприємні збої +* Виправлено з'єднання Jingle (обмін файлами + дзвінки) з JID'ами без ресурсу diff --git a/fastlane/metadata/android/uk/changelogs/394.txt b/fastlane/metadata/android/uk/changelogs/394.txt new file mode 100644 index 0000000000000000000000000000000000000000..374c95fec7e02eacb1fcc8375c60c5adf6c7a103 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/394.txt @@ -0,0 +1,2 @@ +* Виправлено сповіщення, які не з'являлися за певних умов +* Виправлення проблем сумісності та збоїв, пов’язаних з голосовими та відеовикликами diff --git a/fastlane/metadata/android/uk/changelogs/395.txt b/fastlane/metadata/android/uk/changelogs/395.txt new file mode 100644 index 0000000000000000000000000000000000000000..890b5c4739597ac5df3989d058aa069537f21898 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/395.txt @@ -0,0 +1,3 @@ +* Додано «Повернутися до чату» на екрані звукового виклику +* Удосконалено комбінації клавіш +* Виправлення помилок diff --git a/fastlane/metadata/android/uk/changelogs/397.txt b/fastlane/metadata/android/uk/changelogs/397.txt new file mode 100644 index 0000000000000000000000000000000000000000..0c9af05087f8f99782d8fc0bfe7536f4e43cde60 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/397.txt @@ -0,0 +1,3 @@ +* Обробляти файли GPX +* Покращення продуктивності при відновленні резервної копії +* Виправлення помилок diff --git a/fastlane/metadata/android/uk/changelogs/398.txt b/fastlane/metadata/android/uk/changelogs/398.txt new file mode 100644 index 0000000000000000000000000000000000000000..837e85eac4c41cd7399f57f5e373c149f0e70797 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/398.txt @@ -0,0 +1,4 @@ +* Пошук в окремих розмовах +* Сповіщення про невдале надсилання повідомлень +* Імена (нікнейми) користувачів Quicksy зберігаються після перезапуску застосунку +* Додано кнопку для запуску Orbot (Tor) із сповіщення, якщо це необхідно diff --git a/fastlane/metadata/android/uk/changelogs/401.txt b/fastlane/metadata/android/uk/changelogs/401.txt new file mode 100644 index 0000000000000000000000000000000000000000..dbce88932fdf275733733eaa99bae8ad3934e202 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/401.txt @@ -0,0 +1,2 @@ +* Виправлено пошук на версіях Android до 5-ї включно +* Оптимізація використання пам'яті diff --git a/fastlane/metadata/android/uk/changelogs/402.txt b/fastlane/metadata/android/uk/changelogs/402.txt new file mode 100644 index 0000000000000000000000000000000000000000..1f2ec0fbd50a2ffd6eaccd3a20420059b504bdb2 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/402.txt @@ -0,0 +1,3 @@ +* Просте створення запрошень на серверах з підтримкою запрошень +* Перегляд файлів GIF, отриманих з Movim +* Піктограми користувачів зберігаються у кеші diff --git a/fastlane/metadata/android/uk/changelogs/403.txt b/fastlane/metadata/android/uk/changelogs/403.txt new file mode 100644 index 0000000000000000000000000000000000000000..75ec2df348a9566fe0f65ac233121c24b7c4d149 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/403.txt @@ -0,0 +1,3 @@ +* Виправлено проблеми з підключенням, коли різні облікові записи використовували різні механізми SCRAM +* Додано підтримку SCRAM-SHA-512 +* Дозволено обмін файлами P2P (Jingle) із власним контактом diff --git a/fastlane/metadata/android/uk/changelogs/404.txt b/fastlane/metadata/android/uk/changelogs/404.txt new file mode 100644 index 0000000000000000000000000000000000000000..130dce1c6d48ca4f5be4103b1bfaa3fec895788b --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/404.txt @@ -0,0 +1 @@ +* Незначні покращення стабільності для голосових та відеовикликів diff --git a/fastlane/metadata/android/uk/changelogs/405.txt b/fastlane/metadata/android/uk/changelogs/405.txt new file mode 100644 index 0000000000000000000000000000000000000000..215a7e262c32ce86b4ef5c27b405afb99a349ea6 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/405.txt @@ -0,0 +1 @@ +* Quicksy: Автоматично отримувати SMS підтвердження diff --git a/fastlane/metadata/android/uk/changelogs/407.txt b/fastlane/metadata/android/uk/changelogs/407.txt new file mode 100644 index 0000000000000000000000000000000000000000..abc18bf99d64bcf7a88a5ea18f0d24e0764a4701 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/407.txt @@ -0,0 +1,3 @@ +* Показувати кнопку виклику для контактів поза мережею, якщо вони раніше оголосили про підтримку дзвінків +* Кнопка «Назад» більше не завершує дзвінок під час виклику +* Виправлення помилок diff --git a/fastlane/metadata/android/uk/changelogs/42000.txt b/fastlane/metadata/android/uk/changelogs/42000.txt new file mode 100644 index 0000000000000000000000000000000000000000..7657ede43b1162d050f802978b3add3062871068 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42000.txt @@ -0,0 +1,4 @@ +* Можливість вибирати мелодію для вхідних викликів +* Виправлено виявлення ідентифікатора ключа OpenPGP для OpenKeychain 5.6+ +* Коректна перевірка сертифікатів punycode TLS +* Покращення стабільності встановлення сесії RTP (дзвінки) diff --git a/fastlane/metadata/android/uk/changelogs/42006.txt b/fastlane/metadata/android/uk/changelogs/42006.txt new file mode 100644 index 0000000000000000000000000000000000000000..6077b0312005fa1785497090ea995cc1aaca2b18 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42006.txt @@ -0,0 +1,2 @@ +* Перевіряти голосові та відеовиклики за допомогою вже існуючих сесій OMEMO +* Покращено сумісність із реалізаціями WebRTC без libwebrtc diff --git a/fastlane/metadata/android/uk/changelogs/42010.txt b/fastlane/metadata/android/uk/changelogs/42010.txt new file mode 100644 index 0000000000000000000000000000000000000000..0c25752b46c377692b80c4bd88f4580857632ec0 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42010.txt @@ -0,0 +1,2 @@ +* Виправлення різноманітних помилок у підтримці Tor +* Покращення сумісності дзвінків із Dino diff --git a/fastlane/metadata/android/uk/changelogs/42012.txt b/fastlane/metadata/android/uk/changelogs/42012.txt new file mode 100644 index 0000000000000000000000000000000000000000..654b6af7fe1c5b538249ecc71e2ad2fa6ea174f5 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42012.txt @@ -0,0 +1 @@ +* Виправлено передачу/завантаження через HTTP для користувачів, які не довіряють системним ЦС diff --git a/fastlane/metadata/android/uk/changelogs/42013.txt b/fastlane/metadata/android/uk/changelogs/42013.txt new file mode 100644 index 0000000000000000000000000000000000000000..f1a182eb3a2e9fe4a9cfb6d6799ab5f3510de112 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42013.txt @@ -0,0 +1 @@ +* Виправлено проблеми з повідомленням про відсутність з'єднання на Android 7.1 diff --git a/fastlane/metadata/android/uk/changelogs/42014.txt b/fastlane/metadata/android/uk/changelogs/42014.txt new file mode 100644 index 0000000000000000000000000000000000000000..39b9354ce73567d8a99910f8b801bcb18ad6173d --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42014.txt @@ -0,0 +1,2 @@ +* Завжди перевіряти ім'я домену. Без перезапису користувачем +* Підтримка попередньої автентифікації списку контактів diff --git a/fastlane/metadata/android/uk/changelogs/42015.txt b/fastlane/metadata/android/uk/changelogs/42015.txt new file mode 100644 index 0000000000000000000000000000000000000000..e8018465739dfa8f30b4538f9b59e75465ac421e --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42015.txt @@ -0,0 +1 @@ +* Незначні покращення голосових та відеовикликів diff --git a/fastlane/metadata/android/uk/changelogs/42018.txt b/fastlane/metadata/android/uk/changelogs/42018.txt new file mode 100644 index 0000000000000000000000000000000000000000..01ffffc7806033a3c702407401bafaf7aa12065a --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42018.txt @@ -0,0 +1,3 @@ +* Показувати чорні смуги, коли віддалене відео не відповідає пропорціям екрана +* Покращення ефективності пошуку +* Додано налаштування для заборони знімків екрана diff --git a/fastlane/metadata/android/uk/changelogs/42022.txt b/fastlane/metadata/android/uk/changelogs/42022.txt new file mode 100644 index 0000000000000000000000000000000000000000..4a5bfc7bbbda48b012e41e96b07b8aeba2fd5be2 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42022.txt @@ -0,0 +1,2 @@ +* Виправлено проблему коли деякі відео не стискалися +* Виправлено рідкісний збій під час відкриття сповіщення diff --git a/fastlane/metadata/android/uk/changelogs/42023.txt b/fastlane/metadata/android/uk/changelogs/42023.txt new file mode 100644 index 0000000000000000000000000000000000000000..e85b7c0ad148da801871536e8d3118e374d024b4 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42023.txt @@ -0,0 +1,2 @@ +* Виправлено збій при відтворенні деяких лапок +* Виправлено збій на екрані привітання diff --git a/fastlane/metadata/android/uk/changelogs/42037.txt b/fastlane/metadata/android/uk/changelogs/42037.txt new file mode 100644 index 0000000000000000000000000000000000000000..723563a95b3fe62fc2febb3fd34fd4251037a8c6 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42037.txt @@ -0,0 +1,11 @@ +Версія 2.10.9 +* Запитувати дозволи Bluetooth для голосових та відеовикликів (можна відхилити, якщо не використовуєте гарнітуру Bluetooth) +* Виправлено помилку під час виклику Movim +* Виправлено відображення неправильної піктограми для групових чатів +* Завжди запитувати про вимкнення оптимізації батареї +* Установлено прапорець «лише локально» для сповіщень «x облікових записів у мережі» +* Виправлено взаємодію з плагіном Google Maps Share Location +* Видалено примітку щодо плати за сервер +* Зберігати файли в місці, яке підходить для Android 11 +* Пробувати повторно підключити виклик після перемикання мережі +* Показувати JID абонента та JID облікового запису на екрані вхідного виклику diff --git a/fastlane/metadata/android/uk/changelogs/42038.txt b/fastlane/metadata/android/uk/changelogs/42038.txt new file mode 100644 index 0000000000000000000000000000000000000000..df23d26647180a61addbb8e072f5ea81d4651585 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42038.txt @@ -0,0 +1,2 @@ +* Незначні виправлення помилок +* Відновлено можливість викликів через JMP та інші служби (версія Playstore) diff --git a/fastlane/metadata/android/uk/changelogs/42041.txt b/fastlane/metadata/android/uk/changelogs/42041.txt new file mode 100644 index 0000000000000000000000000000000000000000..f0c9635229e92a1a904cae432ba467767f9ce05d --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42041.txt @@ -0,0 +1,5 @@ +* Реалізація Extensible SASL Profile, Bind 2.0 і Fast для швидшого повторного з'єднання +* Реалізація Channel Binding +* Додано можливість перемикатися з голосового на відеовиклик +* Додано можливість видаляти свою піктограму користувача +* Додано сповіщення про пропущені виклики diff --git a/fastlane/metadata/android/uk/changelogs/42042.txt b/fastlane/metadata/android/uk/changelogs/42042.txt new file mode 100644 index 0000000000000000000000000000000000000000..771ad5b106c2039614a01840cabd84f4d8b83c14 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42042.txt @@ -0,0 +1,2 @@ +* Виправлено циклічне повторне надсилання на сервери, які підтримують лише sm:2 +* Показувати «Перемкнути на відео» тільки якщо інша сторона підтримує відео diff --git a/fastlane/metadata/android/uk/changelogs/42043.txt b/fastlane/metadata/android/uk/changelogs/42043.txt new file mode 100644 index 0000000000000000000000000000000000000000..a92ebf0fca121ba5cb0ee3db0892f0a16826d237 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42043.txt @@ -0,0 +1 @@ +* Виправлено регресивну помилку в обміні файлами P2P diff --git a/fastlane/metadata/android/uk/changelogs/42044.txt b/fastlane/metadata/android/uk/changelogs/42044.txt new file mode 100644 index 0000000000000000000000000000000000000000..8facdb3bf5fa3ce25e8f0c67d0367d44c8de4f52 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42044.txt @@ -0,0 +1,3 @@ +* Виправлено повторне надсилання повідомлень при використанні SASL2 +* Виправлення чорного відео між деякими пристроями +* Виправлено збій з порожніми паролями diff --git a/fastlane/metadata/android/uk/changelogs/42046.txt b/fastlane/metadata/android/uk/changelogs/42046.txt new file mode 100644 index 0000000000000000000000000000000000000000..36f6ac725ec714927f4d9c2212a83ca678690b23 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42046.txt @@ -0,0 +1 @@ +* Інтегрований дистриб'ютор UnifiedPush для надсилання push-повідомлень іншим застосункам, які підтримують UnifiedPush, як-от Tusky і Fedilab diff --git a/fastlane/metadata/android/uk/changelogs/42047.txt b/fastlane/metadata/android/uk/changelogs/42047.txt new file mode 100644 index 0000000000000000000000000000000000000000..5cf6316f1da0d08add1521c924ca2ef26d1f9388 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42047.txt @@ -0,0 +1 @@ +* Виправлено збій у дистриб'юторі UnifiedPush diff --git a/fastlane/metadata/android/uk/changelogs/42050.txt b/fastlane/metadata/android/uk/changelogs/42050.txt new file mode 100644 index 0000000000000000000000000000000000000000..cc1ed9ce3ca96a372ee6c4989a1720e2bebac5e0 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42050.txt @@ -0,0 +1 @@ +* Збільшено радіус заокруглення кутів зображення профілю diff --git a/fastlane/metadata/android/uk/changelogs/42059.txt b/fastlane/metadata/android/uk/changelogs/42059.txt new file mode 100644 index 0000000000000000000000000000000000000000..06af124bb8190d36741973dcd81e359b529bd35d --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42059.txt @@ -0,0 +1,2 @@ +* Цільовий SDK знову підвищено до 33 +* Виправлення проблем із серверами, які підтримують SASL2 без вбудованого керування потоком diff --git a/fastlane/metadata/android/uk/changelogs/42060.txt b/fastlane/metadata/android/uk/changelogs/42060.txt new file mode 100644 index 0000000000000000000000000000000000000000..20fde53a4973faa8c1939e1a8a5b8ab09e9e236a --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42060.txt @@ -0,0 +1 @@ +* Виправлено помилкове розпізнавання літери «q» як кириличної diff --git a/fastlane/metadata/android/uk/changelogs/42061.txt b/fastlane/metadata/android/uk/changelogs/42061.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b4441c6ee2b243a0ee09eb89ffc4261a2d7f3ef --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42061.txt @@ -0,0 +1 @@ +* Видалено функцію пошуку каналів із версії Google Play diff --git a/fastlane/metadata/android/uk/changelogs/42062.txt b/fastlane/metadata/android/uk/changelogs/42062.txt new file mode 100644 index 0000000000000000000000000000000000000000..f5768cc6174f52f346791c522b8094a878140d68 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42062.txt @@ -0,0 +1 @@ +* Вимкнено відкривання файлів резервних копій (.ceb) із файлового менеджера diff --git a/fastlane/metadata/android/uk/changelogs/42065.txt b/fastlane/metadata/android/uk/changelogs/42065.txt new file mode 100644 index 0000000000000000000000000000000000000000..4a8122be71de7a4f50fdfbe2ee94009fb69ded68 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42065.txt @@ -0,0 +1 @@ +* Запроваджено новий формат файлу резервної копії diff --git a/fastlane/metadata/android/uk/changelogs/42068.txt b/fastlane/metadata/android/uk/changelogs/42068.txt new file mode 100644 index 0000000000000000000000000000000000000000..eabdf5366a0ecf424e0c120bff3b71b9e90ef398 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42068.txt @@ -0,0 +1,2 @@ +* Підтримка налаштування сповіщень окремо для кожної розмови +* Використання Opus для голосових повідомлень на Android 10 diff --git a/fastlane/metadata/android/uk/changelogs/42072.txt b/fastlane/metadata/android/uk/changelogs/42072.txt new file mode 100644 index 0000000000000000000000000000000000000000..b8ff7f7c744b06f7759142ce5f19f8822febbfcc --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/42072.txt @@ -0,0 +1,3 @@ +* Підвищено залежність libwebrtc до M117 і оновлено libvpx +* Повернення до AAC для голосових повідомлень +* Підтримка своїх налаштувань мови в додатку diff --git a/fastlane/metadata/android/uk/changelogs/4207704.txt b/fastlane/metadata/android/uk/changelogs/4207704.txt new file mode 100644 index 0000000000000000000000000000000000000000..db66544913adaf256b6daddbda609cf9ddce28a4 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4207704.txt @@ -0,0 +1,3 @@ +* Підтримка приватної DNS (DNS over TLS) +* Підтримка тематичного значка додатка в лаунчері +* Виправлено рідкісну проблему з дозволами під час обміну файлами на Android 11 і новіших diff --git a/fastlane/metadata/android/zh-CN/changelogs/349.txt b/fastlane/metadata/android/zh-CN/changelogs/349.txt new file mode 100644 index 0000000000000000000000000000000000000000..689259e2bd44667050d80006c3d0dd3c4edeec74 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/349.txt @@ -0,0 +1,4 @@ +* 引入专家设置在本地服务器上执行频道发现而不是 search.jabber.network +* 默认启用传递复选标记并移除设置 +* 默认启用“发送按钮指示状态”并移除设置 +* 将备份和前台服务设置移至主屏幕 diff --git a/fastlane/metadata/android/zh-CN/changelogs/351.txt b/fastlane/metadata/android/zh-CN/changelogs/351.txt new file mode 100644 index 0000000000000000000000000000000000000000..c3ff6bc46c8682cd79acda658fb01870c87b16f5 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/351.txt @@ -0,0 +1,3 @@ +* 修复了 Jingle IBB 文件传输问题 +* 修复了重复更正填满数据库的问题 +* 切换到最后消息更正 v1.1 diff --git a/fastlane/metadata/android/zh-CN/changelogs/353.txt b/fastlane/metadata/android/zh-CN/changelogs/353.txt new file mode 100644 index 0000000000000000000000000000000000000000..75d7425d1c0e6224ec494ea6a82fc10fb24b4941 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/353.txt @@ -0,0 +1,4 @@ +* 让用户设置自己的昵称 +* 恢复 OMEMO 加密文件的下载 +* 频道现在使用“#”作为头像中的符号 +* Quicksy 使用“始终”作为 OMEMO 加密默认值(隐藏锁定图标) diff --git a/fastlane/metadata/android/zh-CN/changelogs/360.txt b/fastlane/metadata/android/zh-CN/changelogs/360.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc2e905de25188c93aeb10dc110e60823cf20196 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/360.txt @@ -0,0 +1 @@ +* 支持 ?register 和 ?register;preauth XMPP uri 参数 diff --git a/fastlane/metadata/android/zh-CN/changelogs/362.txt b/fastlane/metadata/android/zh-CN/changelogs/362.txt new file mode 100644 index 0000000000000000000000000000000000000000..27249c94f0982e368be36ce85f94542e6b5c6005 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/362.txt @@ -0,0 +1 @@ +* 支持在 Android 10 上自动切换主题 diff --git a/fastlane/metadata/android/zh-CN/changelogs/364.txt b/fastlane/metadata/android/zh-CN/changelogs/364.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b4d64fcbc3fc9536194f1826847049da4aa2c9a --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/364.txt @@ -0,0 +1,2 @@ +* 在 Android 5 以上版本上提供 PDF 预览 +* 为 OMEMO 使用 12 byte IVs diff --git a/fastlane/metadata/android/zh-CN/changelogs/367.txt b/fastlane/metadata/android/zh-CN/changelogs/367.txt new file mode 100644 index 0000000000000000000000000000000000000000..59ac67d5071c51943b55173e655cf50770103332 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/367.txt @@ -0,0 +1,2 @@ +* 修复部分 Android 10 设备上的头像选择问题 +* 修复较大文件的文件传输 diff --git a/fastlane/metadata/android/zh-CN/changelogs/379.txt b/fastlane/metadata/android/zh-CN/changelogs/379.txt new file mode 100644 index 0000000000000000000000000000000000000000..9bd6e9e93f34faae2a25dd85265eba6570bd71d2 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/379.txt @@ -0,0 +1 @@ +* 音频/视频通话(需要通过 XEP-0215 发现的 STUN 和 TURN 服务器形式的服务器支持) diff --git a/fastlane/metadata/android/zh-CN/changelogs/381.txt b/fastlane/metadata/android/zh-CN/changelogs/381.txt new file mode 100644 index 0000000000000000000000000000000000000000..b8db10afda709251446d993d0bbe2cae48c057c3 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/381.txt @@ -0,0 +1,2 @@ +* 语音通话的声音反馈(拨号、通话开始、通话结束)。 +* 修复了重试失败视频通话的问题 diff --git a/fastlane/metadata/android/zh-CN/changelogs/382.txt b/fastlane/metadata/android/zh-CN/changelogs/382.txt new file mode 100644 index 0000000000000000000000000000000000000000..334e75d436c53b456b40afebda522312b957716f --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/382.txt @@ -0,0 +1,2 @@ +* 添加视频通话时切换摄像头的按钮 +* 修复了平板电脑上的语音通话问题 diff --git a/fastlane/metadata/android/zh-CN/changelogs/383.txt b/fastlane/metadata/android/zh-CN/changelogs/383.txt new file mode 100644 index 0000000000000000000000000000000000000000..31ee7aa89172a364c6f38c0946d442f57cd38a0b --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/383.txt @@ -0,0 +1,3 @@ +* 将通话图标移至左侧,以保持其他工具栏图标在一致的位置 +* 音频通话时显示通话时长 +* 音频/视频通话打破僵局(两个人同时打电话给对方) diff --git a/fastlane/metadata/android/zh-CN/changelogs/387.txt b/fastlane/metadata/android/zh-CN/changelogs/387.txt new file mode 100644 index 0000000000000000000000000000000000000000..e8c9f29a3d77d81ffd82ca17122efb9d588e6e15 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/387.txt @@ -0,0 +1,2 @@ +* 重新设计使用证书登录的用户界面 +* 添加将聊天固定在顶部的功能(添加到收藏夹) diff --git a/fastlane/metadata/android/zh-CN/changelogs/388.txt b/fastlane/metadata/android/zh-CN/changelogs/388.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b8536bbefff63fe1776ae7c7895e0d0790fdbcf --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/388.txt @@ -0,0 +1,3 @@ +* 在某些设备上通话时减少回声 +* 修复密码包含特殊字符时的登录问题 +* 视频通话期间扬声器上播放拨号音和忙音 diff --git a/fastlane/metadata/android/zh-CN/changelogs/390.txt b/fastlane/metadata/android/zh-CN/changelogs/390.txt new file mode 100644 index 0000000000000000000000000000000000000000..ee78f954de80e31df09467f3a34e7b5c4c8561d6 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/390.txt @@ -0,0 +1 @@ +* 在接听者忙时提供录制语音消息服务 diff --git a/fastlane/metadata/android/zh-CN/changelogs/393.txt b/fastlane/metadata/android/zh-CN/changelogs/393.txt new file mode 100644 index 0000000000000000000000000000000000000000..1d903fe6cb697dbac92095a86f3dea2372c826a3 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/393.txt @@ -0,0 +1,3 @@ +* 如果音频/视频通话失败则显示帮助按钮 +* 修复了一些恼人的崩溃问题 +* 修复了带有纯 JID 的 Jingle 连接(文件传输 + 通话) diff --git a/fastlane/metadata/android/zh-CN/changelogs/394.txt b/fastlane/metadata/android/zh-CN/changelogs/394.txt new file mode 100644 index 0000000000000000000000000000000000000000..df4f52a34a8368ec0b69d615a4d86daeec922c98 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/394.txt @@ -0,0 +1,2 @@ +* 修复了某些情况下不显示通知的问题 +* 修复了与音频/视频通话相关的兼容性问题和崩溃 diff --git a/fastlane/metadata/android/zh-CN/changelogs/395.txt b/fastlane/metadata/android/zh-CN/changelogs/395.txt new file mode 100644 index 0000000000000000000000000000000000000000..010d78c4da374312af4ef225b74efb5f425d7b90 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/395.txt @@ -0,0 +1,3 @@ +* 在音频通话屏幕中添加“返回聊天” +* 改进键盘快捷键 +* bug 修复 diff --git a/fastlane/metadata/android/zh-CN/changelogs/397.txt b/fastlane/metadata/android/zh-CN/changelogs/397.txt new file mode 100644 index 0000000000000000000000000000000000000000..96c3815c3c5f9924c6aa8b701edc7f6ada32ff7f --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/397.txt @@ -0,0 +1,3 @@ +* 处理 GPX 文件 +* 提高备份恢复性能 +* bug 修复 diff --git a/fastlane/metadata/android/zh-CN/changelogs/398.txt b/fastlane/metadata/android/zh-CN/changelogs/398.txt new file mode 100644 index 0000000000000000000000000000000000000000..5a3df8a3ff6c4e670c57ca5bf4a723c6c2d475fb --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/398.txt @@ -0,0 +1,4 @@ +* 搜索个人对话 +* 消息传递失败时通知用户 +* 重新启动时记住 Quicksy 用户的显示名称(昵称) +* 如有必要,添加按钮以从通知中启动 Orbot(Tor) diff --git a/fastlane/metadata/android/zh-CN/changelogs/401.txt b/fastlane/metadata/android/zh-CN/changelogs/401.txt new file mode 100644 index 0000000000000000000000000000000000000000..df75923de69c44cacde684af8145dbeb4f397c05 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/401.txt @@ -0,0 +1,2 @@ +* 修复了 Android <= 5 上的搜索 +* 优化内存消耗 diff --git a/fastlane/metadata/android/zh-CN/changelogs/402.txt b/fastlane/metadata/android/zh-CN/changelogs/402.txt new file mode 100644 index 0000000000000000000000000000000000000000..7bcabf3b9d17b45da26f8111e73c80bd1554a2af --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/402.txt @@ -0,0 +1,3 @@ +* 在支持的服务器上提供简易邀请生成功能 +* 显示从 Movim 发送的 GIF +* 在缓存中存储头像 diff --git a/fastlane/metadata/android/zh-CN/changelogs/403.txt b/fastlane/metadata/android/zh-CN/changelogs/403.txt new file mode 100644 index 0000000000000000000000000000000000000000..52a13575a2733ebce38216acfa9b1eac64485572 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/403.txt @@ -0,0 +1,3 @@ +* 修复了不同账号使用不同 SCRAM 机制时的连接问题 +* 添加对 SCRAM-SHA-512 的支持 +* 允许通过自联系进行 P2P(Jingle)文件传输 diff --git a/fastlane/metadata/android/zh-CN/changelogs/404.txt b/fastlane/metadata/android/zh-CN/changelogs/404.txt new file mode 100644 index 0000000000000000000000000000000000000000..4619eb7ccd163a97c5b284ef9ece75687ae03aa9 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/404.txt @@ -0,0 +1 @@ +* 对音频/视频通话的稳定性略有改善 diff --git a/fastlane/metadata/android/zh-CN/changelogs/405.txt b/fastlane/metadata/android/zh-CN/changelogs/405.txt new file mode 100644 index 0000000000000000000000000000000000000000..a0fa53f8b6d9db7928655b0c7d51300879e1944c --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/405.txt @@ -0,0 +1 @@ +* Quicksy:自动接收验证短信 diff --git a/fastlane/metadata/android/zh-CN/changelogs/407.txt b/fastlane/metadata/android/zh-CN/changelogs/407.txt new file mode 100644 index 0000000000000000000000000000000000000000..33e0919ada68f15c4a7f313c2e3206680cc14389 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/407.txt @@ -0,0 +1,3 @@ +* 如果离线联系人之前已宣布支持,则显示呼叫按钮 +* 通话接通后,后退按钮不再结束通话 +* bug 修复 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42000.txt b/fastlane/metadata/android/zh-CN/changelogs/42000.txt new file mode 100644 index 0000000000000000000000000000000000000000..8aedfaed840699ba202ffe6616209491542388fd --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42000.txt @@ -0,0 +1,4 @@ +* 能够选择来电铃声 +* 修复 OpenKeychain 5.6+ 的 OpenPGP 密钥 ID 发现问题 +* 正确验证 punycode TLS 证书 +* 提高 RTP 会话建立(呼叫)的稳定性 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42006.txt b/fastlane/metadata/android/zh-CN/changelogs/42006.txt new file mode 100644 index 0000000000000000000000000000000000000000..d9f60038fee32a9c507dd612024deb8a28263a14 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42006.txt @@ -0,0 +1,2 @@ +* 使用预先存在的 OMEMO 会话验证音频/视频通话 +* 提高与非 libwebrtc WebRTC 实现的兼容性 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42010.txt b/fastlane/metadata/android/zh-CN/changelogs/42010.txt new file mode 100644 index 0000000000000000000000000000000000000000..09fddc261568f0e72ea3b5cdf02d2689cf9ffa24 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42010.txt @@ -0,0 +1,2 @@ +* 修复了有关 Tor 支持的各种错误 +* 改进与 Dino 的通话兼容性 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42012.txt b/fastlane/metadata/android/zh-CN/changelogs/42012.txt new file mode 100644 index 0000000000000000000000000000000000000000..9c15d9ceb2c52104677898f079ad24d62a97e7d5 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42012.txt @@ -0,0 +1 @@ +* 修复不信任系统证书颁发机构的用户的 HTTP 上传/下载问题 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42013.txt b/fastlane/metadata/android/zh-CN/changelogs/42013.txt new file mode 100644 index 0000000000000000000000000000000000000000..801cf42fba4fe127a3cfd897b53db22c07d7da22 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42013.txt @@ -0,0 +1 @@ +* 修复了 Android 7.1 上的“无连接”问题 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42014.txt b/fastlane/metadata/android/zh-CN/changelogs/42014.txt new file mode 100644 index 0000000000000000000000000000000000000000..2784135e000c73be9d09ad3714ff5ed5809ca514 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42014.txt @@ -0,0 +1,2 @@ +* 始终验证域名。没有用户覆盖 +* 支持花名册预验证 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42015.txt b/fastlane/metadata/android/zh-CN/changelogs/42015.txt new file mode 100644 index 0000000000000000000000000000000000000000..b9ce380d61c29bca8e0605869c5ecd74b7c99347 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42015.txt @@ -0,0 +1 @@ +* 在音频和视频方面略有改进 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42018.txt b/fastlane/metadata/android/zh-CN/changelogs/42018.txt new file mode 100644 index 0000000000000000000000000000000000000000..974bd029213a00e33e37488342d706a777cfd379 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42018.txt @@ -0,0 +1,3 @@ +* 当远程视频与屏幕宽高比不匹配时显示黑条 +* 提高搜索性能 +* 添加防止截图的设置 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42022.txt b/fastlane/metadata/android/zh-CN/changelogs/42022.txt new file mode 100644 index 0000000000000000000000000000000000000000..08c03bff717f577ac0012cbd222bdaf585499d95 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42022.txt @@ -0,0 +1,2 @@ +* 修复某些视频无法压缩的问题 +* 修复打开通知时罕见的崩溃问题 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42023.txt b/fastlane/metadata/android/zh-CN/changelogs/42023.txt new file mode 100644 index 0000000000000000000000000000000000000000..e24d2373fe9e1dd03ce2ee880d36e039eec6606d --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42023.txt @@ -0,0 +1,2 @@ +* 修复渲染某些引用时的崩溃问题 +* 修复欢迎屏幕崩溃问题 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42037.txt b/fastlane/metadata/android/zh-CN/changelogs/42037.txt new file mode 100644 index 0000000000000000000000000000000000000000..bde3749452d5ba4ebbb8aa9011c67e35c837a54c --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42037.txt @@ -0,0 +1,11 @@ +版本2.10.9 +* 进行音视频通话时请求蓝牙权限(如果您不使用蓝牙耳机可以拒绝) +* 修复呼叫 Movim 时的错误 +* 修复群组聊天的显示错误头像的问题 +* 始终要求选择退出电池优化 +* 在“x 个已连接账号”通知上设置仅本地标志 +* 修复与 Google 地图分享位置插件的交互 +* 移除有关服务器费用的脚注 +* 将文件存储在适合 Android 11 的位置 +* 网络切换后尝试重新连接通话 +* 在来电屏幕中显示来电者JID和帐户JID diff --git a/fastlane/metadata/android/zh-CN/changelogs/42038.txt b/fastlane/metadata/android/zh-CN/changelogs/42038.txt new file mode 100644 index 0000000000000000000000000000000000000000..99c2ed862699739cb5ffe26000e07a37865e5c95 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42038.txt @@ -0,0 +1,2 @@ +* 修正了一些小错误 +* 恢复通过 JMP 和其他服务呼叫的能力(Playstore 版本) diff --git a/fastlane/metadata/android/zh-CN/changelogs/42041.txt b/fastlane/metadata/android/zh-CN/changelogs/42041.txt new file mode 100644 index 0000000000000000000000000000000000000000..b809c9854098bddcdbea610450d2f0c7187da1bf --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42041.txt @@ -0,0 +1,5 @@ +* 实施可扩展 SASL Profile、Bind 2.0 和 Fast,以加快重新连接速度 +* 实现频道绑定 +* 增加从音频通话切换到视频通话的功能 +* 增加删除自己头像的功能 +* 增加未接来电通知功能 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42042.txt b/fastlane/metadata/android/zh-CN/changelogs/42042.txt new file mode 100644 index 0000000000000000000000000000000000000000..2c5975c386b6600bd277299f01993521a1b4541e --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42042.txt @@ -0,0 +1,2 @@ +* 修复仅支持 sm:2 的服务器上的重发循环 +* 仅当对方支持视频时才显示“切换到视频” diff --git a/fastlane/metadata/android/zh-CN/changelogs/42043.txt b/fastlane/metadata/android/zh-CN/changelogs/42043.txt new file mode 100644 index 0000000000000000000000000000000000000000..d830a7f321fdcbffd3fcd8b76dc248a79237f168 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42043.txt @@ -0,0 +1 @@ +* 修复了 P2P 文件传输中的缺陷 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42044.txt b/fastlane/metadata/android/zh-CN/changelogs/42044.txt new file mode 100644 index 0000000000000000000000000000000000000000..f0e54c4588ecd1e5a6e8478f93092277e6113181 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42044.txt @@ -0,0 +1,3 @@ +* 修复使用 SASL2 时重新发送消息的问题 +* 修复部分设备之间的黑屏问题 +* 修复空密码崩溃问题 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42046.txt b/fastlane/metadata/android/zh-CN/changelogs/42046.txt new file mode 100644 index 0000000000000000000000000000000000000000..a4a5e4c1ae6e3f2f145dfb4e81f66cfa8010801a --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42046.txt @@ -0,0 +1 @@ +* 集成 UnifiedPush 分发程序,以便将消息推送到其他支持 UnifiedPush 的应用程序,例如 Tusky 和 Fedilab diff --git a/fastlane/metadata/android/zh-CN/changelogs/42047.txt b/fastlane/metadata/android/zh-CN/changelogs/42047.txt new file mode 100644 index 0000000000000000000000000000000000000000..c67da184321b033383d0ef061e061f08383956d7 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42047.txt @@ -0,0 +1 @@ +* 修复 UnifiedPush 分发程序中的崩溃问题 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42050.txt b/fastlane/metadata/android/zh-CN/changelogs/42050.txt new file mode 100644 index 0000000000000000000000000000000000000000..28c946e811c9d58ce6904270bdd05ae71fb93cd4 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42050.txt @@ -0,0 +1 @@ +* 增加个人资料图片的圆角半径 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42059.txt b/fastlane/metadata/android/zh-CN/changelogs/42059.txt new file mode 100644 index 0000000000000000000000000000000000000000..faeaa2ff179af703941f5a4fe9d8b1471d3bc86c --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42059.txt @@ -0,0 +1,2 @@ +* 将 Target SDK 再次提升至 33 +* 修复支持 SASL2 且不支持内联流管理的服务器上的问题 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42060.txt b/fastlane/metadata/android/zh-CN/changelogs/42060.txt new file mode 100644 index 0000000000000000000000000000000000000000..72a78b5cb85b801923dbb1f22facdf970745553c --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42060.txt @@ -0,0 +1 @@ +* 修复“q”被错误识别为西里尔字母的问题 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42061.txt b/fastlane/metadata/android/zh-CN/changelogs/42061.txt new file mode 100644 index 0000000000000000000000000000000000000000..4cf6ebea1f325326c559a7bdbc2ea60406c440d5 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42061.txt @@ -0,0 +1 @@ +* 从 Google Play 版本中移除频道发现功能 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42062.txt b/fastlane/metadata/android/zh-CN/changelogs/42062.txt new file mode 100644 index 0000000000000000000000000000000000000000..e15f64892e722da4b6ef3bdfceebae493c2aa389 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42062.txt @@ -0,0 +1 @@ +* 禁止从文件管理器打开备份文件(.ceb) diff --git a/fastlane/metadata/android/zh-CN/changelogs/42065.txt b/fastlane/metadata/android/zh-CN/changelogs/42065.txt new file mode 100644 index 0000000000000000000000000000000000000000..419c47a534f40867fbd169c834ac4d2ae6362083 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42065.txt @@ -0,0 +1 @@ +* 引入新的备份文件格式 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42068.txt b/fastlane/metadata/android/zh-CN/changelogs/42068.txt new file mode 100644 index 0000000000000000000000000000000000000000..3c7020ba0da62c54fbe36b56f2dad65b0ed6e274 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42068.txt @@ -0,0 +1,2 @@ +* 支持每个对话通知设置 +* 在 Android 10 上使用 opus 发送语音消息 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42072.txt b/fastlane/metadata/android/zh-CN/changelogs/42072.txt new file mode 100644 index 0000000000000000000000000000000000000000..481587e6876c107e1f774b568e47959422760cb5 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42072.txt @@ -0,0 +1,3 @@ +* 将 libwebrtc 依赖项提升到 M117 并提升 libvpx +* 回到 AAC 语音消息 +* 支持每个应用程序语言设置 diff --git a/fastlane/metadata/android/zh-CN/changelogs/42074.txt b/fastlane/metadata/android/zh-CN/changelogs/42074.txt new file mode 100644 index 0000000000000000000000000000000000000000..12acff938500f1a2a9abbc87b13ea90de996f50f --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/42074.txt @@ -0,0 +1,3 @@ +* 支持私人 DNS(DNS over TLS) +* 支持主题启动器图标 +* 修复在 Android 11+ 分享文件时罕见的权限问题 diff --git a/fastlane/metadata/android/zh-CN/changelogs/4207704.txt b/fastlane/metadata/android/zh-CN/changelogs/4207704.txt new file mode 100644 index 0000000000000000000000000000000000000000..d07b29e61ee671b6673661da9e8c0f441038bd46 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4207704.txt @@ -0,0 +1,3 @@ +* 支持私人 DNS (DNS over TLS) +* 支持主题启动器图标 +* 修复在 Android 11+ 上分享文件时罕见的权限问题 diff --git a/gradle.properties b/gradle.properties index 37c1168b7f757e77ee0505e9a83a646576450d9f..af22f695489fb9afbf5592acca54cbc7334c0d1e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,6 @@ android.useAndroidX=true android.enableJetifier=true +android.nonTransitiveRClass=true +android.nonFinalResIds=false org.gradle.jvmargs=-Xmx4096m org.gradle.daemon=false diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 528419fe6467c1878c33e7a29da3df070ce0f20a..5263b93d56e5e29b07699b26597c9ea0b68aa606 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=97a52d145762adc241bad7fd18289bf7f6801e08ece6badf80402fe2b9f250b1 +distributionSha256Sum=5022b0b25fe182b0e50867e77f484501dba44feeea88f5c1f13b6b4660463640 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip diff --git a/proguard-rules.pro b/proguard-rules.pro index 7e4d7d31d6553d006e1d1008e5c4fd90a1004bb1..03044d5253f09feb2c41f732239f4e12ad0cafcd 100644 --- a/proguard-rules.pro +++ b/proguard-rules.pro @@ -64,7 +64,21 @@ -dontwarn retrofit2.KotlinExtensions -dontwarn retrofit2.KotlinExtensions$* + # With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy # and replaces all potential values with null. Explicitly keeping the interfaces prevents this. -if interface * { @retrofit2.http.* ; } -keep,allowobfuscation interface <1> + +# Keep inherited services. +-if interface * { @retrofit2.http.* ; } +-keep,allowobfuscation interface * extends <1> + +# With R8 full mode generic signatures are stripped for classes that are not +# kept. Suspend functions are wrapped in continuations where the type argument +# is used. +-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation + +# R8 full mode strips generic signatures from return types if not kept. +-if interface * { @retrofit2.http.* public *** *(...); } +-keep,allowoptimization,allowshrinking,allowobfuscation class <3> diff --git a/src/cheogram/res/values/themes.xml b/src/cheogram/res/values/themes.xml index 35fb3f80e33410f642bd21d7a83d7226bb6a7064..df28f80ee084ff99cb0f79ca4cab45f12839aaf4 100644 --- a/src/cheogram/res/values/themes.xml +++ b/src/cheogram/res/values/themes.xml @@ -105,6 +105,7 @@ @drawable/ic_person_add_white_24dp @drawable/ic_cancel_black_24dp @drawable/ic_content_copy_black_24dp + @drawable/ic_qr_code_black_24dp @drawable/ic_delete_white_24dp @drawable/ic_file_download_white_24dp @drawable/ic_edit_white_24dp @@ -264,6 +265,7 @@ @drawable/ic_person_add_white_24dp @drawable/ic_cancel_white_24dp @drawable/ic_content_copy_white_24dp + @drawable/ic_qr_code_white_24dp @drawable/ic_delete_white_24dp @drawable/ic_file_download_white_24dp @drawable/ic_edit_white_24dp diff --git a/fastlane/metadata/android/da-DK/short_description.txt b/src/conversations/fastlane/metadata/android/da-DK/short_description.txt similarity index 100% rename from fastlane/metadata/android/da-DK/short_description.txt rename to src/conversations/fastlane/metadata/android/da-DK/short_description.txt diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/src/conversations/fastlane/metadata/android/de-DE/full_description.txt similarity index 100% rename from fastlane/metadata/android/de-DE/full_description.txt rename to src/conversations/fastlane/metadata/android/de-DE/full_description.txt diff --git a/fastlane/metadata/android/de-DE/short_description.txt b/src/conversations/fastlane/metadata/android/de-DE/short_description.txt similarity index 100% rename from fastlane/metadata/android/de-DE/short_description.txt rename to src/conversations/fastlane/metadata/android/de-DE/short_description.txt diff --git a/fastlane/metadata/android/en-US/full_description.txt b/src/conversations/fastlane/metadata/android/en-US/full_description.txt similarity index 100% rename from fastlane/metadata/android/en-US/full_description.txt rename to src/conversations/fastlane/metadata/android/en-US/full_description.txt diff --git a/src/conversations/fastlane/metadata/android/en-US/images/icon.png b/src/conversations/fastlane/metadata/android/en-US/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..046dbfb3a09088f55918613caf3ec002ab521551 Binary files /dev/null and b/src/conversations/fastlane/metadata/android/en-US/images/icon.png differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/src/conversations/fastlane/metadata/android/en-US/short_description.txt similarity index 100% rename from fastlane/metadata/android/en-US/short_description.txt rename to src/conversations/fastlane/metadata/android/en-US/short_description.txt diff --git a/src/conversations/fastlane/metadata/android/es-ES/full_description.txt b/src/conversations/fastlane/metadata/android/es-ES/full_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..8535495474703dfcc474ead02580c91a8ead8b85 --- /dev/null +++ b/src/conversations/fastlane/metadata/android/es-ES/full_description.txt @@ -0,0 +1,39 @@ +Fácil de usar, fiable y con poca batería. Con soporte integrado para imágenes, chats de grupo y cifrado e2e. + +Principios de diseño: + +* Ser lo más bonito y fácil de usar posible sin sacrificar la seguridad ni la privacidad. +* Basarse en protocolos existentes y bien establecidos. +* No requerir una cuenta de Google o, específicamente, Google Cloud Messaging (GCM). +* Requerir el menor número de permisos posible + +Características: + +* Cifrado de extremo a extremo con OMEMO o OpenPGP. +* Envío y recepción de imágenes +* Llamadas de audio y vídeo cifradas (DTLS-SRTP) +* Interfaz de usuario intuitiva que sigue las directrices de diseño de Android +* Imágenes / Avatares para tus contactos +* Sincronización con el cliente de escritorio +* Conferencias (con soporte para marcadores) +* Integración de la libreta de direcciones +* Múltiples cuentas / bandeja de entrada unificada +* Muy bajo impacto en la duración de la batería + +Conversations hace que sea muy fácil crear una cuenta en el servidor gratuito conversations.im. Sin embargo, Conversations también funciona con cualquier otro servidor XMPP. Muchos servidores XMPP están gestionados por voluntarios y son gratuitos. + +Características de XMPP: + +Conversations funciona con todos los servidores XMPP existentes. Sin embargo, XMPP es un protocolo extensible. Estas extensiones también están estandarizadas en los llamados XEP. Conversations soporta un par de ellas para mejorar la experiencia general del usuario. Existe la posibilidad de que su actual servidor XMPP no soporte estas extensiones. Por lo tanto, para sacar el máximo provecho de Conversaciones deberías considerar o bien cambiar a un servidor XMPP que lo haga o - mejor aún - ejecutar tu propio servidor XMPP para ti y tus amigos. + +Estos XEPs son (por el momento): + +* XEP-0065: SOCKS5 Bytestreams (o mod_proxy65). Se utilizará para transferir archivos si ambas partes están detrás de un cortafuegos (NAT). +* XEP-0163: Protocolo de Evento Personal para avatares +* XEP-0191: El comando de bloqueo te permite hacer una lista negra de spammers o bloquear contactos sin eliminarlos de tu lista. +* XEP-0198: Stream Management permite a XMPP sobrevivir a pequeños cortes de red y cambios de la conexión TCP subyacente. +* XEP-0280: Message Carbons que sincroniza automáticamente los mensajes que envías a tu cliente de escritorio y por lo tanto te permite cambiar sin problemas de tu cliente móvil a tu cliente de escritorio y viceversa en una sola conversación. +* XEP-0237: Versionado de listas, principalmente para ahorrar ancho de banda en conexiones móviles deficientes. +* XEP-0313: Gestión de Archivo de Mensajes sincroniza el historial de mensajes con el servidor. Ponerse al día con los mensajes que fueron enviados mientras Conversaciones estaba fuera de línea. +* XEP-0352: Indicación del Estado del Cliente permite al servidor saber si Conversaciones está o no en segundo plano. Permite al servidor ahorrar ancho de banda reteniendo paquetes sin importancia. +* XEP-0363: Carga de Archivos HTTP permite compartir archivos en conferencias y con contactos sin conexión. Requiere un componente adicional en su servidor. diff --git a/src/conversations/fastlane/metadata/android/es-ES/short_description.txt b/src/conversations/fastlane/metadata/android/es-ES/short_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..7ed04767246cd7fab459ea0ebc43836a01ddd32f --- /dev/null +++ b/src/conversations/fastlane/metadata/android/es-ES/short_description.txt @@ -0,0 +1 @@ +Mensajería instantánea XMPP cifrada y fácil de usar para tu dispositivo móvil diff --git a/fastlane/metadata/android/gl-ES/full_description.txt b/src/conversations/fastlane/metadata/android/gl-ES/full_description.txt similarity index 100% rename from fastlane/metadata/android/gl-ES/full_description.txt rename to src/conversations/fastlane/metadata/android/gl-ES/full_description.txt diff --git a/fastlane/metadata/android/gl-ES/short_description.txt b/src/conversations/fastlane/metadata/android/gl-ES/short_description.txt similarity index 100% rename from fastlane/metadata/android/gl-ES/short_description.txt rename to src/conversations/fastlane/metadata/android/gl-ES/short_description.txt diff --git a/fastlane/metadata/android/it-IT/full_description.txt b/src/conversations/fastlane/metadata/android/it-IT/full_description.txt similarity index 100% rename from fastlane/metadata/android/it-IT/full_description.txt rename to src/conversations/fastlane/metadata/android/it-IT/full_description.txt diff --git a/fastlane/metadata/android/it-IT/short_description.txt b/src/conversations/fastlane/metadata/android/it-IT/short_description.txt similarity index 100% rename from fastlane/metadata/android/it-IT/short_description.txt rename to src/conversations/fastlane/metadata/android/it-IT/short_description.txt diff --git a/fastlane/metadata/android/pl-PL/full_description.txt b/src/conversations/fastlane/metadata/android/pl-PL/full_description.txt similarity index 100% rename from fastlane/metadata/android/pl-PL/full_description.txt rename to src/conversations/fastlane/metadata/android/pl-PL/full_description.txt diff --git a/fastlane/metadata/android/pl-PL/short_description.txt b/src/conversations/fastlane/metadata/android/pl-PL/short_description.txt similarity index 100% rename from fastlane/metadata/android/pl-PL/short_description.txt rename to src/conversations/fastlane/metadata/android/pl-PL/short_description.txt diff --git a/src/conversations/fastlane/metadata/android/ro/full_description.txt b/src/conversations/fastlane/metadata/android/ro/full_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..2d4ae419dc8ea47e99de4ce83565b29e7d393a14 --- /dev/null +++ b/src/conversations/fastlane/metadata/android/ro/full_description.txt @@ -0,0 +1,38 @@ +Ușor de utilizat, fiabil, prietenos cu bateria. Cu suport încorporat pentru imagini, discuții de grup și criptare E2E. + +Principii de proiectare: + +* Să fie cât mai frumos și mai ușor de utilizat posibil, fără a sacrifica securitatea sau confidențialitatea. +* Să se bazeze pe protocoale existente și bine stabilite +* Nu necesită un cont Google sau în mod specific Google Cloud Messaging (GCM). +* Să necesite cât mai puține permisiuni posibil + +Caracteristici: + +* Criptare de la un capăt-la-altul (E2E) cu OMEMO sau OpenPGP +* Trimiterea și primirea de imagini +* Apeluri audio și video criptate (DTLS-SRTP) +* Interfață intuitivă care respectă liniile directoare Android Design +* Imagini / Avataruri pentru contactele dvs. +* Se sincronizează cu clientul desktop +* Conferințe (cu suport pentru marcaje) +* Integrare cu lista de contacte +* Conturi multiple / căsuță de mesaje unificată +* Impact foarte redus asupra duratei de viață a bateriei + +Conversations face foarte ușoară crearea unui cont pe serverul gratuit conversations.im. Cu toate acestea, Conversations va funcționa și cu orice alt server XMPP. O mulțime de servere XMPP sunt administrate de voluntari și sunt gratuite. + +Caracteristici XMPP: + +Conversations funcționează cu orice server XMPP existent. Cu toate acestea, XMPP este un protocol extensibil. Aceste extensii sunt, de asemenea, standardizate în așa-numitele XEP-uri. Conversations suportă câteva dintre acestea pentru a îmbunătăți experiența generală a utilizatorului. Există o șansă ca serverul XMPP actual să nu suporte aceste extensii. Prin urmare, pentru a profita la maximum de Conversations, ar trebui să luați în considerare fie trecerea la un server XMPP care să suporte aceste extensii, fie - și mai bine - să rulați propriul server XMPP pentru dumneavoastră și prietenii dumneavoastră. + +Aceste XEP-uri sunt - deocamdată: +* XEP-0065: SOCKS5 Bytestreams (sau mod_proxy65). Va fi utilizat pentru a transfera fișiere dacă ambele părți se află în spatele unui firewall (NAT). +* XEP-0163: Protocol de evenimente personale pentru avatare. +* XEP-0191: Comanda de blocare vă permite să puneți pe lista neagră spamerii sau să blocați contactele fără a le elimina din listă. +* XEP-0198: Stream Management permite XMPP să supraviețuiască unor mici întreruperi de rețea și schimbărilor conexiunii TCP de bază. +* XEP-0280: Message Carbons, care sincronizează automat mesajele pe care le trimiteți în clientul desktop și vă permite astfel să treceți fără probleme de la clientul mobil la clientul desktop și înapoi în cadrul unei singure conversații. +* XEP-0237: Roster Versioning în principal pentru a economisi lățimea de bandă în cazul conexiunilor mobile slabe +* XEP-0313: Gestionarea arhivei de mesaje sincronizează istoricul mesajelor cu serverul. Recuperați mesajele care au fost trimise în timp ce Conversations era deconectat. +* XEP-0352: Client State Indication permite serverului să știe dacă Conversations este sau nu în fundal. Permite serverului să economisească lățimea de bandă prin reținerea pachetelor neimportante. +* XEP-0363: HTTP File Upload vă permite să partajați fișiere în cadrul conferințelor și cu contactele deconectate. Necesită o componentă suplimentară pe serverul dumneavoastră. diff --git a/fastlane/metadata/android/ro/short_description.txt b/src/conversations/fastlane/metadata/android/ro/short_description.txt similarity index 100% rename from fastlane/metadata/android/ro/short_description.txt rename to src/conversations/fastlane/metadata/android/ro/short_description.txt diff --git a/fastlane/metadata/android/sq/full_description.txt b/src/conversations/fastlane/metadata/android/sq/full_description.txt similarity index 100% rename from fastlane/metadata/android/sq/full_description.txt rename to src/conversations/fastlane/metadata/android/sq/full_description.txt diff --git a/fastlane/metadata/android/sq/short_description.txt b/src/conversations/fastlane/metadata/android/sq/short_description.txt similarity index 100% rename from fastlane/metadata/android/sq/short_description.txt rename to src/conversations/fastlane/metadata/android/sq/short_description.txt diff --git a/fastlane/metadata/android/sv-SE/full_description.txt b/src/conversations/fastlane/metadata/android/sv-SE/full_description.txt similarity index 100% rename from fastlane/metadata/android/sv-SE/full_description.txt rename to src/conversations/fastlane/metadata/android/sv-SE/full_description.txt diff --git a/fastlane/metadata/android/sv-SE/short_description.txt b/src/conversations/fastlane/metadata/android/sv-SE/short_description.txt similarity index 100% rename from fastlane/metadata/android/sv-SE/short_description.txt rename to src/conversations/fastlane/metadata/android/sv-SE/short_description.txt diff --git a/src/conversations/fastlane/metadata/android/uk/full_description.txt b/src/conversations/fastlane/metadata/android/uk/full_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..39971ed5571be157835f2e2b959d96f74a5c7f55 --- /dev/null +++ b/src/conversations/fastlane/metadata/android/uk/full_description.txt @@ -0,0 +1,39 @@ +Надійний, простий у використанні, ощадливо витрачає заряд акумулятора. Має вбудовану підтримку зображень, групових чатів і наскрізного шифрування. + +Принципи проєктування: + +* Бути максимально красивим та простим у використанні, не жертвуючи безпекою чи конфіденційністю +* Покладатися на існуючі, добре встановлені протоколи +* Не вимагати облікового запису Google, зокрема Google Cloud Messaging (GCM) +* Вимагати якомога менше дозволів + +Функції: + +* Наскрізне шифрування (від відправника до одержувача) за допомогою OMEMO або OpenPGP +* Надсилання та отримання зображень +* Зашифровані голосові та відеодзвінки (DTLS-SRTP) +* Інтуїтивно зрозумілий інтерфейс користувача, який відповідає вказівкам Android Design +* Зображення / Аватари для Ваших контактів +* Синхронізація з настільним клієнтом +* Конференції (з підтримкою закладок) +* Інтеграція адресної книги +* Кілька облікових записів / єдина папка вхідних +* Дуже низький вплив на термін служби акумулятора + +Conversations дозволяє легко створити обліковий запис на безкоштовному сервері conversations.im. Однак Conversations працюватиме також із будь-яким іншим XMPP-сервером. Чимало серверів XMPP обслуговуються волонтерами і є безкоштовними. + +Функції XMPP: + +Conversations працює з будь-яким сервером XMPP. Проте XMPP — розширюваний протокол. Розширення також стандартизовані в так званих XEP. Conversations підтримує кілька з них, щоб покращити загальний досвід користування. Може виявитися, що Ваш поточний сервер XMPP не підтримує цих розширень. Тому, щоб отримати максимум від Conversations, розгляньте перехід на XMPP-сервер з підтримкою цих розширень або — ще краще — запускайте власний сервер XMPP для себе і своїх друзів. + +На даний час підтримуються такі XEP: + +* XEP-0065: SOCKS5 Bytestreams (або mod_proxy65). Використовується для передачі файлів, якщо обидві сторони знаходяться за брандмауером (NAT). +* XEP-0163: персональний протокол подій для аватарів +* XEP-0191: команда блокування дозволяє Вам заносити спамерів у чорний список або блокувати контакти, не видаляючи їх зі свого списку. +* XEP-0198: керування потоками дозволяє XMPP витримувати невеликі перебої в мережі та зміни основного TCP-з'єднання. +* XEP-0280: Message Carbons, який автоматично синхронізує повідомлення, які Ви надсилаєте, на настільний клієнт і, таким чином, дозволяє плавно переключатися з мобільного клієнта на клієнт для настільного ПК і назад протягом однієї розмови. +* XEP-0237: версія списку в основному для економії пропускної здатності при поганих мобільних з'єднаннях +* XEP-0313: керування архівом повідомлень синхронізує історію повідомлень із сервером. Дізнавайтеся про повідомлення, надіслані, поки Conversations був офлайн. +* XEP-0352: індикація стану клієнта повідомляє серверу, чи працює Conversations у фоновому режимі. Дозволяє серверу заощаджувати пропускну здатність, утримуючи неважливі пакети. +* XEP-0363: завантаження файлів HTTP дозволяє обмінюватися файлами в конференціях і з офлайн-контактами. Потрібен додатковий компонент на Вашому сервері. diff --git a/src/conversations/fastlane/metadata/android/uk/short_description.txt b/src/conversations/fastlane/metadata/android/uk/short_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..300b89277d3e9bfcc282de098e6ca16d13b0e477 --- /dev/null +++ b/src/conversations/fastlane/metadata/android/uk/short_description.txt @@ -0,0 +1 @@ +Простий у використанні XMPP-клієнт з підтримкою шифрування для Вашого телефона diff --git a/src/conversations/fastlane/metadata/android/zh-CN/full_description.txt b/src/conversations/fastlane/metadata/android/zh-CN/full_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..f19e2cd87ad63a69cb93ac385c655b960d6c59a7 --- /dev/null +++ b/src/conversations/fastlane/metadata/android/zh-CN/full_description.txt @@ -0,0 +1,39 @@ +易于使用、性能可靠、电池友好。内置支持图片、群组聊天和 e2e 加密功能。 + +设计原则: + +* 在不牺牲安全性和隐私性的前提下,尽可能美观易用 +* 依赖现有的、完善的协议 +* 不需要 Google 账号或特定的 Google 云通讯服务 (GCM) +* 要求尽可能少的权限 + +特点: + +* 使用 OMEMOOpenPGP 进行端对端加密 +* 发送和接收图片 +* 加密音视频通话 (DTLS-SRTP) +* 直观的用户界面,遵循 Android 设计准则 +* 为您的联系人添加图片/头像 +* 与桌面客户端同步 +* 群聊(支持书签功能) +* 通讯录集成 +* 多账号/统一收件箱 +* 对电池寿命的影响非常小 + +Conversations 使在免费的 conversations.im 服务器上创建账号变得非常简单。不过,Conversations 也适用于任何其他 XMPP 服务器。许多 XMPP 服务器都是由志愿者免费运行的。 + +XMPP 功能: + +Conversations 适用于所有 XMPP 服务器。然而,XMPP 是一种可扩展的协议。这些扩展在所谓的 XEP 中也是标准化的。Conversations 支持其中的一些扩展,以使整体用户体验更好。有一种可能是您当前的 XMPP 服务器不支持这些扩展。因此,要想充分使用 Conversations 的功能,您应该考虑切换到支持这些扩展的 XMPP 服务器,甚至有更好的方式,或者为您和您的朋友运行自己的 XMPP 服务器。 + +到目前为止,这些 XEP 是: + +* XEP-0065:SOCKS5 字节流 (or mod_proxy65)。如果双方都在防火墙 (NAT) 后面,将用于传输文件。 +* XEP-0163:个人事件协议对于头像 +* XEP-0191:屏蔽指令可让您将垃圾邮件发送者列入黑名单或屏蔽的联系人中,而不会将其从花名册中删除。 +* XEP-0198:流管理允许 XMPP 在小规模网络中断和底层 TCP 连接发生变化时继续运行。 +* XEP-0280:消息抄送,可自动将您发送的消息同步到桌面客户端,因此您可以在一次对话中从手机客户端无缝切换到桌面客户端,然后再返回。 +* XEP-0237:花名册版本控制主要是为了在移动连接不佳的情况下节省带宽 +* XEP-0313:消息存档管理与服务器同步消息历史记录。补发 Conversations 离线时发送的消息。 +* XEP-0352:客户端状态指示让服务器知道 Conversations 是否在后台。允许服务器保留不重要的数据包,从而节省带宽。 +* XEP-0363:通过 HTTP 文件上传功能,您可以在群聊中与离线联系人分享文件。需要在服务器上安装额外组件。 diff --git a/src/conversations/fastlane/metadata/android/zh-CN/short_description.txt b/src/conversations/fastlane/metadata/android/zh-CN/short_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..28659cd560ea87036b941574e960744c0bda7ab3 --- /dev/null +++ b/src/conversations/fastlane/metadata/android/zh-CN/short_description.txt @@ -0,0 +1 @@ +加密、易于使用的 XMPP 即时信使,适用于您的移动设备 diff --git a/fastlane/metadata/android/zh-TW/full_description.txt b/src/conversations/fastlane/metadata/android/zh-TW/full_description.txt similarity index 100% rename from fastlane/metadata/android/zh-TW/full_description.txt rename to src/conversations/fastlane/metadata/android/zh-TW/full_description.txt diff --git a/fastlane/metadata/android/zh-TW/short_description.txt b/src/conversations/fastlane/metadata/android/zh-TW/short_description.txt similarity index 100% rename from fastlane/metadata/android/zh-TW/short_description.txt rename to src/conversations/fastlane/metadata/android/zh-TW/short_description.txt diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java index c118d7375365febc8206b36bf4f2ec1ceb8fa98c..3770709415a4cff7b806b4e6ea1a3629332496ae 100644 --- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java @@ -6,6 +6,7 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; @@ -22,6 +23,21 @@ import androidx.core.app.NotificationManagerCompat; import com.google.common.base.Charsets; import com.google.common.base.Stopwatch; import com.google.common.io.CountingInputStream; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.persistance.DatabaseBackend; +import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.ui.ManageAccountActivity; +import eu.siacs.conversations.utils.BackupFileHeader; +import eu.siacs.conversations.utils.SerialSingleThreadExecutor; +import eu.siacs.conversations.xmpp.Jid; import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.io.CipherInputStream; @@ -40,50 +56,47 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import java.util.zip.ZipException; import javax.crypto.BadPaddingException; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.R; -import eu.siacs.conversations.persistance.DatabaseBackend; -import eu.siacs.conversations.persistance.FileBackend; -import eu.siacs.conversations.ui.ManageAccountActivity; -import eu.siacs.conversations.utils.BackupFileHeader; -import eu.siacs.conversations.utils.SerialSingleThreadExecutor; -import eu.siacs.conversations.xmpp.Jid; - public class ImportBackupService extends Service { private static final int NOTIFICATION_ID = 21; private static final AtomicBoolean running = new AtomicBoolean(false); private final ImportBackupServiceBinder binder = new ImportBackupServiceBinder(); - private final SerialSingleThreadExecutor executor = new SerialSingleThreadExecutor(getClass().getSimpleName()); - private final Set mOnBackupProcessedListeners = Collections.newSetFromMap(new WeakHashMap<>()); + private final SerialSingleThreadExecutor executor = + new SerialSingleThreadExecutor(getClass().getSimpleName()); + private final Set mOnBackupProcessedListeners = + Collections.newSetFromMap(new WeakHashMap<>()); private DatabaseBackend mDatabaseBackend; private NotificationManager notificationManager; - private static int count(String input, char c) { - int count = 0; - for (char aChar : input.toCharArray()) { - if (aChar == c) { - ++count; - } - } - return count; - } + private static final Collection TABLE_ALLOW_LIST = + Arrays.asList( + Account.TABLENAME, + Conversation.TABLENAME, + Message.TABLENAME, + SQLiteAxolotlStore.PREKEY_TABLENAME, + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.IDENTITIES_TABLENAME); + private static final Pattern COLUMN_PATTERN = Pattern.compile("^[a-zA-Z_]+$"); @Override public void onCreate() { mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext()); - notificationManager = (android.app.NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager = + (android.app.NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); } @Override @@ -105,16 +118,17 @@ public class ImportBackupService extends Service { return START_NOT_STICKY; } if (running.compareAndSet(false, true)) { - executor.execute(() -> { - startForegroundService(); - final boolean success = importBackup(uri, password); - stopForeground(true); - running.set(false); - if (success) { - notifySuccess(); - } - stopSelf(); - }); + executor.execute( + () -> { + startForegroundService(); + final boolean success = importBackup(uri, password); + stopForeground(true); + running.set(false); + if (success) { + notifySuccess(); + } + stopSelf(); + }); } else { Log.d(Config.LOGTAG, "backup already running"); } @@ -126,42 +140,62 @@ public class ImportBackupService extends Service { } public void loadBackupFiles(final OnBackupFilesLoaded onBackupFilesLoaded) { - executor.execute(() -> { - final List accounts = mDatabaseBackend.getAccountJids(false); - final ArrayList backupFiles = new ArrayList<>(); - final Set apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name))); - final List directories = new ArrayList<>(); - for (final String app : apps) { - directories.add(FileBackend.getLegacyBackupDirectory(app)); - } - directories.add(FileBackend.getBackupDirectory(this)); - for (final File directory : directories) { - if (!directory.exists() || !directory.isDirectory()) { - Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath()); - continue; - } - final File[] files = directory.listFiles(); - if (files == null) { - continue; - } - for (final File file : files) { - if (file.isFile() && file.getName().endsWith(".ceb")) { - try { - final BackupFile backupFile = BackupFile.read(file); - if (accounts.contains(backupFile.getHeader().getJid())) { - Log.d(Config.LOGTAG, "skipping backup for " + backupFile.getHeader().getJid()); - } else { - backupFiles.add(backupFile); + executor.execute( + () -> { + final List accounts = mDatabaseBackend.getAccountJids(false); + final ArrayList backupFiles = new ArrayList<>(); + final Set apps = + new HashSet<>( + Arrays.asList( + "Conversations", + "Quicksy", + getString(R.string.app_name))); + final List directories = new ArrayList<>(); + for (final String app : apps) { + directories.add(FileBackend.getLegacyBackupDirectory(app)); + } + directories.add(FileBackend.getBackupDirectory(this)); + for (final File directory : directories) { + if (!directory.exists() || !directory.isDirectory()) { + Log.d( + Config.LOGTAG, + "directory not found: " + directory.getAbsolutePath()); + continue; + } + final File[] files = directory.listFiles(); + if (files == null) { + continue; + } + Log.d(Config.LOGTAG, "looking for backups in " + directory); + for (final File file : files) { + if (file.isFile() && file.getName().endsWith(".ceb")) { + try { + final BackupFile backupFile = BackupFile.read(file); + if (accounts.contains(backupFile.getHeader().getJid())) { + Log.d( + Config.LOGTAG, + "skipping backup for " + + backupFile.getHeader().getJid()); + } else { + backupFiles.add(backupFile); + } + } catch (final IOException + | IllegalArgumentException + | BackupFileHeader.OutdatedBackupFileVersion e) { + Log.d(Config.LOGTAG, "unable to read backup file ", e); + } } - } catch (IOException | IllegalArgumentException e) { - Log.d(Config.LOGTAG, "unable to read backup file ", e); } } - } - } - Collections.sort(backupFiles, (a, b) -> a.header.getJid().toString().compareTo(b.header.getJid().toString())); - onBackupFilesLoaded.onBackupFilesLoaded(backupFiles); - }); + Collections.sort( + backupFiles, + (a, b) -> + a.header + .getJid() + .toString() + .compareTo(b.header.getJid().toString())); + onBackupFilesLoaded.onBackupFilesLoaded(backupFiles); + }); } private void startForegroundService() { @@ -180,14 +214,16 @@ public class ImportBackupService extends Service { } final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); try { - notificationManager.notify(NOTIFICATION_ID, createImportBackupNotification(max, progress)); + notificationManager.notify( + NOTIFICATION_ID, createImportBackupNotification(max, progress)); } catch (final RuntimeException e) { Log.d(Config.LOGTAG, "unable to make notification", e); } } private Notification createImportBackupNotification(final int max, final int progress) { - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup"); + NotificationCompat.Builder mBuilder = + new NotificationCompat.Builder(getBaseContext(), "backup"); mBuilder.setContentTitle(getString(R.string.restoring_backup)) .setSmallIcon(R.drawable.ic_unarchive_white_24dp) .setProgress(max, progress, max == 1 && progress == 0); @@ -212,7 +248,9 @@ public class ImportBackupService extends Service { fileSize = 0; } else { returnCursor.moveToFirst(); - fileSize = returnCursor.getLong(returnCursor.getColumnIndex(OpenableColumns.SIZE)); + fileSize = + returnCursor.getLong( + returnCursor.getColumnIndexOrThrow(OpenableColumns.SIZE)); returnCursor.close(); } inputStream = getContentResolver().openInputStream(uri); @@ -242,40 +280,46 @@ public class ImportBackupService extends Service { final byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt()); final AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); - cipher.init(false, new AEADParameters(new KeyParameter(key), 128, backupFileHeader.getIv())); - final CipherInputStream cipherInputStream = new CipherInputStream(countingInputStream, cipher); + cipher.init( + false, + new AEADParameters(new KeyParameter(key), 128, backupFileHeader.getIv())); + final CipherInputStream cipherInputStream = + new CipherInputStream(countingInputStream, cipher); final GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream); - final BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, Charsets.UTF_8)); + final BufferedReader reader = + new BufferedReader(new InputStreamReader(gzipInputStream, Charsets.UTF_8)); + final JsonReader jsonReader = new JsonReader(reader); + if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) { + jsonReader.beginArray(); + } else { + throw new IllegalStateException("Backup file did not begin with array"); + } db.beginTransaction(); - String line; - StringBuilder multiLineQuery = null; - while ((line = reader.readLine()) != null) { - int count = count(line, '\''); - if (multiLineQuery != null) { - multiLineQuery.append('\n'); - multiLineQuery.append(line); - if (count % 2 == 1) { - db.execSQL(multiLineQuery.toString()); - multiLineQuery = null; - updateImportBackupNotification(fileSize, countingInputStream.getCount()); - } - } else { - if (count % 2 == 0) { - db.execSQL(line); - updateImportBackupNotification(fileSize, countingInputStream.getCount()); - } else { - multiLineQuery = new StringBuilder(line); - } + while (jsonReader.hasNext()) { + if (jsonReader.peek() == JsonToken.BEGIN_OBJECT) { + importRow(db, jsonReader, backupFileHeader.getJid(), password); + } else if (jsonReader.peek() == JsonToken.END_ARRAY) { + jsonReader.endArray(); + continue; } + updateImportBackupNotification(fileSize, countingInputStream.getCount()); } db.setTransactionSuccessful(); db.endTransaction(); final Jid jid = backupFileHeader.getJid(); - final Cursor countCursor = db.rawQuery("select count(messages.uuid) from messages join conversations on conversations.uuid=messages.conversationUuid join accounts on conversations.accountUuid=accounts.uuid where accounts.username=? and accounts.server=?", new String[]{jid.getEscapedLocal(), jid.getDomain().toEscapedString()}); + final Cursor countCursor = + db.rawQuery( + "select count(messages.uuid) from messages join conversations on conversations.uuid=messages.conversationUuid join accounts on conversations.accountUuid=accounts.uuid where accounts.username=? and accounts.server=?", + new String[] { + jid.getEscapedLocal(), jid.getDomain().toEscapedString() + }); countCursor.moveToFirst(); final int count = countCursor.getInt(0); - Log.d(Config.LOGTAG, String.format("restored %d messages in %s", count, stopwatch.stop().toString())); + Log.d( + Config.LOGTAG, + String.format( + "restored %d messages in %s", count, stopwatch.stop().toString())); countCursor.close(); stopBackgroundService(); synchronized (mOnBackupProcessedListeners) { @@ -286,7 +330,8 @@ public class ImportBackupService extends Service { return true; } catch (final Exception e) { final Throwable throwable = e.getCause(); - final boolean reasonWasCrypto = throwable instanceof BadPaddingException || e instanceof ZipException; + final boolean reasonWasCrypto = + throwable instanceof BadPaddingException || e instanceof ZipException; synchronized (mOnBackupProcessedListeners) { for (OnBackupProcessed l : mOnBackupProcessedListeners) { if (reasonWasCrypto) { @@ -301,14 +346,75 @@ public class ImportBackupService extends Service { } } + private void importRow( + final SQLiteDatabase db, + final JsonReader jsonReader, + final Jid account, + final String passphrase) + throws IOException { + jsonReader.beginObject(); + final String firstParameter = jsonReader.nextName(); + if (!firstParameter.equals("table")) { + throw new IllegalStateException("Expected key 'table'"); + } + final String table = jsonReader.nextString(); + if (!TABLE_ALLOW_LIST.contains(table)) { + throw new IOException(String.format("%s is not recognized for import", table)); + } + final ContentValues contentValues = new ContentValues(); + final String secondParameter = jsonReader.nextName(); + if (!secondParameter.equals("values")) { + throw new IllegalStateException("Expected key 'values'"); + } + jsonReader.beginObject(); + while (jsonReader.peek() != JsonToken.END_OBJECT) { + final String name = jsonReader.nextName(); + if (COLUMN_PATTERN.matcher(name).matches()) { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); + contentValues.putNull(name); + } else if (jsonReader.peek() == JsonToken.NUMBER) { + contentValues.put(name, jsonReader.nextLong()); + } else { + contentValues.put(name, jsonReader.nextString()); + } + } else { + throw new IOException(String.format("Unexpected column name %s", name)); + } + } + jsonReader.endObject(); + jsonReader.endObject(); + if (Account.TABLENAME.equals(table)) { + final Jid jid = + Jid.of( + contentValues.getAsString(Account.USERNAME), + contentValues.getAsString(Account.SERVER), + null); + final String password = contentValues.getAsString(Account.PASSWORD); + if (jid.equals(account) && passphrase.equals(password)) { + Log.d(Config.LOGTAG, "jid and password from backup header had matching row"); + } else { + throw new IOException("jid or password in table did not match backup"); + } + } + db.insert(table, null, contentValues); + } + private void notifySuccess() { - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup"); + NotificationCompat.Builder mBuilder = + new NotificationCompat.Builder(getBaseContext(), "backup"); mBuilder.setContentTitle(getString(R.string.notification_restored_backup_title)) .setContentText(getString(R.string.notification_restored_backup_subtitle)) .setAutoCancel(true) - .setContentIntent(PendingIntent.getActivity(this, 145, new Intent(this, ManageAccountActivity.class), s() - ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT - : PendingIntent.FLAG_UPDATE_CURRENT)) + .setContentIntent( + PendingIntent.getActivity( + this, + 145, + new Intent(this, ManageAccountActivity.class), + s() + ? PendingIntent.FLAG_IMMUTABLE + | PendingIntent.FLAG_UPDATE_CURRENT + : PendingIntent.FLAG_UPDATE_CURRENT)) .setSmallIcon(R.drawable.ic_unarchive_white_24dp); notificationManager.notify(NOTIFICATION_ID, mBuilder.build()); } @@ -391,4 +497,4 @@ public class ImportBackupService extends Service { return ImportBackupService.this; } } -} \ No newline at end of file +} diff --git a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java index 6e4815159cd4ab8283d8836ae0f5582ed12d0563..ed998677b31ab2e45c915b88decf48cdadb708dd 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java @@ -30,6 +30,7 @@ import eu.siacs.conversations.databinding.DialogEnterPasswordBinding; import eu.siacs.conversations.services.ImportBackupService; import eu.siacs.conversations.ui.adapter.BackupFileAdapter; import eu.siacs.conversations.ui.util.SettingsUtils; +import eu.siacs.conversations.utils.BackupFileHeader; import eu.siacs.conversations.utils.ThemeHelper; public class ImportBackupActivity extends ActionBarActivity implements ServiceConnection, ImportBackupService.OnBackupFilesLoaded, BackupFileAdapter.OnItemClickedListener, ImportBackupService.OnBackupProcessed { @@ -131,6 +132,8 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo try { final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri); showEnterPasswordDialog(backupFile, finishOnCancel); + } catch (final BackupFileHeader.OutdatedBackupFileVersion e) { + Snackbar.make(binding.coordinator, R.string.outdated_backup_file_format, Snackbar.LENGTH_LONG).show(); } catch (final IOException | IllegalArgumentException e) { Log.d(Config.LOGTAG, "unable to open backup file " + uri, e); Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG).show(); diff --git a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java index 888a1f13ec82b69f1bac7dc0b539fba076c19a8a..4446acefe77c7bb6d3a8f17e5d5f73d8d3720db7 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -359,6 +359,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda private void enableAccount(Account account) { account.setOption(Account.OPTION_DISABLED, false); + account.setOption(Account.OPTION_SOFT_DISABLED, false); final XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.resetEverything(); diff --git a/src/conversations/res/drawable/ic_launcher_foreground.xml b/src/conversations/res/drawable/ic_launcher_foreground.xml index 07a7ee7eb1677b7fd0ae928b27dc548aa3d88124..5851e5f2c06f45ed22d3b15f01d9469a05396c1e 100644 --- a/src/conversations/res/drawable/ic_launcher_foreground.xml +++ b/src/conversations/res/drawable/ic_launcher_foreground.xml @@ -1,24 +1,13 @@ - - - + android:width="108dp" + android:height="108dp" + android:viewportWidth="1146.7721" + android:viewportHeight="1146.7721"> + + diff --git a/src/conversations/res/drawable/ic_launcher_monochrome.xml b/src/conversations/res/drawable/ic_launcher_monochrome.xml new file mode 100644 index 0000000000000000000000000000000000000000..56895d60519f596e8d611ca7cefe6a2bfbf10f7f --- /dev/null +++ b/src/conversations/res/drawable/ic_launcher_monochrome.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/src/conversations/res/values-el/strings.xml b/src/conversations/res/values-el/strings.xml index bb7bcadf0a75709dae51239dd8fbabcec63489bb..c64e3d68e4b22698877f9f8e4ce3bf49d18997c3 100644 --- a/src/conversations/res/values-el/strings.xml +++ b/src/conversations/res/values-el/strings.xml @@ -12,5 +12,5 @@ Πατήστε το πλήκτρο διαμοιρασμού για να στείλετε στην επαφή σας μια πρόσκληση στο %1$s. Αν η επαφή σας βρίσκεται κοντά σας, μπορεί επίσης να σαρώσει τον κωδικό παρακάτω για να αποδεχτεί την πρόσκλησή σας. Μπείτε στο %1$s και συνομιλήστε μαζί μου: %2$s - Διαμοιρασμός πρόσκλησης με... + Διαμοιρασμός πρόσκλησης με… \ No newline at end of file diff --git a/src/conversations/res/values-sk/strings.xml b/src/conversations/res/values-sk/strings.xml index ed58bbefbf0b3fac2d1e37de1d7568bae9c0af6c..e280344c4b7f92ee7cdb8314e3bd389ad47e1a16 100644 --- a/src/conversations/res/values-sk/strings.xml +++ b/src/conversations/res/values-sk/strings.xml @@ -10,5 +10,5 @@ Ťuknite na tlačidlo zdieľať na odoslanie pozvánky do %1$s vášmu kontaktu. Ak je váš kontakt blízko, na prijatie vašej pozvánky si môže nasnímať kód nižšie. Pripojte sa k %1$sa rozprávajte sa so mnou: %2$s - Zdieľať pozvánku s... + Zdieľať pozvánku s… \ No newline at end of file diff --git a/src/conversations/res/values-tr-rTR/strings.xml b/src/conversations/res/values-tr-rTR/strings.xml index 6fb383cf7c7d8319f90e135471217f9d557d52ca..415bc89e09f418b583b849173f751d800313374e 100644 --- a/src/conversations/res/values-tr-rTR/strings.xml +++ b/src/conversations/res/values-tr-rTR/strings.xml @@ -11,6 +11,6 @@ Yanlış ayarlanmış düzenleme kodu Kişinize, %1$s grubuna davet etmek için Paylaş düğmesine basın. Kişiniz yakınınızda ise, aşağıdaki kodu tarayak daveti kabul edebilirler. - %1$s grubuna katıl ve benimle sohpet et: %2$s - Daveti şununla paylaş... + %1$s grubuna katıl ve benimle sohbet et: %2$s + Daveti şununla paylaş… \ No newline at end of file diff --git a/src/conversations/res/values-uk/strings.xml b/src/conversations/res/values-uk/strings.xml index 3b855ab5cc8f3f32564b2581c2f8a7bc610f48ac..f9e37cea7d80698b46af898ef847ec3f66aad29c 100644 --- a/src/conversations/res/values-uk/strings.xml +++ b/src/conversations/res/values-uk/strings.xml @@ -3,10 +3,18 @@ Виберіть постачальника послуг обміну повідомленнями XMPP Скористатися conversations.im Створити новий обліковий запис - Вже маєте обліковий запис XMPP? Можливо, користуєтеся іншою програмою XMPP або користувалися цією програмою раніше. Якщо ні, можете створити новий обліковий запис XMPP просто зараз.\nЗверніть увагу, що деякі постачальники електронної пошти у той же час надають облікові записи XMPP. - XMPP — це мережа обміну повідомленнями, незалежна від постачальників. Можете використовувати цю програму з будь-яким XMPP сервером, який оберете.\nПроте, для зручності, ми спростили створення облікового запису на conversations.im — у постачальника, який спеціально налаштований на роботу з цією програмою. - Вас запросили до %1$s. Ми проведемо вас крок за кроком, щоб створити обліковий запис.\nОбираючи %1$s в якості свого постачальника, ви зможете спілкуватися з користувачами інших постачальників, для цього повідомте їм свою повну адресу XMPP. - Вас запросили до %1$s. Для вас створено ім\'я користувача. Ми проведемо вас крок за кроком, щоб створити обліковий запис.\nВи зможете спілкуватися з користувачами інших постачальників, для цього повідомите їм свою повну адресу XMPP. + Уже маєте обліковий запис XMPP\? Можливо, користуєтеся іншою програмою XMPP або користувалися Conversations раніше. Якщо ні, можете створити новий обліковий запис XMPP просто зараз. +\nЗверніть увагу, що деякі постачальники електронної пошти у той же час надають облікові записи XMPP. + XMPP — це мережа обміну повідомленнями, незалежна від постачальників. Можете використовувати цю програму з будь-яким XMPP-сервером, який оберете. +\nПроте для зручності ми спростили створення облікового запису на conversations.im — у постачальника, спеціально налаштованого на роботу з Conversations. + Вас запросили до %1$s. Ми проведемо Вас крок за кроком, щоб створити обліковий запис. +\nОбравши %1$s в якості свого постачальника, Ви зможете спілкуватися з користувачами інших постачальників, для цього повідомте їм свою повну адресу XMPP. + Вас запросили до %1$s. Для Вас створено ім\'я користувача. Ми проведемо Вас крок за кроком, щоб створити обліковий запис. +\nВи зможете спілкуватися з користувачами інших постачальників, для цього повідомте їм свою повну адресу XMPP. Ваше запрошення до сервера Неправильно відформатований код забезпечення - \ No newline at end of file + Якщо контакт поблизу, він також може прийняти запрошення, відсканувавши код нижче. + Приєднуйтеся до %1$s і спілкуйтеся зі мною: %2$s + Запросити… + Натисніть «Поділитися», щоб надіслати Вашому контакту запрошення до %1$s. + \ No newline at end of file diff --git a/src/conversations/res/values-zh-rCN/strings.xml b/src/conversations/res/values-zh-rCN/strings.xml index 961973b8c1faf6dc1797b12614ffdd65f851ba33..058adbf1e3ec5979085013ceb2daaa81f6fc4480 100644 --- a/src/conversations/res/values-zh-rCN/strings.xml +++ b/src/conversations/res/values-zh-rCN/strings.xml @@ -2,15 +2,19 @@ 选择您的 XMPP 提供者 使用 conversations.im - 创建新账户 - 您有 XMPP 账户吗?如果您之前使用过其他的 XMPP 客户端,那么您已经拥有这种账户了。如果没有的话,您现在可以创建一个。\n提示:有些电子邮件服务也提供XMPP账户。 - XMPP 是独立于提供者的即时消息网络。您可以将此客户端与任意 XMPP 服务器一同使用。\n不过,您可以很容易地在 conversations.im 上创建账户;它是特别适合与“Conversations”一起使用的提供者。 - 您已受邀加入 %1$s。我们将指导您完成创建帐户的过程。\n使用 %1$s 作为提供者时,您可以通过您的完整 XMPP 地址与其他提供者的用户进行交流。 - 您已受邀加入 %1$s。已为您选择了一个用户名。我们将指导您完成创建帐户的过程。\n您可以使用完整的 XMPP 地址来与其他提供者的用户进行交流。 - 你的服务器邀请 - 格式不正确的配置代码 - 点击分享按钮向您的联系人发送加入 %1$s 的邀请。 - 如果你的联系人在附近,他们也可以扫描下面的代码来接受你的邀请。 + 创建新账号 + 您已经有 XMPP 账号了吗?如果您之前使用过 Conversations 或其他 XMPP 客户端,那么您已经有这种账号了。如果没有,您可以立即创建一个。 +\n提示:一些电子邮件服务也提供 XMPP 账号。 + XMPP 是独立于提供者的即时通讯网络。您选择的任何 XMPP 服务器都可以使用此客户端。 +\n不过,您可以轻松地在 conversations.im 上创建账号;特别适合与 Conversations 使用的提供者。 + 您已受邀加入 %1$s。我们将指导您创建账号。 +\n当选择 %1$s 作为提供者时,向其他 XMPP 用户提供您的完整地址,就能和对方交流。 + 您已受邀加入 %1$s。已为您选择了用户名。我们将指导您创建账号。 +\n向其他 XMPP 用户提供您的完整地址,就能和对方交流。 + 您的服务器邀请 + 配置代码格式不正确 + 点击分享按钮,向您的联系人发送加入 %1$s 的邀请。 + 如果您的联系人在附近,对方也可以扫描下方二维码接受邀请。 加入 %1$s 和我聊天:%2$s - 分享邀请… + 分享邀请至… \ No newline at end of file diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 10d5666bda67fd898107052d04034d0b1af4d589..690de3db4624a945bc23b3f91a8e839d0f2ab50c 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -3,8 +3,13 @@ xmlns:tools="http://schemas.android.com/tools"> - - + + - + + + + + + + + + + @@ -77,30 +94,60 @@ + tools:targetApi="tiramisu"> - + + + + + + + + + + + + + + android:exported="false"> - + @@ -147,7 +194,6 @@ + android:exported="true"> @@ -281,7 +326,6 @@ @@ -327,19 +371,6 @@ android:name=".ui.MediaBrowserActivity" android:label="@string/media_browser" /> - - - - - - - - QUERY_CACHE = + new LruCache<>(1024); + private final Context context; + private final NetworkDataSource networkDataSource = new NetworkDataSource(); + private boolean askForDnssec = false; + + public AndroidDNSClient(final Context context) { + super(); + this.setDataSource(networkDataSource); + this.context = context; + } + + private static String getPrivateDnsServerName(final LinkProperties linkProperties) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + return linkProperties.getPrivateDnsServerName(); + } else { + return null; + } + } + + private static boolean isPrivateDnsActive(final LinkProperties linkProperties) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + return linkProperties.isPrivateDnsActive(); + } else { + return false; + } + } + + @Override + protected DNSMessage.Builder newQuestion(final DNSMessage.Builder message) { + message.setRecursionDesired(true); + message.getEdnsBuilder() + .setUdpPayloadSize(networkDataSource.getUdpPayloadSize()) + .setDnssecOk(askForDnssec); + return message; + } + + @Override + protected DNSMessage query(final DNSMessage.Builder queryBuilder) throws IOException { + final DNSMessage question = newQuestion(queryBuilder).build(); + for (final DNSServer dnsServer : getDNSServers()) { + final QuestionServerTuple cacheKey = new QuestionServerTuple(dnsServer, question); + final DNSMessage cachedResponse = queryCache(cacheKey); + if (cachedResponse != null) { + return cachedResponse; + } + final DNSMessage response = this.networkDataSource.query(question, dnsServer); + if (response == null) { + continue; + } + switch (response.responseCode) { + case NO_ERROR: + case NX_DOMAIN: + break; + default: + continue; + } + cacheQuery(cacheKey, response); + return response; + } + return null; + } + + public boolean isAskForDnssec() { + return askForDnssec; + } + + public void setAskForDnssec(boolean askForDnssec) { + this.askForDnssec = askForDnssec; + } + + private List getDNSServers() { + final ImmutableList.Builder dnsServerBuilder = new ImmutableList.Builder<>(); + final ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + final Network[] networks = getActiveNetworks(connectivityManager); + for (final Network network : networks) { + final LinkProperties linkProperties = connectivityManager.getLinkProperties(network); + if (linkProperties == null) { + continue; + } + final String privateDnsServerName = getPrivateDnsServerName(linkProperties); + if (Strings.isNullOrEmpty(privateDnsServerName)) { + final boolean isPrivateDns = isPrivateDnsActive(linkProperties); + for (final InetAddress dnsServer : linkProperties.getDnsServers()) { + if (isPrivateDns) { + dnsServerBuilder.add(new DNSServer(dnsServer, Transport.TLS)); + } else { + dnsServerBuilder.add(new DNSServer(dnsServer)); + } + } + } else { + dnsServerBuilder.add(new DNSServer(privateDnsServerName, Transport.TLS)); + } + } + return dnsServerBuilder.build(); + } + + private Network[] getActiveNetworks(final ConnectivityManager connectivityManager) { + if (connectivityManager == null) { + return new Network[0]; + } + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + final Network activeNetwork = connectivityManager.getActiveNetwork(); + if (activeNetwork != null) { + return new Network[] {activeNetwork}; + } + } + return connectivityManager.getAllNetworks(); + } + + private DNSMessage queryCache(final QuestionServerTuple key) { + final DNSMessage cachedResponse; + synchronized (QUERY_CACHE) { + cachedResponse = QUERY_CACHE.get(key); + if (cachedResponse == null) { + return null; + } + final long expiresIn = expiresIn(cachedResponse); + if (expiresIn < 0) { + QUERY_CACHE.remove(key); + return null; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Log.d( + Config.LOGTAG, + "DNS query came from cache. expires in " + Duration.ofMillis(expiresIn)); + } + } + return cachedResponse; + } + + private void cacheQuery(final QuestionServerTuple key, final DNSMessage response) { + if (response.receiveTimestamp <= 0) { + return; + } + synchronized (QUERY_CACHE) { + QUERY_CACHE.put(key, response); + } + } + + private static long ttl(final DNSMessage dnsMessage) { + final List> answerSection = dnsMessage.answerSection; + if (answerSection == null || answerSection.isEmpty()) { + final List> authoritySection = dnsMessage.authoritySection; + if (authoritySection == null || authoritySection.isEmpty()) { + return 0; + } else { + return Collections.min(Collections2.transform(authoritySection, d -> d.ttl)); + } + + } else { + return Collections.min(Collections2.transform(answerSection, d -> d.ttl)); + } + } + + private static long expiresAt(final DNSMessage dnsMessage) { + return dnsMessage.receiveTimestamp + (Math.min(DNS_MAX_TTL, ttl(dnsMessage)) * 1000L); + } + + private static long expiresIn(final DNSMessage dnsMessage) { + return expiresAt(dnsMessage) - System.currentTimeMillis(); + } + + private static class QuestionServerTuple { + private final DNSServer dnsServer; + private final DNSMessage question; + + private QuestionServerTuple(final DNSServer dnsServer, final DNSMessage question) { + this.dnsServer = dnsServer; + this.question = question.asNormalizedVersion(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + QuestionServerTuple that = (QuestionServerTuple) o; + return Objects.equal(dnsServer, that.dnsServer) + && Objects.equal(question, that.question); + } + + @Override + public int hashCode() { + return Objects.hashCode(dnsServer, question); + } + } +} diff --git a/src/main/java/de/gultsch/minidns/DNSServer.java b/src/main/java/de/gultsch/minidns/DNSServer.java new file mode 100644 index 0000000000000000000000000000000000000000..7486ec2c60c6a6601220b37bf19efbfc96f9e888 --- /dev/null +++ b/src/main/java/de/gultsch/minidns/DNSServer.java @@ -0,0 +1,104 @@ +package de.gultsch.minidns; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; + +import java.net.InetAddress; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.annotation.Nonnull; + +public final class DNSServer { + + public final InetAddress inetAddress; + public final String hostname; + public final int port; + public final List transports; + + public DNSServer(InetAddress inetAddress, Integer port, Transport transport) { + this.inetAddress = inetAddress; + this.port = port == null ? 0 : port; + this.transports = Collections.singletonList(transport); + this.hostname = null; + } + + public DNSServer(final String hostname, final Integer port, final Transport transport) { + Preconditions.checkArgument( + Arrays.asList(Transport.HTTPS, Transport.TLS).contains(transport), + "hostname validation only works with TLS based transports"); + this.hostname = hostname; + this.port = port == null ? 0 : port; + this.transports = Collections.singletonList(transport); + this.inetAddress = null; + } + + public DNSServer(final String hostname, final Transport transport) { + this(hostname, Transport.DEFAULT_PORTS.get(transport), transport); + } + + public DNSServer(InetAddress inetAddress, Transport transport) { + this(inetAddress, Transport.DEFAULT_PORTS.get(transport), transport); + } + + public DNSServer(final InetAddress inetAddress) { + this(inetAddress, 53, Arrays.asList(Transport.UDP, Transport.TCP)); + } + + public DNSServer(final InetAddress inetAddress, int port, List transports) { + this(inetAddress, null, port, transports); + } + + private DNSServer( + final InetAddress inetAddress, + final String hostname, + final int port, + final List transports) { + this.inetAddress = inetAddress; + this.hostname = hostname; + this.port = port; + this.transports = transports; + } + + public Transport uniqueTransport() { + return Iterables.getOnlyElement(this.transports); + } + + public DNSServer asUniqueTransport(final Transport transport) { + Preconditions.checkArgument( + this.transports.contains(transport), + "This DNS server does not have transport ", + transport); + return new DNSServer(inetAddress, hostname, port, Collections.singletonList(transport)); + } + + @Override + @Nonnull + public String toString() { + return MoreObjects.toStringHelper(this) + .add("inetAddress", inetAddress) + .add("hostname", hostname) + .add("port", port) + .add("transports", transports) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DNSServer dnsServer = (DNSServer) o; + return port == dnsServer.port + && Objects.equal(inetAddress, dnsServer.inetAddress) + && Objects.equal(hostname, dnsServer.hostname) + && Objects.equal(transports, dnsServer.transports); + } + + @Override + public int hashCode() { + return Objects.hashCode(inetAddress, hostname, port, transports); + } +} diff --git a/src/main/java/de/gultsch/minidns/DNSSocket.java b/src/main/java/de/gultsch/minidns/DNSSocket.java new file mode 100644 index 0000000000000000000000000000000000000000..a3403115a73c097e231626436453f59ac3b7a0bc --- /dev/null +++ b/src/main/java/de/gultsch/minidns/DNSSocket.java @@ -0,0 +1,199 @@ +package de.gultsch.minidns; + +import android.util.Log; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; + +import de.measite.minidns.DNSMessage; + +import eu.siacs.conversations.Config; + +import org.conscrypt.OkHostnameVerifier; + +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +final class DNSSocket implements Closeable { + + public static final int QUERY_TIMEOUT = 5_000; + + private final Semaphore semaphore = new Semaphore(1); + private final Map> inFlightQueries = new HashMap<>(); + private final Socket socket; + private final DataInputStream dataInputStream; + private final DataOutputStream dataOutputStream; + + private DNSSocket( + final Socket socket, + final DataInputStream dataInputStream, + final DataOutputStream dataOutputStream) { + this.socket = socket; + this.dataInputStream = dataInputStream; + this.dataOutputStream = dataOutputStream; + new Thread(this::readDNSMessages).start(); + } + + private void readDNSMessages() { + try { + while (socket.isConnected()) { + final DNSMessage response = readDNSMessage(); + final SettableFuture future; + synchronized (inFlightQueries) { + future = inFlightQueries.remove(response.id); + } + if (future != null) { + future.set(response); + } else { + Log.e(Config.LOGTAG, "no in flight query found for response id " + response.id); + } + } + evictInFlightQueries(new EOFException()); + } catch (final IOException e) { + evictInFlightQueries(e); + } + } + + private void evictInFlightQueries(final Exception e) { + synchronized (inFlightQueries) { + final Iterator>> iterator = + inFlightQueries.entrySet().iterator(); + while (iterator.hasNext()) { + final Map.Entry> entry = iterator.next(); + entry.getValue().setException(e); + iterator.remove(); + } + } + } + + private static DNSSocket of(final Socket socket) throws IOException { + final DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); + final DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); + return new DNSSocket(socket, dataInputStream, dataOutputStream); + } + + public static DNSSocket connect(final DNSServer dnsServer) throws IOException { + switch (dnsServer.uniqueTransport()) { + case TCP: + return connectTcpSocket(dnsServer); + case TLS: + return connectTlsSocket(dnsServer); + default: + throw new IllegalStateException("This is not a socket based transport"); + } + } + + private static DNSSocket connectTcpSocket(final DNSServer dnsServer) throws IOException { + Preconditions.checkArgument(dnsServer.uniqueTransport() == Transport.TCP); + final SocketAddress socketAddress = + new InetSocketAddress(dnsServer.inetAddress, dnsServer.port); + final Socket socket = new Socket(); + socket.connect(socketAddress, QUERY_TIMEOUT / 2); + socket.setSoTimeout(QUERY_TIMEOUT); + return DNSSocket.of(socket); + } + + private static DNSSocket connectTlsSocket(final DNSServer dnsServer) throws IOException { + Preconditions.checkArgument(dnsServer.uniqueTransport() == Transport.TLS); + final SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); + final SSLSocket sslSocket = (SSLSocket) factory.createSocket(); + if (Strings.isNullOrEmpty(dnsServer.hostname)) { + final SocketAddress socketAddress = + new InetSocketAddress(dnsServer.inetAddress, dnsServer.port); + sslSocket.connect(socketAddress, QUERY_TIMEOUT / 2); + sslSocket.setSoTimeout(QUERY_TIMEOUT); + sslSocket.startHandshake(); + } else { + final SocketAddress socketAddress = new InetSocketAddress(dnsServer.hostname, dnsServer.port); + sslSocket.connect(socketAddress, QUERY_TIMEOUT / 2); + sslSocket.setSoTimeout(QUERY_TIMEOUT); + sslSocket.startHandshake(); + final SSLSession session = sslSocket.getSession(); + final Certificate[] peerCertificates = session.getPeerCertificates(); + if (peerCertificates.length == 0 || !(peerCertificates[0] instanceof X509Certificate)) { + throw new IOException("Peer did not provide X509 certificates"); + } + final X509Certificate certificate = (X509Certificate) peerCertificates[0]; + if (!OkHostnameVerifier.strictInstance().verify(dnsServer.hostname, certificate)) { + throw new SSLPeerUnverifiedException("Peer did not provide valid certificates"); + } + } + return DNSSocket.of(sslSocket); + } + + public DNSMessage query(final DNSMessage query) throws IOException, InterruptedException { + try { + return queryAsync(query).get(QUERY_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (final ExecutionException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IOException) { + throw (IOException) cause; + } else { + throw new IOException(e); + } + } catch (final TimeoutException e) { + throw new IOException(e); + } + } + + public ListenableFuture queryAsync(final DNSMessage query) + throws InterruptedException, IOException { + final SettableFuture responseFuture = SettableFuture.create(); + synchronized (this.inFlightQueries) { + this.inFlightQueries.put(query.id, responseFuture); + } + this.semaphore.acquire(); + try { + query.writeTo(this.dataOutputStream); + this.dataOutputStream.flush(); + } finally { + this.semaphore.release(); + } + return responseFuture; + } + + private DNSMessage readDNSMessage() throws IOException { + final int length = this.dataInputStream.readUnsignedShort(); + byte[] data = new byte[length]; + int read = 0; + while (read < length) { + read += this.dataInputStream.read(data, read, length - read); + } + return new DNSMessage(data); + } + + @Override + public void close() throws IOException { + this.socket.close(); + } + + public void closeQuietly() { + try { + this.socket.close(); + } catch (final IOException ignored) { + + } + } +} diff --git a/src/main/java/de/gultsch/minidns/NetworkDataSource.java b/src/main/java/de/gultsch/minidns/NetworkDataSource.java new file mode 100644 index 0000000000000000000000000000000000000000..93909891d54a37b7a7c0b5570cb3d2fb7f8f885e --- /dev/null +++ b/src/main/java/de/gultsch/minidns/NetworkDataSource.java @@ -0,0 +1,160 @@ +package de.gultsch.minidns; + +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.cache.RemovalListener; +import com.google.common.collect.ImmutableList; + +import de.measite.minidns.DNSMessage; +import de.measite.minidns.MiniDNSException; +import de.measite.minidns.source.DNSDataSource; +import de.measite.minidns.util.MultipleIoException; + +import eu.siacs.conversations.Config; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public class NetworkDataSource extends DNSDataSource { + + private static final LoadingCache socketCache = + CacheBuilder.newBuilder() + .removalListener( + (RemovalListener) + notification -> { + final DNSServer dnsServer = notification.getKey(); + final DNSSocket dnsSocket = notification.getValue(); + if (dnsSocket == null) { + return; + } + Log.d(Config.LOGTAG, "closing connection to " + dnsServer); + dnsSocket.closeQuietly(); + }) + .expireAfterAccess(5, TimeUnit.MINUTES) + .build( + new CacheLoader() { + @Override + @NonNull + public DNSSocket load(@NonNull final DNSServer dnsServer) + throws Exception { + Log.d(Config.LOGTAG, "establishing connection to " + dnsServer); + return DNSSocket.connect(dnsServer); + } + }); + + private static List transportsForPort(final int port) { + final ImmutableList.Builder transportBuilder = new ImmutableList.Builder<>(); + for (final Map.Entry entry : Transport.DEFAULT_PORTS.entrySet()) { + if (entry.getValue().equals(port)) { + transportBuilder.add(entry.getKey()); + } + } + return transportBuilder.build(); + } + + @Override + public DNSMessage query(final DNSMessage message, final InetAddress address, final int port) + throws IOException { + final List transports = transportsForPort(port); + Log.w( + Config.LOGTAG, + "using legacy DataSource interface. guessing transports " + + transports + + " from port"); + if (transports.isEmpty()) { + throw new IOException(String.format("No transports found for port %d", port)); + } + return query(message, new DNSServer(address, port, transports)); + } + + public DNSMessage query(final DNSMessage message, final DNSServer dnsServer) + throws IOException { + Log.d(Config.LOGTAG, "using " + dnsServer); + final List ioExceptions = new ArrayList<>(); + for (final Transport transport : dnsServer.transports) { + try { + final DNSMessage response = + queryWithUniqueTransport(message, dnsServer.asUniqueTransport(transport)); + if (response != null && !response.truncated) { + return response; + } + } catch (final IOException e) { + ioExceptions.add(e); + } catch (final InterruptedException e) { + throw new IOException(e); + } + } + MultipleIoException.throwIfRequired(ioExceptions); + return null; + } + + private DNSMessage queryWithUniqueTransport(final DNSMessage message, final DNSServer dnsServer) + throws IOException, InterruptedException { + final Transport transport = dnsServer.uniqueTransport(); + switch (transport) { + case UDP: + return queryUdp(message, dnsServer.inetAddress, dnsServer.port); + case TCP: + case TLS: + return queryDnsSocket(message, dnsServer); + default: + throw new IOException( + String.format("Transport %s has not been implemented", transport)); + } + } + + protected DNSMessage queryUdp( + final DNSMessage message, final InetAddress address, final int port) + throws IOException { + final DatagramPacket request = message.asDatagram(address, port); + final byte[] buffer = new byte[udpPayloadSize]; + try (final DatagramSocket socket = new DatagramSocket()) { + socket.setSoTimeout(timeout); + socket.send(request); + final DatagramPacket response = new DatagramPacket(buffer, buffer.length); + socket.receive(response); + DNSMessage dnsMessage = new DNSMessage(response.getData()); + if (dnsMessage.id != message.id) { + throw new MiniDNSException.IdMismatch(message, dnsMessage); + } + return dnsMessage; + } + } + + protected DNSMessage queryDnsSocket(final DNSMessage message, final DNSServer dnsServer) + throws IOException, InterruptedException { + final DNSSocket cachedDnsSocket = socketCache.getIfPresent(dnsServer); + if (cachedDnsSocket != null) { + try { + return cachedDnsSocket.query(message); + } catch (final IOException e) { + Log.d( + Config.LOGTAG, + "IOException occurred at cached socket. invalidating and falling through to new socket creation"); + socketCache.invalidate(dnsServer); + } + } + try { + return socketCache.get(dnsServer).query(message); + } catch (final ExecutionException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IOException) { + throw (IOException) cause; + } else { + throw new IOException(cause); + } + } + } +} diff --git a/src/main/java/de/gultsch/minidns/Transport.java b/src/main/java/de/gultsch/minidns/Transport.java new file mode 100644 index 0000000000000000000000000000000000000000..3aabfacaa57177e6c3d1d6f5250f8303a3120063 --- /dev/null +++ b/src/main/java/de/gultsch/minidns/Transport.java @@ -0,0 +1,23 @@ +package de.gultsch.minidns; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public enum Transport { + UDP, + TCP, + TLS, + HTTPS; + + public static final Map DEFAULT_PORTS; + + static { + final ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + builder.put(Transport.UDP, 53); + builder.put(Transport.TCP, 53); + builder.put(Transport.TLS, 853); + builder.put(Transport.HTTPS, 443); + DEFAULT_PORTS = builder.build(); + } +} diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index fb3e54728a119fe6fb6cd03ad118792521edb7e7..1e0e71f41b72586283c006682e81811724f9783e 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -84,6 +84,8 @@ public final class Config { public static final boolean XEP_0392 = true; //enables XEP-0392 v0.6.0 + + // media file formats. Homogenous Android or Conversations only deployments can switch to opus and webp public static final int AVATAR_SIZE = 192; public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.JPEG; public static final int AVATAR_CHAR_LIMIT = 9400; @@ -92,6 +94,8 @@ public final class Config { public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG; public static final int IMAGE_QUALITY = 75; + public static final boolean USE_OPUS_VOICE_MESSAGES = true; + public static final int MESSAGE_MERGE_WINDOW = 20; public static final int PAGE_SIZE = 50; diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java index db84e0cf4da5e3ca8336dcb6842f594acdc45cf1..68447e552faf651eaf19be8fd1f6073e453a9adc 100644 --- a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java +++ b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java @@ -156,7 +156,8 @@ public class PgpDecryptionService { && manager.getAutoAcceptFileSize() > 0) { manager.createNewDownloadConnection(message); } - } catch (IOException e) { + } catch (final IOException e) { + Log.d(Config.LOGTAG,"decryption failed", e); message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); } mXmppConnectionService.updateMessage(message); @@ -170,6 +171,7 @@ public class PgpDecryptionService { } break; case OpenPgpApi.RESULT_CODE_ERROR: + Log.d(Config.LOGTAG,"decryption failed (api error)"); message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); mXmppConnectionService.updateMessage(message); break; diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 05ffdbdca5bf085b1fd151362e67cd1c5f0122f4..9cf3d9091a6cf4d659fe91797e94817f6b73b120 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -736,8 +736,12 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return axolotlStore.getFingerprintCertificate(fingerprint); } - public void setFingerprintTrust(String fingerprint, FingerprintStatus status) { + public void setFingerprintTrust(final String fingerprint, final FingerprintStatus status) { axolotlStore.setFingerprintStatus(fingerprint, status); + // TODO we decided to call this after a fingerprint gets toggled to update the 'your contact + // is using unverified devices text'; however this means the entire screen gets redrawn + // after a toggle which might be annoying or cause other weird UI glitches + mXmppConnectionService.updateAccountUi(); } private ListenableFuture verifySessionWithPEP(final XmppAxolotlSession session) { diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java index 2f1856d09efe242ae5615fc3bc28793b56512767..dffde90a1db81e882cbd9830a595a1dc78e62344 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java @@ -97,6 +97,10 @@ public class FingerprintStatus implements Comparable { return trust == Trust.TRUSTED || isVerified(); } + public boolean isUnverified() { + return trust == Trust.TRUSTED; + } + public boolean isVerified() { return trust == Trust.VERIFIED || trust == Trust.VERIFIED_X509; } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java index 216f3d7f81f834b02d5e8640e5b1ba57ff6e62cb..2eb5e39fb2999e47a6c97c9691838c195a26c43d 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java @@ -117,4 +117,14 @@ public enum ChannelBinding { throw new AssertionError("Missing short name for " + channelBinding); } } + + public static int priority(final ChannelBinding channelBinding) { + if (Arrays.asList(TLS_EXPORTER,TLS_UNIQUE).contains(channelBinding)) { + return 2; + } else if (channelBinding == ChannelBinding.TLS_SERVER_END_POINT) { + return 1; + } else { + return 0; + } + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBindingMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBindingMechanism.java index b94210a60d5ead030bf78db758a6ad34b349111d..7343eb86e82b5a065882d6bc49552b4b20222a67 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBindingMechanism.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBindingMechanism.java @@ -97,4 +97,13 @@ public interface ChannelBindingMechanism { messageDigest.update(encodedCertificate); return messageDigest.digest(); } + + static int getPriority(final SaslMechanism mechanism) { + if (mechanism instanceof ChannelBindingMechanism) { + final ChannelBindingMechanism channelBindingMechanism = (ChannelBindingMechanism) mechanism; + return ChannelBinding.priority(channelBindingMechanism.getChannelBinding()); + } else { + return 0; + } + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1Plus.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1Plus.java index 2ca27570f79e8108b447f5fe7deef8ec09e21bc6..4490d7621cc3dad1ecde4128b6874adaba58c316 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1Plus.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1Plus.java @@ -27,7 +27,7 @@ public class ScramSha1Plus extends ScramPlusMechanism { @Override public int getPriority() { - return 35; // higher than SCRAM-SHA512 (30) + return 35 + ChannelBinding.priority(this.channelBinding); // higher than SCRAM-SHA512 (30) } @Override diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256Plus.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256Plus.java index 4db33a2fa08566a3ea5c3abb882ab5bf6b7a9255..eafc86fbcfd137b43e1ecf514a3b97b9dc06fd89 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256Plus.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256Plus.java @@ -27,7 +27,7 @@ public class ScramSha256Plus extends ScramPlusMechanism { @Override public int getPriority() { - return 40; + return 40 + ChannelBinding.priority(this.channelBinding); } @Override diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512Plus.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512Plus.java index 5d846197314d713ac5b47d6ab721f078ad6bfd35..d110e77082380e8c151dbe4d68981b447a6cce4c 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512Plus.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512Plus.java @@ -27,7 +27,7 @@ public class ScramSha512Plus extends ScramPlusMechanism { @Override public int getPriority() { - return 45; + return 45 + ChannelBinding.priority(this.channelBinding); } @Override diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 2b31cacd5c172d3d242cd2bd3c81f21e438d520a..7f1d11db4cdf88ccaaaeccaddf73991c065d71da 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -34,7 +34,6 @@ import eu.siacs.conversations.crypto.sasl.HashedToken; import eu.siacs.conversations.crypto.sasl.HashedTokenSha256; import eu.siacs.conversations.crypto.sasl.HashedTokenSha512; import eu.siacs.conversations.crypto.sasl.SaslMechanism; -import eu.siacs.conversations.crypto.sasl.ScramPlusMechanism; import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.UIHelper; @@ -74,6 +73,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable public static final int OPTION_UNVERIFIED = 8; public static final int OPTION_FIXED_USERNAME = 9; public static final int OPTION_QUICKSTART_AVAILABLE = 10; + public static final int OPTION_SOFT_DISABLED = 11; private static final String KEY_PGP_SIGNATURE = "pgp_signature"; private static final String KEY_PGP_ID = "pgp_id"; @@ -271,11 +271,18 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable return !isOptionSet(Account.OPTION_DISABLED); } + public boolean isConnectionEnabled() { + return !isOptionSet(Account.OPTION_DISABLED) && !isOptionSet(Account.OPTION_SOFT_DISABLED); + } + public boolean isOptionSet(final int option) { return ((options & (1 << option)) != 0); } public boolean setOption(final int option, final boolean value) { + if (value && (option == OPTION_DISABLED || option == OPTION_SOFT_DISABLED)) { + this.setStatus(State.OFFLINE); + } final int before = this.options; if (value) { this.options |= 1 << option; @@ -345,11 +352,17 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable public State getStatus() { if (isOptionSet(OPTION_DISABLED)) { return State.DISABLED; + } else if (isOptionSet(OPTION_SOFT_DISABLED)) { + return State.LOGGED_OUT; } else { return this.status; } } + public boolean unauthorized() { + return this.status == State.UNAUTHORIZED || this.lastErrorStatus == State.UNAUTHORIZED; + } + public State getLastErrorStatus() { return this.lastErrorStatus; } @@ -787,6 +800,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable public enum State { DISABLED(false, false), + LOGGED_OUT(false,false), OFFLINE(false), CONNECTING(false), ONLINE(false), @@ -812,6 +826,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable BIND_FAILURE, HOST_UNKNOWN, STREAM_ERROR, + SEE_OTHER_HOST, STREAM_OPENING_ERROR, POLICY_VIOLATION, PAYMENT_REQUIRED, @@ -845,6 +860,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable switch (this) { case DISABLED: return R.string.account_status_disabled; + case LOGGED_OUT: + return R.string.account_state_logged_out; case ONLINE: return R.string.account_status_online; case CONNECTING: @@ -899,6 +916,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable return R.string.account_status_stream_opening_error; case PAYMENT_REQUIRED: return R.string.payment_required; + case SEE_OTHER_HOST: + return R.string.reconnect_on_other_host; case MISSING_INTERNET_PERMISSION: return R.string.missing_internet_permission; case TEMPORARY_AUTH_FAILURE: diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 22b907557cc4e4c3db2f8ab6e97e919a8f68e504..ff3e9a00da8d138d304adc024fcd9bd84594a98d 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -176,8 +176,9 @@ public class MucOptions { } public boolean participantsCanChangeSubject() { - Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_changesubject"); - if (field == null) field = getRoomInfoForm().getFieldByName("muc#roominfo_changesubject"); + final Field configField = getRoomInfoForm().getFieldByName("muc#roomconfig_changesubject"); + final Field infoField = getRoomInfoForm().getFieldByName("muc#roominfo_changesubject"); + final Field field = configField != null ? configField : infoField; return field != null && "1".equals(field.getValue()); } diff --git a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java index 495cfaae4ef91135b668e268c851694d2d22dd17..1168d205e1d482c9072d9d5222717278b4e322e2 100644 --- a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java +++ b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java @@ -17,6 +17,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import eu.siacs.conversations.xml.Element; @@ -100,9 +101,9 @@ public class ServiceDiscoveryResult { public ServiceDiscoveryResult(Cursor cursor) throws JSONException { this( - cursor.getString(cursor.getColumnIndex(HASH)), - Base64.decode(cursor.getString(cursor.getColumnIndex(VER)), Base64.DEFAULT), - new JSONObject(cursor.getString(cursor.getColumnIndex(RESULT))) + cursor.getString(cursor.getColumnIndexOrThrow(HASH)), + Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(VER)), Base64.DEFAULT), + new JSONObject(cursor.getString(cursor.getColumnIndexOrThrow(RESULT))) ); } @@ -213,24 +214,23 @@ public class ServiceDiscoveryResult { .append("<"); } - List features = this.getFeatures(); + final List features = this.getFeatures(); Collections.sort(features); - - for (String feature : features) { + for (final String feature : features) { s.append(clean(feature)).append("<"); } - Collections.sort(forms, (lhs, rhs) -> lhs.getFormType().compareTo(rhs.getFormType())); - - for (Data form : forms) { + Collections.sort(forms, Comparator.comparing(Data::getFormType)); + for (final Data form : forms) { s.append(clean(form.getFormType())).append("<"); - List fields = form.getFields(); - Collections.sort(fields, (lhs, rhs) -> Strings.nullToEmpty(lhs.getFieldName()).compareTo(Strings.nullToEmpty(rhs.getFieldName()))); - for (Field field : fields) { + final List fields = form.getFields(); + Collections.sort( + fields, Comparator.comparing(lhs -> Strings.nullToEmpty(lhs.getFieldName()))); + for (final Field field : fields) { s.append(Strings.nullToEmpty(field.getFieldName())).append("<"); - List values = field.getValues(); - Collections.sort(values); - for (String value : values) { + final List values = field.getValues(); + Collections.sort(values, Comparator.comparing(ServiceDiscoveryResult::blankNull)); + for (final String value : values) { s.append(blankNull(value)).append("<"); } } diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 709a6b464ac74fba7fb691e1b18fc2e95914a89e..ebfe0885d1be72028298e374db2730d6bc5b1fdd 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -224,8 +224,8 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket invite(Conversation conversation, Jid contact) { - MessagePacket packet = new MessagePacket(); + public MessagePacket invite(final Conversation conversation, final Jid contact) { + final MessagePacket packet = new MessagePacket(); packet.setTo(conversation.getJid().asBareJid()); packet.setFrom(conversation.getAccount().getJid()); Element x = new Element("x"); diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 5224a0200e448db4d1ebe67a3dcb56a12ff68d44..4eaa445afaeea8ab9df94bef30e86bb8b9e9072e 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -63,7 +63,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH); - private static final List JINGLE_MESSAGE_ELEMENT_NAMES = Arrays.asList("accept", "propose", "proceed", "reject", "retract"); + private static final List JINGLE_MESSAGE_ELEMENT_NAMES = + Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing"); public MessageParser(XmppConnectionService service) { super(service); @@ -180,14 +181,19 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece return null; } - private Invite extractInvite(Element message) { + private Invite extractInvite(final Element message) { final Element mucUser = message.findChild("x", Namespace.MUC_USER); if (mucUser != null) { - Element invite = mucUser.findChild("invite"); + final Element invite = mucUser.findChild("invite"); if (invite != null) { - String password = mucUser.findChildContent("password"); - Jid from = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("from")); - Jid room = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from")); + final String password = mucUser.findChildContent("password"); + final Jid from = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("from")); + final Jid to = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("to")); + if (to != null && from == null) { + Log.d(Config.LOGTAG,"do not parse outgoing mediated invite "+message); + return null; + } + final Jid room = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from")); if (room == null) { return null; } @@ -494,8 +500,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece final Invite invite = extractInvite(packet); if (invite != null) { - if (isTypeGroupChat) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring invite to " + invite.jid + " because type=groupchat"); + if (invite.jid.asBareJid().equals(account.getJid().asBareJid())) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignore invite to "+invite.jid+" because it matches account"); + } else if (isTypeGroupChat) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring invite to " + invite.jid + " because it was received as group chat"); } else if (invite.direct && (mucUserElement != null || invite.inviter == null || mXmppConnectionService.isMuc(account, invite.inviter))) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring direct invite to " + invite.jid + " because it was received in MUC"); } else { @@ -1016,9 +1024,22 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece if (serverMsgId == null) { serverMsgId = extractStanzaId(account, packet); } - mXmppConnectionService.getJingleConnectionManager().deliverMessage(account, packet.getTo(), packet.getFrom(), child, remoteMsgId, serverMsgId, timestamp); - if (!account.getJid().asBareJid().equals(from.asBareJid()) && remoteMsgId != null) { - processMessageReceipts(account, packet, remoteMsgId, query); + mXmppConnectionService + .getJingleConnectionManager() + .deliverMessage( + account, + packet.getTo(), + packet.getFrom(), + child, + remoteMsgId, + serverMsgId, + timestamp); + final Contact contact = account.getRoster().getContact(from); + if (mXmppConnectionService.confirmMessages() + && !contact.isSelf() + && remoteMsgId != null + && contact.showInContactList()) { + processMessageReceipts(account, packet, remoteMsgId, null); } } else if (query.isCatchup()) { if ("propose".equals(action)) { diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 12fb829116d6ecd97152ff724cc699ab0b37111e..c6eccde5ea72b3c73267ef17808a1236ec95c91a 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -684,8 +684,13 @@ public class FileBackend { } } - public String getOriginalPath(Uri uri) { - return FileUtils.getPath(mXmppConnectionService, uri); + public String getOriginalPath(final Uri uri) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // On Android 11+ we don’t have access to the original file + return null; + } else { + return FileUtils.getPath(mXmppConnectionService, uri); + } } public void copyFileToDocumentFile(Context ctx, File file, DocumentFile df) throws FileCopyException { diff --git a/src/main/java/eu/siacs/conversations/services/AbstractQuickConversationsService.java b/src/main/java/eu/siacs/conversations/services/AbstractQuickConversationsService.java index d05e40ae86bf04b0eced721a850c2ba7b1ad2ae4..5d6f8eee5090ae984653f5fdc4044da08e883623 100644 --- a/src/main/java/eu/siacs/conversations/services/AbstractQuickConversationsService.java +++ b/src/main/java/eu/siacs/conversations/services/AbstractQuickConversationsService.java @@ -1,6 +1,7 @@ package eu.siacs.conversations.services; import android.content.Intent; +import android.os.Build; import eu.siacs.conversations.BuildConfig; @@ -25,6 +26,10 @@ public abstract class AbstractQuickConversationsService { return "conversations".equals(BuildConfig.FLAVOR_mode); } + public static boolean isPlayStoreFlavor() { + return "playstore".equals(BuildConfig.FLAVOR_distribution); + } + public abstract void signalAccountStateChange(); public abstract boolean isSynchronizing(); diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index f2febdec813174f26c087a0a3d0dd2bcce2b3e09..7ce19cd3f6e148f58f8f25c7208c4261f02f3a55 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -128,7 +128,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { public Bitmap getRoundedShortcut(final MucOptions mucOptions) { final DisplayMetrics metrics = mXmppConnectionService.getResources().getDisplayMetrics(); final int size = Math.round(metrics.density * 48); - Bitmap bitmap = FileBackend.drawDrawable(get(mucOptions, size, false)); + final Bitmap bitmap = FileBackend.drawDrawable(get(mucOptions, size, false)); final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(output); final Paint paint = new Paint(); diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 83b2ecbe077c10a94f52f5c9d2aec4ef7378b958..a86e0add3fdaebb6a2b0ea116fa0e03b3f0c11a4 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -25,6 +25,7 @@ import android.os.Bundle; import android.os.SystemClock; import android.os.Vibrator; import android.preference.PreferenceManager; +import android.provider.Settings; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.text.SpannableString; @@ -47,6 +48,7 @@ import androidx.core.graphics.drawable.IconCompat; import com.google.common.base.Joiner; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import java.io.File; @@ -720,6 +722,14 @@ public class NotificationService { builder.setCategory(NotificationCompat.CATEGORY_CALL); builder.setContentIntent(createPendingRtpSession(id, Intent.ACTION_VIEW, 101)); builder.setOngoing(true); + builder.addAction( + new NotificationCompat.Action.Builder( + R.drawable.ic_call_end_white_48dp, + mXmppConnectionService.getString(R.string.hang_up), + createCallAction( + id.sessionId, XmppConnectionService.ACTION_END_CALL, 104)) + .build()); + builder.setLocalOnly(true); return builder.build(); } @@ -850,6 +860,25 @@ public class NotificationService { } } + public void clearMissedCall(final Message message) { + synchronized (mMissedCalls) { + final Iterator> iterator = mMissedCalls.entrySet().iterator(); + while (iterator.hasNext()) { + final Map.Entry entry = iterator.next(); + final Conversational conversational = entry.getKey(); + final MissedCallsInfo missedCallsInfo = entry.getValue(); + if (conversational.getUuid().equals(message.getConversation().getUuid())) { + if (missedCallsInfo.removeMissedCall()) { + cancel(conversational.getUuid(), MISSED_CALL_NOTIFICATION_ID); + Log.d(Config.LOGTAG,conversational.getAccount().getJid().asBareJid()+": dismissed missed call because call was picked up on other device"); + iterator.remove(); + } + } + } + updateMissedCallNotifications(null); + } + } + public void clearMissedCalls() { synchronized (mMissedCalls) { for (final Conversational conversation : mMissedCalls.keySet()) { @@ -1375,8 +1404,8 @@ public class NotificationService { } final ShortcutInfoCompat info; if (conversation.getMode() == Conversation.MODE_SINGLE) { - Contact contact = conversation.getContact(); - Uri systemAccount = contact.getSystemAccount(); + final Contact contact = conversation.getContact(); + final Uri systemAccount = contact.getSystemAccount(); if (systemAccount != null) { mBuilder.addPerson(systemAccount.toString()); } @@ -1397,7 +1426,9 @@ public class NotificationService { mBuilder.setShortcutInfo(info); if (Build.VERSION.SDK_INT >= 30) { - mXmppConnectionService.getSystemService(ShortcutManager.class).pushDynamicShortcut(info.toShortcutInfo()); + mXmppConnectionService + .getSystemService(ShortcutManager.class) + .pushDynamicShortcut(info.toShortcutInfo()); // mBuilder.setBubbleMetadata(new NotificationCompat.BubbleMetadata.Builder(info.getId()).build()); } } @@ -1763,51 +1794,26 @@ public class NotificationService { } private PendingIntent createCallAction(String sessionId, final String action, int requestCode) { - final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); - intent.setAction(action); - intent.setPackage(mXmppConnectionService.getPackageName()); - intent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, sessionId); - return PendingIntent.getService( - mXmppConnectionService, - requestCode, - intent, - s() - ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT - : PendingIntent.FLAG_UPDATE_CURRENT); + return pendingServiceIntent(mXmppConnectionService, action, requestCode, ImmutableMap.of(RtpSessionActivity.EXTRA_SESSION_ID, sessionId)); } - private PendingIntent createSnoozeIntent(Conversation conversation) { - final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); - intent.setAction(XmppConnectionService.ACTION_SNOOZE); - intent.putExtra("uuid", conversation.getUuid()); - intent.setPackage(mXmppConnectionService.getPackageName()); - return PendingIntent.getService( - mXmppConnectionService, - generateRequestCode(conversation, 22), - intent, - s() - ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT - : PendingIntent.FLAG_UPDATE_CURRENT); + private PendingIntent createSnoozeIntent(final Conversation conversation) { + return pendingServiceIntent(mXmppConnectionService, XmppConnectionService.ACTION_SNOOZE, generateRequestCode(conversation,22),ImmutableMap.of("uuid",conversation.getUuid())); } - private PendingIntent createTryAgainIntent() { - final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); - intent.setAction(XmppConnectionService.ACTION_TRY_AGAIN); - return PendingIntent.getService( - mXmppConnectionService, - 45, - intent, - s() - ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT - : PendingIntent.FLAG_UPDATE_CURRENT); + private static PendingIntent pendingServiceIntent(final Context context, final String action, final int requestCode) { + return pendingServiceIntent(context, action, requestCode, ImmutableMap.of()); } - private PendingIntent createDismissErrorIntent() { - final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); - intent.setAction(XmppConnectionService.ACTION_DISMISS_ERROR_NOTIFICATIONS); + private static PendingIntent pendingServiceIntent(final Context context, final String action, final int requestCode, final Map extras) { + final Intent intent = new Intent(context, XmppConnectionService.class); + intent.setAction(action); + for(final Map.Entry entry : extras.entrySet()) { + intent.putExtra(entry.getKey(), entry.getValue()); + } return PendingIntent.getService( - mXmppConnectionService, - 69, + context, + requestCode, intent, s() ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT @@ -1874,17 +1880,15 @@ public class NotificationService { final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService); mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.app_name)); final List accounts = mXmppConnectionService.getAccounts(); - int enabled = 0; - int connected = 0; - if (accounts != null) { - for (Account account : accounts) { - if (account.isOnlineAndConnected()) { - connected++; - enabled++; - } else if (account.isEnabled()) { - enabled++; - } - } + final int enabled; + final int connected; + if (accounts == null) { + enabled = 0; + connected = 0; + } else { + enabled = Iterables.size(Iterables.filter(accounts, Account::isEnabled)); + connected = + Iterables.size(Iterables.filter(accounts, Account::isOnlineAndConnected)); } mBuilder.setContentText( mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled)); @@ -1902,11 +1906,36 @@ public class NotificationService { if (Compatibility.runsTwentySix()) { mBuilder.setChannelId("foreground"); + mBuilder.addAction( + R.drawable.ic_logout_white_24dp, + mXmppConnectionService.getString(R.string.log_out), + pendingServiceIntent( + mXmppConnectionService, + XmppConnectionService.ACTION_TEMPORARILY_DISABLE, + 87)); + mBuilder.addAction( + R.drawable.ic_notifications_off_white_24dp, + mXmppConnectionService.getString(R.string.hide_notification), + pendingNotificationSettingsIntent(mXmppConnectionService)); } return mBuilder.build(); } + @RequiresApi(api = Build.VERSION_CODES.O) + private static PendingIntent pendingNotificationSettingsIntent(final Context context) { + final Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); + intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()); + intent.putExtra(Settings.EXTRA_CHANNEL_ID, "foreground"); + return PendingIntent.getActivity( + context, + 89, + intent, + s() + ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT + : PendingIntent.FLAG_UPDATE_CURRENT); + } + private PendingIntent createOpenConversationsIntent() { try { return PendingIntent.getActivity( @@ -1957,7 +1986,7 @@ public class NotificationService { mBuilder.addAction( R.drawable.ic_autorenew_white_24dp, mXmppConnectionService.getString(R.string.try_again), - createTryAgainIntent()); + pendingServiceIntent(mXmppConnectionService, XmppConnectionService.ACTION_TRY_AGAIN, 45)); if (torNotAvailable) { if (TorServiceUtils.isOrbotInstalled(mXmppConnectionService)) { mBuilder.addAction( @@ -1985,7 +2014,7 @@ public class NotificationService { : PendingIntent.FLAG_UPDATE_CURRENT)); } } - mBuilder.setDeleteIntent(createDismissErrorIntent()); + mBuilder.setDeleteIntent(pendingServiceIntent(mXmppConnectionService,XmppConnectionService.ACTION_DISMISS_ERROR_NOTIFICATIONS, 69)); mBuilder.setVisibility(Notification.VISIBILITY_PRIVATE); mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp); mBuilder.setLocalOnly(true); @@ -2080,6 +2109,11 @@ public class NotificationService { lastTime = time; } + public boolean removeMissedCall() { + --numberOfCalls; + return numberOfCalls <= 0; + } + public int getNumberOfCalls() { return numberOfCalls; } diff --git a/src/main/java/eu/siacs/conversations/services/ShortcutService.java b/src/main/java/eu/siacs/conversations/services/ShortcutService.java index 01c7bcad872eab66ef944b3100310f2339574446..017714ca4a9f489c7fd47d7c59952865a5f6798f 100644 --- a/src/main/java/eu/siacs/conversations/services/ShortcutService.java +++ b/src/main/java/eu/siacs/conversations/services/ShortcutService.java @@ -11,6 +11,7 @@ import android.os.Build; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.graphics.drawable.IconCompat; @@ -102,25 +103,16 @@ public class ShortcutService { } public ShortcutInfoCompat getShortcutInfoCompat(final MucOptions mucOptions) { - final ShortcutInfoCompat.Builder builder = - new ShortcutInfoCompat.Builder(xmppConnectionService, getShortcutId(mucOptions)) + return new ShortcutInfoCompat.Builder(xmppConnectionService, getShortcutId(mucOptions)) .setShortLabel(mucOptions.getConversation().getName()) .setIntent(getShortcutIntent(mucOptions)) - .setIsConversation(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - builder.setIcon( - IconCompat.createFromIcon( - xmppConnectionService, - Icon.createWithBitmap( - xmppConnectionService - .getAvatarService() - .getRoundedShortcut(mucOptions)))); - } - return builder.build(); + .setIcon(IconCompat.createWithBitmap(xmppConnectionService.getAvatarService().getRoundedShortcut(mucOptions))) + .setIsConversation() + .build(); } @TargetApi(Build.VERSION_CODES.N_MR1) - private ShortcutInfo getShortcutInfo(Contact contact) { + private ShortcutInfo getShortcutInfo(final Contact contact) { return getShortcutInfoCompat(contact).toShortcutInfo(); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 2ee68d1cfc3b1fb118f24ca81fa531f53a56f4d0..aa41e9ccc961302d0a7cf0b48f7b47c6b5cab58b 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -19,6 +19,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.drawable.AnimatedImageDrawable; @@ -101,6 +102,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; @@ -144,6 +146,7 @@ import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.UnifiedPushDatabase; import eu.siacs.conversations.ui.ChooseAccountForProfilePictureActivity; +import eu.siacs.conversations.ui.ConversationsActivity; import eu.siacs.conversations.ui.RtpSessionActivity; import eu.siacs.conversations.ui.SettingsActivity; import eu.siacs.conversations.ui.UiCallback; @@ -211,7 +214,11 @@ public class XmppConnectionService extends Service { public static final String ACTION_CLEAR_MISSED_CALL_NOTIFICATION = "clear_missed_call_notification"; public static final String ACTION_DISMISS_ERROR_NOTIFICATIONS = "dismiss_error"; public static final String ACTION_TRY_AGAIN = "try_again"; + + public static final String ACTION_TEMPORARILY_DISABLE = "temporarily_disable"; + public static final String ACTION_PING = "ping"; public static final String ACTION_IDLE_PING = "idle_ping"; + public static final String ACTION_INTERNAL_PING = "internal_ping"; public static final String ACTION_FCM_TOKEN_REFRESH = "fcm_token_refresh"; public static final String ACTION_FCM_MESSAGE_RECEIVED = "fcm_message_received"; public static final String ACTION_DISMISS_CALL = "dismiss_call"; @@ -225,6 +232,8 @@ public class XmppConnectionService extends Service { public final CountDownLatch restoredFromDatabaseLatch = new CountDownLatch(1); private final static Executor FILE_OBSERVER_EXECUTOR = Executors.newSingleThreadExecutor(); private final static Executor FILE_ATTACHMENT_EXECUTOR = Executors.newSingleThreadExecutor(); + + private final ScheduledExecutorService internalPingExecutor = Executors.newSingleThreadScheduledExecutor(); private final static SerialSingleThreadExecutor VIDEO_COMPRESSION_EXECUTOR = new SerialSingleThreadExecutor("VideoCompression"); private final SerialSingleThreadExecutor mDatabaseWriterExecutor = new SerialSingleThreadExecutor("DatabaseWriter"); private final SerialSingleThreadExecutor mDatabaseReaderExecutor = new SerialSingleThreadExecutor("DatabaseReader"); @@ -488,9 +497,9 @@ public class XmppConnectionService extends Service { joinMuc(conversation); } scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode()); - } else if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED) { + } else if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED || account.getStatus() == Account.State.LOGGED_OUT) { resetSendingToWaiting(account); - if (account.isEnabled() && isInLowPingTimeoutMode(account)) { + if (account.isConnectionEnabled() && isInLowPingTimeoutMode(account)) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": went into offline state during low ping mode. reconnecting now"); reconnectAccount(account, true, false); } else { @@ -503,7 +512,8 @@ public class XmppConnectionService extends Service { } else if (account.getStatus() != Account.State.CONNECTING && account.getStatus() != Account.State.NO_INTERNET) { resetSendingToWaiting(account); if (connection != null && account.getStatus().isAttemptReconnect()) { - final boolean aggressive = hasJingleRtpConnection(account); + final boolean aggressive = account.getStatus() == Account.State.SEE_OTHER_HOST + || hasJingleRtpConnection(account); final int next = connection.getTimeToNextAttempt(aggressive); final boolean lowPingTimeoutMode = isInLowPingTimeoutMode(account); if (next <= 0) { @@ -513,6 +523,13 @@ public class XmppConnectionService extends Service { final int attempt = connection.getAttempt() + 1; Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": error connecting account. try again in " + next + "s for the " + attempt + " time. lowPingTimeout=" + lowPingTimeoutMode+", aggressive="+aggressive); scheduleWakeUpCall(next, account.getUuid().hashCode()); + if (aggressive) { + internalPingExecutor.schedule( + XmppConnectionService.this::manageAccountConnectionStatesInternal, + (next * 1000L) + 50, + TimeUnit.MILLISECONDS + ); + } } } } @@ -524,6 +541,7 @@ public class XmppConnectionService extends Service { private WakeLock wakeLock; private LruCache mDrawableCache; private final BroadcastReceiver mInternalEventReceiver = new InternalEventReceiver(); + private final BroadcastReceiver mInternalRestrictedEventReceiver = new RestrictedEventReceiver(Arrays.asList(TorServiceUtils.ACTION_STATUS)); private final BroadcastReceiver mInternalScreenEventReceiver = new InternalEventReceiver(); private EmojiSearch emojiSearch = null; @@ -797,241 +815,261 @@ public class XmppConnectionService extends Service { } @Override - public int onStartCommand(Intent intent, int flags, int startId) { - final String action = intent == null ? null : intent.getAction(); + public int onStartCommand(final Intent intent, int flags, int startId) { + final String action = Strings.nullToEmpty(intent == null ? null : intent.getAction()); final boolean needsForegroundService = intent != null && intent.getBooleanExtra(EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE, false); if (needsForegroundService) { Log.d(Config.LOGTAG, "toggle forced foreground service after receiving event (action=" + action + ")"); toggleForegroundService(true); } - String pushedAccountHash = null; - boolean interactive = false; - if (action != null) { - final String uuid = intent.getStringExtra("uuid"); - switch (action) { - case QuickConversationsService.SMS_RETRIEVED_ACTION: - mQuickConversationsService.handleSmsReceived(intent); - break; - case ConnectivityManager.CONNECTIVITY_ACTION: - if (hasInternetConnection()) { - if (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0) { - schedulePostConnectivityChange(); - } - if (Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) { - resetAllAttemptCounts(true, false); - } - Resolver.clearCache(); + final String uuid = intent == null ? null : intent.getStringExtra("uuid"); + switch (action) { + case QuickConversationsService.SMS_RETRIEVED_ACTION: + mQuickConversationsService.handleSmsReceived(intent); + break; + case ConnectivityManager.CONNECTIVITY_ACTION: + if (hasInternetConnection()) { + if (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0) { + schedulePostConnectivityChange(); } - break; - case Intent.ACTION_SHUTDOWN: - logoutAndSave(true); - return START_NOT_STICKY; - case ACTION_CLEAR_MESSAGE_NOTIFICATION: - mNotificationExecutor.execute(() -> { - try { - final Conversation c = findConversationByUuid(uuid); - if (c != null) { - mNotificationService.clearMessages(c); - } else { - mNotificationService.clearMessages(); - } - restoredFromDatabaseLatch.await(); - - } catch (InterruptedException e) { - Log.d(Config.LOGTAG, "unable to process clear message notification"); + if (Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) { + resetAllAttemptCounts(true, false); + } + Resolver.clearCache(); + } + break; + case Intent.ACTION_SHUTDOWN: + logoutAndSave(true); + return START_NOT_STICKY; + case ACTION_CLEAR_MESSAGE_NOTIFICATION: + mNotificationExecutor.execute(() -> { + try { + final Conversation c = findConversationByUuid(uuid); + if (c != null) { + mNotificationService.clearMessages(c); + } else { + mNotificationService.clearMessages(); } - }); - break; - case ACTION_CLEAR_MISSED_CALL_NOTIFICATION: - mNotificationExecutor.execute(() -> { - try { - final Conversation c = findConversationByUuid(uuid); - if (c != null) { - mNotificationService.clearMissedCalls(c); - } else { - mNotificationService.clearMissedCalls(); - } - restoredFromDatabaseLatch.await(); + restoredFromDatabaseLatch.await(); - } catch (InterruptedException e) { - Log.d(Config.LOGTAG, "unable to process clear missed call notification"); + } catch (InterruptedException e) { + Log.d(Config.LOGTAG, "unable to process clear message notification"); + } + }); + break; + case ACTION_CLEAR_MISSED_CALL_NOTIFICATION: + mNotificationExecutor.execute(() -> { + try { + final Conversation c = findConversationByUuid(uuid); + if (c != null) { + mNotificationService.clearMissedCalls(c); + } else { + mNotificationService.clearMissedCalls(); } - }); - break; - case ACTION_DISMISS_CALL: { - final String sessionId = intent.getStringExtra(RtpSessionActivity.EXTRA_SESSION_ID); - Log.d(Config.LOGTAG, "received intent to dismiss call with session id " + sessionId); - mJingleConnectionManager.rejectRtpSession(sessionId); - break; - } - case TorServiceUtils.ACTION_STATUS: - final String status = intent.getStringExtra(TorServiceUtils.EXTRA_STATUS); - //TODO port and host are in 'extras' - but this may not be a reliable source? - if ("ON".equals(status)) { - handleOrbotStartedEvent(); - return START_STICKY; + restoredFromDatabaseLatch.await(); + + } catch (InterruptedException e) { + Log.d(Config.LOGTAG, "unable to process clear missed call notification"); } + }); + break; + case ACTION_DISMISS_CALL: { + if (intent == null) { break; - case ACTION_END_CALL: { - final String sessionId = intent.getStringExtra(RtpSessionActivity.EXTRA_SESSION_ID); - Log.d(Config.LOGTAG, "received intent to end call with session id " + sessionId); - mJingleConnectionManager.endRtpSession(sessionId); } + final String sessionId = intent.getStringExtra(RtpSessionActivity.EXTRA_SESSION_ID); + Log.d(Config.LOGTAG, "received intent to dismiss call with session id " + sessionId); + mJingleConnectionManager.rejectRtpSession(sessionId); break; - case ACTION_PROVISION_ACCOUNT: { - final String address = intent.getStringExtra("address"); - final String password = intent.getStringExtra("password"); - if (QuickConversationsService.isQuicksy() || Strings.isNullOrEmpty(address) || Strings.isNullOrEmpty(password)) { - break; - } - provisionAccount(address, password); + } + case TorServiceUtils.ACTION_STATUS: + final String status = intent == null ? null : intent.getStringExtra(TorServiceUtils.EXTRA_STATUS); + //TODO port and host are in 'extras' - but this may not be a reliable source? + if ("ON".equals(status)) { + handleOrbotStartedEvent(); + return START_STICKY; + } + break; + case ACTION_END_CALL: { + if (intent == null) { break; } - case ACTION_DISMISS_ERROR_NOTIFICATIONS: - dismissErrorNotifications(); + final String sessionId = intent.getStringExtra(RtpSessionActivity.EXTRA_SESSION_ID); + Log.d(Config.LOGTAG, "received intent to end call with session id " + sessionId); + mJingleConnectionManager.endRtpSession(sessionId); + } + break; + case ACTION_PROVISION_ACCOUNT: { + if (intent == null) { break; - case ACTION_TRY_AGAIN: - resetAllAttemptCounts(false, true); - interactive = true; + } + final String address = intent.getStringExtra("address"); + final String password = intent.getStringExtra("password"); + if (QuickConversationsService.isQuicksy() || Strings.isNullOrEmpty(address) || Strings.isNullOrEmpty(password)) { break; - case ACTION_REPLY_TO_CONVERSATION: - Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); - if (remoteInput == null) { - break; - } - final CharSequence body = remoteInput.getCharSequence("text_reply"); - final boolean dismissNotification = intent.getBooleanExtra("dismiss_notification", false); - final String lastMessageUuid = intent.getStringExtra("last_message_uuid"); - if (body == null || body.length() <= 0) { - break; - } - mNotificationExecutor.execute(() -> { - try { - restoredFromDatabaseLatch.await(); - final Conversation c = findConversationByUuid(uuid); - if (c != null) { - directReply(c, body.toString(), lastMessageUuid, dismissNotification); - } - } catch (InterruptedException e) { - Log.d(Config.LOGTAG, "unable to process direct reply"); - } - }); + } + provisionAccount(address, password); + break; + } + case ACTION_DISMISS_ERROR_NOTIFICATIONS: + dismissErrorNotifications(); + break; + case ACTION_TRY_AGAIN: + resetAllAttemptCounts(false, true); + break; + case ACTION_REPLY_TO_CONVERSATION: + final Bundle remoteInput = intent == null ? null : RemoteInput.getResultsFromIntent(intent); + if (remoteInput == null) { break; - case ACTION_MARK_AS_READ: - mNotificationExecutor.execute(() -> { - final Conversation c = findConversationByUuid(uuid); - if (c == null) { - Log.d(Config.LOGTAG, "received mark read intent for unknown conversation (" + uuid + ")"); - return; - } - try { - restoredFromDatabaseLatch.await(); - sendReadMarker(c, null); - } catch (InterruptedException e) { - Log.d(Config.LOGTAG, "unable to process notification read marker for conversation " + c.getName()); - } - - }); + } + final CharSequence body = remoteInput.getCharSequence("text_reply"); + final boolean dismissNotification = intent.getBooleanExtra("dismiss_notification", false); + final String lastMessageUuid = intent.getStringExtra("last_message_uuid"); + if (body == null || body.length() <= 0) { break; - case ACTION_SNOOZE: - mNotificationExecutor.execute(() -> { + } + mNotificationExecutor.execute(() -> { + try { + restoredFromDatabaseLatch.await(); final Conversation c = findConversationByUuid(uuid); - if (c == null) { - Log.d(Config.LOGTAG, "received snooze intent for unknown conversation (" + uuid + ")"); - return; + if (c != null) { + directReply(c, body.toString(), lastMessageUuid, dismissNotification); } - c.setMutedTill(System.currentTimeMillis() + 30 * 60 * 1000); - mNotificationService.clearMessages(c); - updateConversation(c); - }); - case AudioManager.RINGER_MODE_CHANGED_ACTION: - case NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED: - if (dndOnSilentMode()) { - refreshAllPresences(); + } catch (InterruptedException e) { + Log.d(Config.LOGTAG, "unable to process direct reply"); } - break; - case Intent.ACTION_SCREEN_ON: - deactivateGracePeriod(); - case Intent.ACTION_USER_PRESENT: - case Intent.ACTION_SCREEN_OFF: - if (awayWhenScreenLocked()) { - refreshAllPresences(); - } - break; - case ACTION_FCM_TOKEN_REFRESH: - refreshAllFcmTokens(); - break; - case ACTION_RENEW_UNIFIED_PUSH_ENDPOINTS: - final String instance = intent.getStringExtra("instance"); - final String application = intent.getStringExtra("application"); - final Messenger messenger = intent.getParcelableExtra("messenger"); - final UnifiedPushBroker.PushTargetMessenger pushTargetMessenger; - if (messenger != null && application != null && instance != null) { - pushTargetMessenger = new UnifiedPushBroker.PushTargetMessenger(new UnifiedPushDatabase.PushTarget(application, instance),messenger); - Log.d(Config.LOGTAG,"found push target messenger"); - } else { - pushTargetMessenger = null; + }); + break; + case ACTION_MARK_AS_READ: + mNotificationExecutor.execute(() -> { + final Conversation c = findConversationByUuid(uuid); + if (c == null) { + Log.d(Config.LOGTAG, "received mark read intent for unknown conversation (" + uuid + ")"); + return; } - final Optional transport = renewUnifiedPushEndpoints(pushTargetMessenger); - if (instance != null && transport.isPresent()) { - unifiedPushBroker.rebroadcastEndpoint(messenger, instance, transport.get()); + try { + restoredFromDatabaseLatch.await(); + sendReadMarker(c, null); + } catch (InterruptedException e) { + Log.d(Config.LOGTAG, "unable to process notification read marker for conversation " + c.getName()); } - break; - case ACTION_IDLE_PING: - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - scheduleNextIdlePing(); + + }); + break; + case ACTION_SNOOZE: + mNotificationExecutor.execute(() -> { + final Conversation c = findConversationByUuid(uuid); + if (c == null) { + Log.d(Config.LOGTAG, "received snooze intent for unknown conversation (" + uuid + ")"); + return; } + c.setMutedTill(System.currentTimeMillis() + 30 * 60 * 1000); + mNotificationService.clearMessages(c); + updateConversation(c); + }); + case AudioManager.RINGER_MODE_CHANGED_ACTION: + case NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED: + if (dndOnSilentMode()) { + refreshAllPresences(); + } + break; + case Intent.ACTION_SCREEN_ON: + deactivateGracePeriod(); + case Intent.ACTION_USER_PRESENT: + case Intent.ACTION_SCREEN_OFF: + if (awayWhenScreenLocked()) { + refreshAllPresences(); + } + break; + case ACTION_FCM_TOKEN_REFRESH: + refreshAllFcmTokens(); + break; + case ACTION_RENEW_UNIFIED_PUSH_ENDPOINTS: + if (intent == null) { break; - case ACTION_FCM_MESSAGE_RECEIVED: - pushedAccountHash = intent.getStringExtra("account"); - Log.d(Config.LOGTAG, "push message arrived in service. account=" + pushedAccountHash); - break; - case Intent.ACTION_SEND: - Uri uri = intent.getData(); - if (uri != null) { - Log.d(Config.LOGTAG, "received uri permission for " + uri); - } - return START_STICKY; - } - } - synchronized (this) { - WakeLockHelper.acquire(wakeLock); - 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); - for (Account account : accounts) { - final boolean pushWasMeantForThisAccount = CryptoHelper.getAccountFingerprint(account, androidId).equals(pushedAccountHash); - pingNow |= processAccountState(account, - interactive, - "ui".equals(action), - pushWasMeantForThisAccount, - pingCandidates); - } - if (pingNow) { - for (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()); - } - long msToMucPing = (mLastMucPing + (Config.PING_MAX_INTERVAL * 2000L)) - SystemClock.elapsedRealtime(); - if (msToMucPing <= 0) { - mLastMucPing = SystemClock.elapsedRealtime(); - for (Conversation c : getConversations()) { - if (c.getMode() == Conversation.MODE_MULTI && c.getMucOptions().online()) { - mucSelfPingAndRejoin(c); - } - } } - } - WakeLockHelper.release(wakeLock); + final String instance = intent.getStringExtra("instance"); + final String application = intent.getStringExtra("application"); + final Messenger messenger = intent.getParcelableExtra("messenger"); + final UnifiedPushBroker.PushTargetMessenger pushTargetMessenger; + if (messenger != null && application != null && instance != null) { + pushTargetMessenger = new UnifiedPushBroker.PushTargetMessenger(new UnifiedPushDatabase.PushTarget(application, instance),messenger); + Log.d(Config.LOGTAG,"found push target messenger"); + } else { + pushTargetMessenger = null; + } + final Optional transport = renewUnifiedPushEndpoints(pushTargetMessenger); + if (instance != null && transport.isPresent()) { + unifiedPushBroker.rebroadcastEndpoint(messenger, instance, transport.get()); + } + break; + case ACTION_IDLE_PING: + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + scheduleNextIdlePing(); + } + break; + case ACTION_FCM_MESSAGE_RECEIVED: + Log.d(Config.LOGTAG, "push message arrived in service. account"); + break; + case Intent.ACTION_SEND: + final Uri uri = intent == null ? null : intent.getData(); + if (uri != null) { + Log.d(Config.LOGTAG, "received uri permission for " + uri); + } + return START_STICKY; + case ACTION_TEMPORARILY_DISABLE: + toggleSoftDisabled(true); + if (checkListeners()) { + stopSelf(); + } + return START_NOT_STICKY; } + manageAccountConnectionStates(action, intent == null ? null : intent.getExtras()); if (SystemClock.elapsedRealtime() - mLastExpiryRun.get() >= Config.EXPIRY_INTERVAL) { expireOldMessages(); } return START_STICKY; } + private void manageAccountConnectionStatesInternal() { + manageAccountConnectionStates(ACTION_INTERNAL_PING, null); + } + + 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); + WakeLockHelper.acquire(wakeLock); + 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); + for (final Account account : accounts) { + final boolean pushWasMeantForThisAccount = CryptoHelper.getAccountFingerprint(account, androidId).equals(pushedAccountHash); + pingNow |= processAccountState(account, + interactive, + "ui".equals(action), + pushWasMeantForThisAccount, + pingCandidates); + } + if (pingNow) { + for (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()); + } + long msToMucPing = (mLastMucPing + (Config.PING_MAX_INTERVAL * 2000L)) - SystemClock.elapsedRealtime(); + if (msToMucPing <= 0) { + mLastMucPing = SystemClock.elapsedRealtime(); + for (Conversation c : getConversations()) { + if (c.getMode() == Conversation.MODE_MULTI && c.getMucOptions().online()) { + mucSelfPingAndRejoin(c); + } + } + } + } + WakeLockHelper.release(wakeLock); + } + private void handleOrbotStartedEvent() { for (final Account account : accounts) { if (account.getStatus() == Account.State.TOR_NOT_AVAILABLE) { @@ -1041,79 +1079,85 @@ public class XmppConnectionService extends Service { } private boolean processAccountState(Account account, boolean interactive, boolean isUiAction, boolean isAccountPushed, HashSet pingCandidates) { - boolean pingNow = false; - if (account.getStatus().isAttemptReconnect()) { - if (!hasInternetConnection()) { - account.setStatus(Account.State.NO_INTERNET); - if (statusListener != null) { - statusListener.onStatusChanged(account); - } - } else { - if (account.getStatus() == Account.State.NO_INTERNET) { - account.setStatus(Account.State.OFFLINE); - if (statusListener != null) { - statusListener.onStatusChanged(account); - } - } - if (account.getStatus() == Account.State.ONLINE) { - synchronized (mLowPingTimeoutMode) { - long lastReceived = account.getXmppConnection().getLastPacketReceived(); - long lastSent = account.getXmppConnection().getLastPingSent(); - long pingInterval = isUiAction ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000; - long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime(); - int pingTimeout = mLowPingTimeoutMode.contains(account.getJid().asBareJid()) ? Config.LOW_PING_TIMEOUT * 1000 : Config.PING_TIMEOUT * 1000; - long pingTimeoutIn = (lastSent + pingTimeout) - SystemClock.elapsedRealtime(); - if (lastSent > lastReceived) { - if (pingTimeoutIn < 0) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ping timeout"); - this.reconnectAccount(account, true, interactive); - } else { - int secs = (int) (pingTimeoutIn / 1000); - this.scheduleWakeUpCall(secs, account.getUuid().hashCode()); + if (!account.getStatus().isAttemptReconnect()) { + return false; + } + if (!hasInternetConnection()) { + account.setStatus(Account.State.NO_INTERNET); + statusListener.onStatusChanged(account); + } else { + if (account.getStatus() == Account.State.NO_INTERNET) { + account.setStatus(Account.State.OFFLINE); + statusListener.onStatusChanged(account); + } + if (account.getStatus() == Account.State.ONLINE) { + synchronized (mLowPingTimeoutMode) { + long lastReceived = account.getXmppConnection().getLastPacketReceived(); + long lastSent = account.getXmppConnection().getLastPingSent(); + long pingInterval = isUiAction ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000; + long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime(); + int pingTimeout = mLowPingTimeoutMode.contains(account.getJid().asBareJid()) ? Config.LOW_PING_TIMEOUT * 1000 : Config.PING_TIMEOUT * 1000; + long pingTimeoutIn = (lastSent + pingTimeout) - SystemClock.elapsedRealtime(); + if (lastSent > lastReceived) { + if (pingTimeoutIn < 0) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ping timeout"); + this.reconnectAccount(account, true, interactive); + } else { + int secs = (int) (pingTimeoutIn / 1000); + this.scheduleWakeUpCall(secs, account.getUuid().hashCode()); + } + } else { + pingCandidates.add(account); + if (isAccountPushed) { + if (mLowPingTimeoutMode.add(account.getJid().asBareJid())) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": entering low ping timeout mode"); } + return true; + } else if (msToNextPing <= 0) { + return true; } else { - pingCandidates.add(account); - if (isAccountPushed) { - pingNow = true; - if (mLowPingTimeoutMode.add(account.getJid().asBareJid())) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": entering low ping timeout mode"); - } - } else if (msToNextPing <= 0) { - pingNow = true; - } else { - this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode()); - if (mLowPingTimeoutMode.remove(account.getJid().asBareJid())) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": leaving low ping timeout mode"); - } + this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode()); + if (mLowPingTimeoutMode.remove(account.getJid().asBareJid())) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": leaving low ping timeout mode"); } } } - } else if (account.getStatus() == Account.State.OFFLINE) { + } + } else if (account.getStatus() == Account.State.OFFLINE) { + reconnectAccount(account, true, interactive); + } else if (account.getStatus() == Account.State.CONNECTING) { + long secondsSinceLastConnect = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000; + long secondsSinceLastDisco = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastDiscoStarted()) / 1000; + long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco; + long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect; + if (timeout < 0) { + Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting (secondsSinceLast=" + secondsSinceLastConnect + ")"); + account.getXmppConnection().resetAttemptCount(false); reconnectAccount(account, true, interactive); - } else if (account.getStatus() == Account.State.CONNECTING) { - long secondsSinceLastConnect = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000; - long secondsSinceLastDisco = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastDiscoStarted()) / 1000; - long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco; - long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect; - if (timeout < 0) { - Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting (secondsSinceLast=" + secondsSinceLastConnect + ")"); - account.getXmppConnection().resetAttemptCount(false); - reconnectAccount(account, true, interactive); - } else if (discoTimeout < 0) { - account.getXmppConnection().sendDiscoTimeout(); - scheduleWakeUpCall((int) Math.min(timeout, discoTimeout), account.getUuid().hashCode()); - } else { - scheduleWakeUpCall((int) Math.min(timeout, discoTimeout), account.getUuid().hashCode()); - } + } else if (discoTimeout < 0) { + account.getXmppConnection().sendDiscoTimeout(); + scheduleWakeUpCall((int) Math.min(timeout, discoTimeout), account.getUuid().hashCode()); } else { - final boolean aggressive = hasJingleRtpConnection(account); - if (account.getXmppConnection().getTimeToNextAttempt(aggressive) <= 0) { - reconnectAccount(account, true, interactive); - } + scheduleWakeUpCall((int) Math.min(timeout, discoTimeout), account.getUuid().hashCode()); + } + } else { + final boolean aggressive = account.getStatus() == Account.State.SEE_OTHER_HOST || hasJingleRtpConnection(account); + if (account.getXmppConnection().getTimeToNextAttempt(aggressive) <= 0) { + reconnectAccount(account, true, interactive); + } + } + } + return false; + } + + private void toggleSoftDisabled(final boolean softDisabled) { + for(final Account account : this.accounts) { + if (account.isEnabled()) { + if (account.setOption(Account.OPTION_SOFT_DISABLED, softDisabled)) { + updateAccount(account); } } } - return pingNow; } public boolean processUnifiedPushMessage(final Account account, final Jid transport, final Element push) { @@ -1395,7 +1439,7 @@ public class XmppConnectionService extends Service { if (Config.supportOpenPgp()) { this.pgpServiceConnection = new OpenPgpServiceConnection(this, "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() { @Override - public void onBound(IOpenPgpService2 service) { + public void onBound(final IOpenPgpService2 service) { for (Account account : accounts) { final PgpDecryptionService pgp = account.getPgpDecryptionService(); if (pgp != null) { @@ -1405,7 +1449,8 @@ public class XmppConnectionService extends Service { } @Override - public void onError(Exception e) { + public void onError(final Exception exception) { + Log.e(Config.LOGTAG,"could not bind to OpenKeyChain", exception); } }); this.pgpServiceConnection.bindToService(); @@ -1417,20 +1462,31 @@ public class XmppConnectionService extends Service { toggleForegroundService(); updateUnreadCountBadge(); toggleScreenEventReceiver(); - final IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(TorServiceUtils.ACTION_STATUS); + final IntentFilter systemBroadcastFilter = new IntentFilter(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { scheduleNextIdlePing(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - } - intentFilter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED); - } - registerReceiver(this.mInternalEventReceiver, intentFilter); + systemBroadcastFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + } + systemBroadcastFilter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED); + } + ContextCompat.registerReceiver( + this, + this.mInternalEventReceiver, + systemBroadcastFilter, + ContextCompat.RECEIVER_NOT_EXPORTED); + final IntentFilter exportedBroadcastFilter = new IntentFilter(); + exportedBroadcastFilter.addAction(TorServiceUtils.ACTION_STATUS); + ContextCompat.registerReceiver( + this, + this.mInternalRestrictedEventReceiver, + exportedBroadcastFilter, + ContextCompat.RECEIVER_EXPORTED); mForceDuringOnCreate.set(false); toggleForegroundService(); setupPhoneStateListener(); rescanStickers(); + internalPingExecutor.scheduleAtFixedRate(this::manageAccountConnectionStatesInternal,10,10,TimeUnit.SECONDS); } @@ -1497,12 +1553,14 @@ public class XmppConnectionService extends Service { public void onDestroy() { try { unregisterReceiver(this.mInternalEventReceiver); + unregisterReceiver(this.mInternalRestrictedEventReceiver); unregisterReceiver(this.mInternalScreenEventReceiver); } catch (final IllegalArgumentException e) { //ignored } destroyed = false; fileObserver.stopWatching(); + internalPingExecutor.shutdown(); super.onDestroy(); } @@ -1579,9 +1637,27 @@ public class XmppConnectionService extends Service { private void startForegroundOrCatch(final int id, final Notification notification) { try { - startForeground(id, notification); - } catch (final IllegalStateException e) { - Log.e(Config.LOGTAG,"Could not start foreground service", e); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + final int foregroundServiceType; + if (getSystemService(PowerManager.class) + .isIgnoringBatteryOptimizations(getPackageName())) { + foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED; + } else if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) + == PackageManager.PERMISSION_GRANTED) { + foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; + } else if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) + == PackageManager.PERMISSION_GRANTED) { + 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"); + } + startForeground(id, notification, foregroundServiceType); + } else { + startForeground(id, notification); + } + } catch (final IllegalStateException | SecurityException e) { + Log.e(Config.LOGTAG, "Could not start foreground service", e); } } @@ -1602,7 +1678,7 @@ public class XmppConnectionService extends Service { private void logoutAndSave(boolean stop) { int activeAccounts = 0; for (final Account account : accounts) { - if (account.getStatus() != Account.State.DISABLED) { + if (account.isConnectionEnabled()) { databaseBackend.writeRoster(account.getRoster()); activeAccounts++; } @@ -1638,25 +1714,18 @@ public class XmppConnectionService extends Service { } } - public void scheduleWakeUpCall(int seconds, int requestCode) { + public void scheduleWakeUpCall(final int seconds, final int requestCode) { final long timeToWake = SystemClock.elapsedRealtime() + (seconds < 0 ? 1 : seconds + 1) * 1000L; final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); if (alarmManager == null) { return; } final Intent intent = new Intent(this, EventReceiver.class); - intent.setAction("ping"); + intent.setAction(ACTION_PING); try { - final PendingIntent pendingIntent; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - pendingIntent = - PendingIntent.getBroadcast( - this, requestCode, intent, PendingIntent.FLAG_IMMUTABLE); - } else { - pendingIntent = - PendingIntent.getBroadcast( - this, requestCode, intent, 0); - } + final PendingIntent pendingIntent = + PendingIntent.getBroadcast( + this, requestCode, intent, PendingIntent.FLAG_IMMUTABLE); alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake, pendingIntent); } catch (RuntimeException e) { Log.e(Config.LOGTAG, "unable to schedule alarm for ping", e); @@ -3120,6 +3189,7 @@ public class XmppConnectionService extends Service { } private void switchToForeground() { + toggleSoftDisabled(false); final boolean broadcastLastActivity = broadcastLastActivity(); for (Conversation conversation : getConversations()) { if (conversation.getMode() == Conversation.MODE_MULTI) { @@ -3500,8 +3570,8 @@ public class XmppConnectionService extends Service { if (this.accounts == null) { return false; } - for (Account account : this.accounts) { - if (account.isEnabled()) { + for (final Account account : this.accounts) { + if (account.isConnectionEnabled()) { return true; } } @@ -3948,23 +4018,23 @@ public class XmppConnectionService extends Service { }); } - private void disconnect(Account account, boolean force) { - if ((account.getStatus() == Account.State.ONLINE) - || (account.getStatus() == Account.State.DISABLED)) { - final XmppConnection connection = account.getXmppConnection(); - if (!force) { - List conversations = getConversations(); - for (Conversation conversation : conversations) { - if (conversation.getAccount() == account) { - if (conversation.getMode() == Conversation.MODE_MULTI) { - leaveMuc(conversation, true); - } + private void disconnect(final Account account, boolean force) { + final XmppConnection connection = account.getXmppConnection(); + if (connection == null) { + return; + } + if (!force) { + final List conversations = getConversations(); + for (Conversation conversation : conversations) { + if (conversation.getAccount() == account) { + if (conversation.getMode() == Conversation.MODE_MULTI) { + leaveMuc(conversation, true); } } - sendOfflinePresence(account); } - connection.disconnect(force); + sendOfflinePresence(account); } + connection.disconnect(force); } @Override @@ -4485,13 +4555,18 @@ public class XmppConnectionService extends Service { private void reconnectAccount(final Account account, final boolean force, final boolean interactive) { synchronized (account) { - XmppConnection connection = account.getXmppConnection(); - if (connection == null) { + final XmppConnection existingConnection = account.getXmppConnection(); + final XmppConnection connection; + if (existingConnection != null) { + connection = existingConnection; + } else if (account.isConnectionEnabled()) { connection = createConnection(account); account.setXmppConnection(connection); + } else { + return; } - boolean hasInternet = hasInternetConnection(); - if (account.isEnabled() && hasInternet) { + final boolean hasInternet = hasInternetConnection(); + if (account.isConnectionEnabled() && hasInternet) { if (!force) { disconnect(account, false); } @@ -4989,7 +5064,7 @@ public class XmppConnectionService extends Service { public void refreshAllPresences() { boolean includeIdleTimestamp = checkListeners() && broadcastLastActivity(); for (Account account : getAccounts()) { - if (account.isEnabled()) { + if (account.isConnectionEnabled()) { sendPresence(account, includeIdleTimestamp); } } @@ -5532,11 +5607,30 @@ public class XmppConnectionService extends Service { private class InternalEventReceiver extends BroadcastReceiver { @Override - public void onReceive(Context context, Intent intent) { + public void onReceive(final Context context, final Intent intent) { onStartCommand(intent, 0, 0); } } + private class RestrictedEventReceiver extends BroadcastReceiver { + + private final Collection allowedActions; + + private RestrictedEventReceiver(final Collection allowedActions) { + this.allowedActions = allowedActions; + } + + @Override + public void onReceive(final Context context, final Intent intent) { + final String action = intent == null ? null : intent.getAction(); + if (allowedActions.contains(action)) { + onStartCommand(intent,0,0); + } else { + Log.e(Config.LOGTAG,"restricting broadcast of event "+action); + } + } + } + public static class OngoingCall { public final AbstractJingleConnection.Id id; public final Set media; @@ -5562,5 +5656,19 @@ public class XmppConnectionService extends Service { } } + public static void toggleForegroundService(final XmppConnectionService service) { + if (service == null) { + return; + } + service.toggleForegroundService(); + } + + public static void toggleForegroundService(final ConversationsActivity activity) { + if (activity == null) { + return; + } + toggleForegroundService(activity.xmppConnectionService); + } + public static class BlockedMediaException extends Exception { } } diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java index 94f7aa9d7996b48a508f0791dd3c471a49938df9..d6c80b61bb91b3220deb3c122044d49fe9231e64 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java @@ -279,7 +279,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im return; } for (final Account account : xmppConnectionService.getAccounts()) { - if (account.getStatus() != Account.State.DISABLED) { + if (account.isEnabled()) { for (final Contact contact : account.getRoster().getContacts()) { if (contact.showInContactList() && !filterContacts.contains(contact.getJid().asBareJid().toString()) @@ -382,7 +382,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im filterContacts(); this.mActivatedAccounts.clear(); for (Account account : xmppConnectionService.getAccounts()) { - if (account.getStatus() != Account.State.DISABLED) { + if (account.isEnabled()) { if (Config.DOMAIN_LOCK != null) { this.mActivatedAccounts.add(account.getJid().getEscapedLocal()); } else { @@ -402,6 +402,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); ScanActivity.onRequestPermissionResult(this, requestCode, grantResults); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index fe13b1113bda4f805a98a43f29fd90656ba2379c..3d5cb242b93789811b8d95d7265cc78f053810a0 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -153,7 +153,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers builder.setMultiChoiceItems(configuration.names, values, (dialog, which, isChecked) -> values[which] = isChecked); builder.setNegativeButton(R.string.cancel, null); builder.setPositiveButton(R.string.confirm, (dialog, which) -> { - Bundle options = configuration.toBundle(values); + final Bundle options = configuration.toBundle(values); options.putString("muc#roomconfig_persistentroom", "1"); options.putString("{http://prosody.im/protocol/muc}roomconfig_allowmemberinvites", options.getString("muc#roomconfig_allowinvites")); xmppConnectionService.pushConferenceConfiguration(mConversation, diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 7ba00a7295354a5dd4412d947eaa86a149bd7d0f..d05b3093477de909eef056527b39e95e5924c386 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -543,6 +543,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp } boolean skippedInactive = false; boolean showsInactive = false; + boolean showUnverifiedWarning = false; for (final XmppAxolotlSession session : sessions) { final FingerprintStatus trust = session.getTrust(); hasKeys |= !trust.isCompromised(); @@ -558,7 +559,11 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp boolean highlight = session.getFingerprint().equals(messageFingerprint); addFingerprintRow(binding.detailsContactKeys, session, highlight); } + if (trust.isUnverified()) { + showUnverifiedWarning = true; + } } + binding.unverifiedWarning.setVisibility(showUnverifiedWarning ? View.VISIBLE : View.GONE); if (showsInactive || skippedInactive) { binding.showInactiveDevices.setText(showsInactive ? R.string.hide_inactive_devices : R.string.show_inactive_devices); binding.showInactiveDevices.setVisibility(View.VISIBLE); @@ -568,7 +573,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp } else { binding.showInactiveDevices.setVisibility(View.GONE); } - binding.scanButton.setVisibility(hasKeys && isCameraFeatureAvailable() ? View.VISIBLE : View.GONE); + final boolean isCameraFeatureAvailable = isCameraFeatureAvailable(); + binding.scanButton.setVisibility(hasKeys && isCameraFeatureAvailable ? View.VISIBLE : View.GONE); if (hasKeys) { binding.scanButton.setOnClickListener((v) -> ScanActivity.scan(this)); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index f5a2e8ce40e0acabc3b1886d2615b1a718e9305a..db52319a2e037a2f06fb7b02364d2645a78b51bf 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -4,6 +4,8 @@ import static eu.siacs.conversations.ui.XmppActivity.EXTRA_ACCOUNT; import static eu.siacs.conversations.ui.XmppActivity.REQUEST_INVITE_TO_CONVERSATION; import static eu.siacs.conversations.ui.util.SoftKeyboardUtils.hideSoftKeyboard; import static eu.siacs.conversations.utils.PermissionUtils.allGranted; +import static eu.siacs.conversations.utils.PermissionUtils.audioGranted; +import static eu.siacs.conversations.utils.PermissionUtils.cameraGranted; import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied; import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; @@ -187,6 +189,19 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; + public class ConversationFragment extends XmppFragment implements EditMessage.KeyboardListener, MessageAdapter.OnContactPictureLongClicked, @@ -468,6 +483,7 @@ public class ConversationFragment extends XmppFragment public void onClick(View v) { final Account account = conversation == null ? null : conversation.getAccount(); if (account != null) { + account.setOption(Account.OPTION_SOFT_DISABLED, false); account.setOption(Account.OPTION_DISABLED, false); activity.xmppConnectionService.updateAccount(account); } @@ -530,7 +546,8 @@ public class ConversationFragment extends XmppFragment null, 0, 0, - 0); + 0, + Compatibility.pgpStartIntentSenderOptions()); } catch (SendIntentException e) { Toast.makeText( getActivity(), @@ -2075,6 +2092,10 @@ public class ConversationFragment extends XmppFragment .show(); return; } + final Account account = conversation.getAccount(); + if (account.setOption(Account.OPTION_SOFT_DISABLED, false)) { + activity.xmppConnectionService.updateAccount(account); + } final Contact contact = conversation.getContact(); if (RtpCapability.jmiSupport(contact)) { triggerRtpSession(contact.getAccount(), contact.getJid().asBareJid(), action); @@ -2331,6 +2352,9 @@ public class ConversationFragment extends XmppFragment } refresh(); } + if (cameraGranted(grantResults, permissions) || audioGranted(grantResults, permissions)) { + XmppConnectionService.toggleForegroundService(activity); + } } public void startDownloadable(Message message) { @@ -3362,6 +3386,8 @@ public class ConversationFragment extends XmppFragment R.string.this_account_is_disabled, R.string.enable, this.mEnableAccountListener); + } else if (account.getStatus() == Account.State.LOGGED_OUT) { + showSnackbar(R.string.this_account_is_logged_out,R.string.log_in,this.mEnableAccountListener); } else if (conversation.isBlocked()) { showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener); } else if (contact != null @@ -4114,7 +4140,7 @@ public class ConversationFragment extends XmppFragment try { getActivity() .startIntentSenderForResult( - pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0); + pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0, Compatibility.pgpStartIntentSenderOptions()); } catch (final SendIntentException ignored) { } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index ba328d1da16e88a4f4ebc55324aec201b5ddb966..f676e31ad6781b7e8c8ccde738306ae875cf53e6 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -72,8 +72,9 @@ import io.michaelrocks.libphonenumber.android.NumberParseException; import org.openintents.openpgp.util.OpenPgpApi; import java.util.Arrays; -import java.util.List; import java.util.HashSet; +import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -439,14 +440,16 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } } - private void handleActivityResult(ActivityResult activityResult) { + private void handleActivityResult(final ActivityResult activityResult) { if (activityResult.resultCode == Activity.RESULT_OK) { handlePositiveActivityResult(activityResult.requestCode, activityResult.data); } else { handleNegativeActivityResult(activityResult.requestCode); } if (activityResult.requestCode == REQUEST_BATTERY_OP) { + // the result code is always 0 even when battery permission were granted requestNotificationPermissionIfNeeded(); + XmppConnectionService.toggleForegroundService(xmppConnectionService); } } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 9454ed2029e441bf7daf459b464580768ec8fabc..dab84da05ead6515da48abcd1391b401d15ece9d 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -54,6 +54,7 @@ import java.util.concurrent.atomic.AtomicInteger; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.databinding.ActivityEditAccountBinding; import eu.siacs.conversations.databinding.DialogPresenceBinding; @@ -71,6 +72,7 @@ import eu.siacs.conversations.ui.util.AvatarWorkerTask; import eu.siacs.conversations.ui.util.MenuDoubleTabUtil; import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.ui.util.SoftKeyboardUtils; +import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.Resolver; import eu.siacs.conversations.utils.SignupUtils; @@ -161,7 +163,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat if (mInitMode && mAccount != null) { mAccount.setOption(Account.OPTION_DISABLED, false); } - if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited) { + if (mAccount != null && Arrays.asList(Account.State.DISABLED, Account.State.LOGGED_OUT).contains(mAccount.getStatus()) && !accountInfoEdited) { + mAccount.setOption(Account.OPTION_SOFT_DISABLED, false); mAccount.setOption(Account.OPTION_DISABLED, false); if (!xmppConnectionService.updateAccount(mAccount)) { Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show(); @@ -481,6 +484,10 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat if (requestCode == REQUEST_BATTERY_OP || requestCode == REQUEST_DATA_SAVER) { updateAccountInformation(mAccount == null); } + if (requestCode == REQUEST_BATTERY_OP) { + // the result code is always 0 even when battery permission were granted + XmppConnectionService.toggleForegroundService(xmppConnectionService); + } if (requestCode == REQUEST_CHANGE_STATUS) { PresenceTemplate template = mPendingPresenceTemplate.pop(); if (template != null && resultCode == Activity.RESULT_OK) { @@ -652,6 +659,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat intent.putExtra("suffix", ":" + mAccount.getUuid()); startActivity(intent); }); + this.binding.scanButton.setOnClickListener((v) -> ScanActivity.scan(this)); } private void onEditYourNameClicked(View view) { @@ -1021,7 +1029,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat public void userInputRequired(PendingIntent pi, String object) { mPendingPresenceTemplate.push(template); try { - startIntentSenderForResult(pi.getIntentSender(), REQUEST_CHANGE_STATUS, null, 0, 0, 0); + startIntentSenderForResult(pi.getIntentSender(), REQUEST_CHANGE_STATUS, null, 0, 0, 0, Compatibility.pgpStartIntentSenderOptions()); } catch (final IntentSender.SendIntentException ignored) { } } @@ -1225,19 +1233,24 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint); } this.binding.axolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2))); - this.binding.actionCopyAxolotlToClipboard.setVisibility(View.VISIBLE); - this.binding.actionCopyAxolotlToClipboard.setOnClickListener(v -> copyOmemoFingerprint(ownAxolotlFingerprint)); + this.binding.showQrCodeButton.setVisibility(View.VISIBLE); + this.binding.showQrCodeButton.setOnClickListener(v -> showQrCode()); } else { this.binding.axolotlFingerprintBox.setVisibility(View.GONE); } boolean hasKeys = false; + boolean showUnverifiedWarning = false; binding.otherDeviceKeys.removeAllViews(); - for (XmppAxolotlSession session : mAccount.getAxolotlService().findOwnSessions()) { - if (!session.getTrust().isCompromised()) { + for (final XmppAxolotlSession session : mAccount.getAxolotlService().findOwnSessions()) { + final FingerprintStatus trust = session.getTrust(); + if (!trust.isCompromised()) { boolean highlight = session.getFingerprint().equals(messageFingerprint); addFingerprintRow(binding.otherDeviceKeys, session, highlight); hasKeys = true; } + if (trust.isUnverified()) { + showUnverifiedWarning = true; + } } if (hasKeys && Config.supportOmemo()) { //TODO: either the button should be visible if we print an active device or the device list should be fed with reactived devices this.binding.otherDeviceKeysCard.setVisibility(View.VISIBLE); @@ -1247,6 +1260,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } else { binding.clearDevices.setVisibility(View.VISIBLE); } + binding.unverifiedWarning.setVisibility(showUnverifiedWarning ? View.VISIBLE : View.GONE); + binding.scanButton.setVisibility(showUnverifiedWarning ? View.VISIBLE : View.GONE); } else { this.binding.otherDeviceKeysCard.setVisibility(View.GONE); } diff --git a/src/main/java/eu/siacs/conversations/ui/MucUsersActivity.java b/src/main/java/eu/siacs/conversations/ui/MucUsersActivity.java index dfa5e4292b67682d0b7a470b250fca08787c2311..e759ee18efbf7a8260d1727d7f625b81c75bc5b0 100644 --- a/src/main/java/eu/siacs/conversations/ui/MucUsersActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/MucUsersActivity.java @@ -13,8 +13,13 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import com.google.common.collect.Ordering; + import java.util.ArrayList; import java.util.Collections; import java.util.Locale; @@ -56,30 +61,38 @@ public class MucUsersActivity extends XmppActivity implements XmppConnectionServ private void loadAndSubmitUsers() { if (mConversation != null) { allUsers = mConversation.getMucOptions().getUsers(); - Collections.sort(allUsers); submitFilteredList(mSearchEditText != null ? mSearchEditText.getText().toString() : null); } } - private void submitFilteredList(String search) { + private void submitFilteredList(final String search) { if (TextUtils.isEmpty(search)) { - userAdapter.submitList(allUsers); + userAdapter.submitList(Ordering.natural().immutableSortedCopy(allUsers)); } else { final String needle = search.toLowerCase(Locale.getDefault()); - ArrayList filtered = new ArrayList<>(); - for(MucOptions.User user : allUsers) { - final String name = user.getNick(); - final Contact contact = user.getContact(); - if (name != null && name.toLowerCase(Locale.getDefault()).contains(needle) || contact != null && contact.getDisplayName().toLowerCase(Locale.getDefault()).contains(needle)) { - filtered.add(user); - } - } - userAdapter.submitList(filtered); + userAdapter.submitList( + Ordering.natural() + .immutableSortedCopy( + Collections2.filter( + this.allUsers, + user -> { + final String name = user.getName(); + final Contact contact = user.getContact(); + return name != null + && name.toLowerCase( + Locale.getDefault()) + .contains(needle) + || contact != null + && contact.getDisplayName() + .toLowerCase( + Locale.getDefault()) + .contains(needle); + }))); } } @Override - public boolean onContextItemSelected(MenuItem item) { + public boolean onContextItemSelected(@NonNull MenuItem item) { if (!MucDetailsContextMenuHelper.onContextItemSelected(item, userAdapter.getSelectedUser(), this)) { return super.onContextItemSelected(item); } diff --git a/src/main/java/eu/siacs/conversations/ui/OmemoActivity.java b/src/main/java/eu/siacs/conversations/ui/OmemoActivity.java index 89fdae33364893c98490da69533ff519ac4e18b4..44af0d0b237d3234c6ff45e4e601f3641719acdc 100644 --- a/src/main/java/eu/siacs/conversations/ui/OmemoActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/OmemoActivity.java @@ -205,6 +205,7 @@ public abstract class OmemoActivity extends XmppActivity { @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); ScanActivity.onRequestPermissionResult(this, requestCode, grantResults); } } diff --git a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java index 1624295a2ce35a7bd3e88aeb15da9ae074b65325..df3dabdceb0898d1f456c2b3220c55ebf8a1ab42 100644 --- a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java @@ -98,18 +98,20 @@ public class RecordingActivity extends Activity implements View.OnClickListener private boolean startRecording() { mRecorder = new MediaRecorder(); mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); - if (Build.VERSION.SDK_INT >= 29) { - mRecorder.setOutputFormat(MediaRecorder.OutputFormat.OGG); + final int outputFormat; + if (Config.USE_OPUS_VOICE_MESSAGES && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + outputFormat = MediaRecorder.OutputFormat.OGG; + mRecorder.setOutputFormat(outputFormat); mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS); - mRecorder.setAudioEncodingBitRate(24000); - mRecorder.setAudioSamplingRate(48000); + mRecorder.setAudioEncodingBitRate(32000); } else { - mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); + outputFormat = MediaRecorder.OutputFormat.MPEG_4; + mRecorder.setOutputFormat(outputFormat); mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mRecorder.setAudioEncodingBitRate(96000); mRecorder.setAudioSamplingRate(22050); } - setupOutputFile(); + setupOutputFile(outputFormat); mRecorder.setOutputFile(mOutputFile.getAbsolutePath()); try { @@ -117,10 +119,10 @@ public class RecordingActivity extends Activity implements View.OnClickListener mRecorder.start(); mStartTime = SystemClock.elapsedRealtime(); mHandler.postDelayed(mTickExecutor, 100); - Log.d("Voice Recorder", "started recording to " + mOutputFile.getAbsolutePath()); + Log.d(Config.LOGTAG, "started recording to " + mOutputFile.getAbsolutePath()); return true; } catch (Exception e) { - Log.e("Voice Recorder", "prepare() failed " + e.getMessage()); + Log.e(Config.LOGTAG, "prepare() failed ", e); return false; } } @@ -182,14 +184,18 @@ public class RecordingActivity extends Activity implements View.OnClickListener } } - private File generateOutputFilename() { + private File generateOutputFilename(final int outputFormat) { final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); - final String filename; - if (Build.VERSION.SDK_INT >= 29) { - filename = "RECORDING_" + dateFormat.format(new Date()) + ".opus"; + final String extension; + if (outputFormat == MediaRecorder.OutputFormat.MPEG_4) { + extension = "m4a"; + } else if (outputFormat == MediaRecorder.OutputFormat.OGG) { + extension = "oga"; } else { - filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a"; + throw new IllegalStateException("Unrecognized output format"); } + final String filename = + String.format("RECORDING_%s.%s", dateFormat.format(new Date()), extension); final File parentDirectory; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { parentDirectory = @@ -202,8 +208,8 @@ public class RecordingActivity extends Activity implements View.OnClickListener return new File(conversationsDirectory, filename); } - private void setupOutputFile() { - mOutputFile = generateOutputFilename(); + private void setupOutputFile(final int outputFormat) { + mOutputFile = generateOutputFilename(outputFormat); final File parentDirectory = mOutputFile.getParentFile(); if (Objects.requireNonNull(parentDirectory).mkdirs()) { Log.d(Config.LOGTAG, "created " + parentDirectory.getAbsolutePath()); diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java index 01db1e9982c995c00d16bdf436067457e86c88d9..92953da6f66a468d23f4041803fab56298e12231 100644 --- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java @@ -173,6 +173,7 @@ public class SettingsActivity extends XmppActivity implements OnSharedPreference changeOmemoSettingSummary(); if (QuickConversationsService.isQuicksy() + || QuickConversationsService.isPlayStoreFlavor() || Strings.isNullOrEmpty(Config.CHANNEL_DISCOVERY)) { final PreferenceCategory groupChats = (PreferenceCategory) mSettingsFragment.findPreference("group_chats"); diff --git a/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java b/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java index 33164c95c3ce99e26fa270b82f749ba6eaf3f544..ff935beae5456ff9bd18383869eac4d612b68d94 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java @@ -61,7 +61,7 @@ public class ShortcutActivity extends AbstractSearchableListItemActivity { return; } for (final Account account : xmppConnectionService.getAccounts()) { - if (account.getStatus() != Account.State.DISABLED) { + if (account.isEnabled()) { for (final Contact contact : account.getRoster().getContacts()) { if (contact.showInContactList() && contact.match(this, needle)) { diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 8063f4c52652407b4dee400708898f407511ff75..e743dfd629d08bb78d6bbc7d26644356bae2d846 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -345,7 +345,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } switch (actionItem.getId()) { case R.id.discover_public_channels: - startActivity(new Intent(this, ChannelDiscoveryActivity.class)); + if (QuickConversationsService.isPlayStoreFlavor()) { + throw new IllegalStateException("Channel discovery is not available on Google Play flavor"); + } else { + startActivity(new Intent(this, ChannelDiscoveryActivity.class)); + } break; case R.id.create_private_group_chat: showCreatePrivateGroupChatDialog(); @@ -368,6 +372,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne final Menu menu = popupMenu.getMenu(); for (int i = 0; i < menu.size(); i++) { final MenuItem menuItem = menu.getItem(i); + if (QuickConversationsService.isPlayStoreFlavor() && menuItem.getItemId() == R.id.discover_public_channels) { + continue; + } final SpeedDialActionItem actionItem = new SpeedDialActionItem.Builder(menuItem.getItemId(), menuItem.getIcon()) .setLabel(menuItem.getTitle() != null ? menuItem.getTitle().toString() : null) .setFabImageTintColor(ContextCompat.getColor(this, R.color.white)) @@ -881,6 +888,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults.length > 0) if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { ScanActivity.onRequestPermissionResult(this, requestCode, grantResults); @@ -1091,8 +1099,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne ArrayList tags = new ArrayList<>(); final List accounts = xmppConnectionService.getAccounts(); boolean foundSopranica = false; - for (Account account : accounts) { - if (account.getStatus() != Account.State.DISABLED) { + for (final Account account : accounts) { + if (account.isEnabled()) { for (Contact contact : account.getRoster().getContacts()) { Presence.Status s = contact.getShownStatus(); if (contact.showInContactList() && contact.match(this, needle) @@ -1152,7 +1160,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne protected void filterConferences(String needle) { this.conferences.clear(); for (final Account account : xmppConnectionService.getAccounts()) { - if (account.getStatus() != Account.State.DISABLED) { + if (account.isEnabled()) { for (final Bookmark bookmark : account.getBookmarks()) { if (bookmark.match(this, needle)) { this.conferences.add(bookmark); diff --git a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java index 76db02b575265559f317598f8ad67f42c5e18932..a683bedd40990aace80d14dceff5b9f6d81e7ed3 100644 --- a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java @@ -38,12 +38,20 @@ import eu.siacs.conversations.utils.ProvisioningUtils; import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; + import okhttp3.Call; import okhttp3.Callback; import okhttp3.HttpUrl; import okhttp3.Request; import okhttp3.Response; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class UriHandlerActivity extends AppCompatActivity { public static final String ACTION_SCAN_QR_CODE = "scan_qr_code"; @@ -62,7 +70,9 @@ public class UriHandlerActivity extends AppCompatActivity { } public static void scan(final Activity activity, final boolean provisioning) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M + || ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) + == PackageManager.PERMISSION_GRANTED) { final Intent intent = new Intent(activity, UriHandlerActivity.class); intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE); if (provisioning) { @@ -72,14 +82,17 @@ public class UriHandlerActivity extends AppCompatActivity { activity.startActivity(intent); } else { activity.requestPermissions( - new String[]{Manifest.permission.CAMERA}, - provisioning ? REQUEST_CAMERA_PERMISSIONS_TO_SCAN_AND_PROVISION : REQUEST_CAMERA_PERMISSIONS_TO_SCAN - ); + new String[] {Manifest.permission.CAMERA}, + provisioning + ? REQUEST_CAMERA_PERMISSIONS_TO_SCAN_AND_PROVISION + : REQUEST_CAMERA_PERMISSIONS_TO_SCAN); } } - public static void onRequestPermissionResult(Activity activity, int requestCode, int[] grantResults) { - if (requestCode != REQUEST_CAMERA_PERMISSIONS_TO_SCAN && requestCode != REQUEST_CAMERA_PERMISSIONS_TO_SCAN_AND_PROVISION) { + public static void onRequestPermissionResult( + Activity activity, int requestCode, int[] grantResults) { + if (requestCode != REQUEST_CAMERA_PERMISSIONS_TO_SCAN + && requestCode != REQUEST_CAMERA_PERMISSIONS_TO_SCAN_AND_PROVISION) { return; } if (grantResults.length > 0) { @@ -90,7 +103,11 @@ public class UriHandlerActivity extends AppCompatActivity { scan(activity); } } else { - Toast.makeText(activity, R.string.qr_code_scanner_needs_access_to_camera, Toast.LENGTH_SHORT).show(); + Toast.makeText( + activity, + R.string.qr_code_scanner_needs_access_to_camera, + Toast.LENGTH_SHORT) + .show(); } } } @@ -141,7 +158,7 @@ public class UriHandlerActivity extends AppCompatActivity { private boolean handleUri(final Uri uri, final boolean scanned) { final Intent intent; final XmppUri xmppUri = new XmppUri(uri); - final List accounts = DatabaseBackend.getInstance(this).getAccountJids(true); + final List accounts = DatabaseBackend.getInstance(this).getAccountJids(false); if (uri.getScheme().equals("sgnl")) { stickers = Uri.parse("https://stickers.cheogram.com/signal/" + uri.getQueryParameter("pack_id") + "," + uri.getQueryParameter("pack_key")); @@ -170,7 +187,12 @@ public class UriHandlerActivity extends AppCompatActivity { startActivity(intent); return true; } - if (accounts.size() == 0 && xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter(XmppUri.PARAMETER_IBR))) { + if (accounts.size() == 0 + && xmppUri.isAction(XmppUri.ACTION_ROSTER) + && "y" + .equalsIgnoreCase( + Strings.nullToEmpty(xmppUri.getParameter(XmppUri.PARAMETER_IBR)) + .trim())) { intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preAuth); intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); startActivity(intent); @@ -236,29 +258,28 @@ public class UriHandlerActivity extends AppCompatActivity { private void checkForLinkHeader(final HttpUrl url) { Log.d(Config.LOGTAG, "checking for link header on " + url); - this.call = HttpConnectionManager.OK_HTTP_CLIENT.newCall(new Request.Builder() - .url(url) - .head() - .build()); - this.call.enqueue(new Callback() { - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - Log.d(Config.LOGTAG, "unable to check HTTP url", e); - showError(R.string.no_xmpp_adddress_found); - } - - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) { - if (response.isSuccessful()) { - final String linkHeader = response.header("Link"); - if (linkHeader != null && processLinkHeader(linkHeader)) { - return; + this.call = + HttpConnectionManager.OK_HTTP_CLIENT.newCall( + new Request.Builder().url(url).head().build()); + this.call.enqueue( + new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + Log.d(Config.LOGTAG, "unable to check HTTP url", e); + showError(R.string.no_xmpp_adddress_found); } - } - showError(R.string.no_xmpp_adddress_found); - } - }); + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + if (response.isSuccessful()) { + final String linkHeader = response.header("Link"); + if (linkHeader != null && processLinkHeader(linkHeader)) { + return; + } + } + showError(R.string.no_xmpp_adddress_found); + } + }); } private boolean processLinkHeader(final String header) { @@ -295,7 +316,8 @@ public class UriHandlerActivity extends AppCompatActivity { } switch (action) { case Intent.ACTION_MAIN: - binding.progress.setVisibility(call != null && !call.isCanceled() ? View.VISIBLE : View.INVISIBLE); + binding.progress.setVisibility( + call != null && !call.isCanceled() ? View.VISIBLE : View.INVISIBLE); break; case Intent.ACTION_VIEW: case Intent.ACTION_SENDTO: @@ -319,7 +341,8 @@ public class UriHandlerActivity extends AppCompatActivity { private boolean allowProvisioning() { final Intent launchIntent = getIntent(); - return launchIntent != null && launchIntent.getBooleanExtra(EXTRA_ALLOW_PROVISIONING, false); + return launchIntent != null + && launchIntent.getBooleanExtra(EXTRA_ALLOW_PROVISIONING, false); } @Override @@ -342,13 +365,17 @@ public class UriHandlerActivity extends AppCompatActivity { showError(R.string.no_xmpp_adddress_found); } return; - } else if (QuickConversationsService.isConversations() && looksLikeJsonObject(result) && allowProvisioning) { + } else if (QuickConversationsService.isConversations() + && looksLikeJsonObject(result) + && allowProvisioning) { ProvisioningUtils.provision(this, result); finish(); return; } final Uri uri = Uri.parse(result.trim()); - if (allowProvisioning && "https".equalsIgnoreCase(uri.getScheme()) && !XmppUri.INVITE_DOMAIN.equalsIgnoreCase(uri.getHost())) { + if (allowProvisioning + && "https".equalsIgnoreCase(uri.getScheme()) + && !XmppUri.INVITE_DOMAIN.equalsIgnoreCase(uri.getHost())) { final HttpUrl httpUrl = HttpUrl.parse(uri.toString()); if (httpUrl != null) { checkForLinkHeader(httpUrl); diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 36697e9844b23af82bd437c4b66c5bec937593fa..3234b0058ea1f8a1b1bd7af9e09fa4726501458f 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -53,7 +53,6 @@ import android.widget.Toast; import androidx.annotation.BoolRes; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog.Builder; @@ -87,17 +86,23 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder; import eu.siacs.conversations.ui.util.MenuDoubleTabUtil; import eu.siacs.conversations.ui.util.PresenceSelector; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.ui.util.SoftKeyboardUtils; import eu.siacs.conversations.utils.AccountUtils; import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.ExceptionHelper; -import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.ThemeHelper; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.RejectedExecutionException; + public abstract class XmppActivity extends ActionBarActivity { public static final String EXTRA_ACCOUNT = "account"; @@ -327,22 +332,24 @@ public abstract class XmppActivity extends ActionBarActivity { button.setText(R.string.please_wait); button.setEnabled(false); xmppConnectionService.unregisterAccount(account, result -> { - if (result) { - dialog.dismiss(); - if (postDelete != null) { - postDelete.run(); - } - if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) { - final Intent intent = SignupUtils.getSignUpIntent(this); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(intent); + runOnUiThread(()->{ + if (result) { + dialog.dismiss(); + if (postDelete != null) { + postDelete.run(); + } + if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) { + final Intent intent = SignupUtils.getSignUpIntent(this); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + } + } else { + deleteFromServer.setEnabled(true); + button.setText(R.string.delete); + button.setEnabled(true); + Toast.makeText(this,R.string.could_not_delete_account_from_server,Toast.LENGTH_LONG).show(); } - } else { - deleteFromServer.setEnabled(true); - button.setText(R.string.delete); - button.setEnabled(true); - Toast.makeText(this,R.string.could_not_delete_account_from_server,Toast.LENGTH_LONG).show(); - } + }); }); } else { Toast.makeText(this,R.string.not_connected_try_again,Toast.LENGTH_LONG).show(); @@ -671,9 +678,9 @@ public abstract class XmppActivity extends ActionBarActivity { xmppConnectionService.getPgpEngine().generateSignature(intent, account, status, new UiCallback() { @Override - public void userInputRequired(PendingIntent pi, String signature) { + public void userInputRequired(final PendingIntent pi, final String signature) { try { - startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); + startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0,Compatibility.pgpStartIntentSenderOptions()); } catch (final SendIntentException ignored) { } } @@ -734,7 +741,7 @@ public abstract class XmppActivity extends ActionBarActivity { public void userInputRequired(PendingIntent pi, Account object) { try { startIntentSenderForResult(pi.getIntentSender(), - REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0); + REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0, Compatibility.pgpStartIntentSenderOptions()); } catch (final SendIntentException ignored) { } } @@ -938,8 +945,9 @@ public abstract class XmppActivity extends ActionBarActivity { try { startIntentSenderForResult( pgp.getIntentForKey(keyId).getIntentSender(), 0, null, 0, - 0, 0); - } catch (Throwable e) { + 0, 0, Compatibility.pgpStartIntentSenderOptions()); + } catch (final Throwable e) { + Log.d(Config.LOGTAG,"could not launch OpenKeyChain", e); Toast.makeText(XmppActivity.this, R.string.openpgp_error, Toast.LENGTH_SHORT).show(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java index 412ee0e37d05a21b40d984cea4dbec7fa817a0a7..21d1e9f0f116c962a7acdb11f580f08b0f959ba5 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -61,6 +61,7 @@ public class AccountAdapter extends ArrayAdapter { viewHolder.binding.accountStatus.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorOnline)); break; case DISABLED: + case LOGGED_OUT: case CONNECTING: viewHolder.binding.accountStatus.setTextColor(StyledAttributes.getColor(activity, android.R.attr.textColorSecondary)); break; diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java index 2733e7b8bd6a69513f118bf692b95b9ab57bbf44..2683876c7d12ca57f392a69d6fade2211764c00d 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java @@ -74,6 +74,8 @@ public class MediaAdapter extends RecyclerView.Adapter implements View.OnCreateContextMenuListener { @@ -109,7 +110,7 @@ public class UserAdapter extends ListAdapter getEnabledAccounts(final XmppConnectionService service) { final ArrayList accounts = new ArrayList<>(); for (final Account account : service.getAccounts()) { - if (account.getStatus() != Account.State.DISABLED) { + if (account.isEnabled()) { if (Config.DOMAIN_LOCK != null) { accounts.add(account.getJid().getEscapedLocal()); } else { diff --git a/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java b/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java index 54387a8eef71cb3ae232ab7eded3e71383698114..3b536c27a89c5f874ae7181ea9cd20b10dc860fb 100644 --- a/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java +++ b/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.utils; +import androidx.annotation.NonNull; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -8,7 +10,7 @@ import eu.siacs.conversations.xmpp.Jid; public class BackupFileHeader { - private static final int VERSION = 1; + private static final int VERSION = 2; private final String app; private final Jid jid; @@ -17,6 +19,7 @@ public class BackupFileHeader { private final byte[] salt; + @NonNull @Override public String toString() { return "BackupFileHeader{" + @@ -47,17 +50,19 @@ public class BackupFileHeader { public static BackupFileHeader read(DataInputStream inputStream) throws IOException { final int version = inputStream.readInt(); - if (version > VERSION) { - throw new IllegalArgumentException("Backup File version was " + version + " but app only supports up to version " + VERSION); - } - String app = inputStream.readUTF(); - String jid = inputStream.readUTF(); + final String app = inputStream.readUTF(); + final String jid = inputStream.readUTF(); long timestamp = inputStream.readLong(); - byte[] iv = new byte[12]; + final byte[] iv = new byte[12]; inputStream.readFully(iv); - byte[] salt = new byte[16]; + final byte[] salt = new byte[16]; inputStream.readFully(salt); - + if (version < VERSION) { + throw new OutdatedBackupFileVersion(); + } + if (version != VERSION) { + throw new IllegalArgumentException("Backup File version was " + version + " but app only supports version " + VERSION); + } return new BackupFileHeader(app, Jid.of(jid), timestamp, iv, salt); } @@ -81,4 +86,8 @@ public class BackupFileHeader { public long getTimestamp() { return timestamp; } + + public static class OutdatedBackupFileVersion extends RuntimeException { + + } } diff --git a/src/main/java/eu/siacs/conversations/utils/Compatibility.java b/src/main/java/eu/siacs/conversations/utils/Compatibility.java index 4853928423c9e35515f74270ac847033d32df5f5..64f730eb4955c476fa35f8becff07a1b263319ad 100644 --- a/src/main/java/eu/siacs/conversations/utils/Compatibility.java +++ b/src/main/java/eu/siacs/conversations/utils/Compatibility.java @@ -3,6 +3,7 @@ package eu.siacs.conversations.utils; import static eu.siacs.conversations.services.EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE; import android.annotation.SuppressLint; +import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -10,6 +11,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.os.Build; +import android.os.Bundle; import android.preference.Preference; import android.preference.PreferenceCategory; import android.preference.PreferenceManager; @@ -20,15 +22,15 @@ import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.core.content.ContextCompat; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.ui.SettingsActivity; import eu.siacs.conversations.ui.SettingsFragment; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + public class Compatibility { private static final List UNUSED_SETTINGS_POST_TWENTYSIX = @@ -41,7 +43,8 @@ public class Compatibility { Collections.singletonList("message_notification_settings"); public static boolean hasStoragePermission(final Context context) { - return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + return Build.VERSION.SDK_INT < Build.VERSION_CODES.M + || Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU || ContextCompat.checkSelfPermission( context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; @@ -189,4 +192,15 @@ public class Compatibility { return ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED; } } + + public static Bundle pgpStartIntentSenderOptions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + return ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + .toBundle(); + } else { + return null; + } + } } diff --git a/src/main/java/eu/siacs/conversations/utils/FileUtils.java b/src/main/java/eu/siacs/conversations/utils/FileUtils.java index 43e3da11876544f34d23443f56c17618f1f4a0e2..e439ab030c8a8b5e72239f79b004415e9cc828e6 100644 --- a/src/main/java/eu/siacs/conversations/utils/FileUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/FileUtils.java @@ -34,10 +34,8 @@ public class FileUtils { return null; } - final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; - // DocumentProvider - if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + if (DocumentsContract.isDocumentUri(context, uri)) { // ExternalStorageProvider if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); diff --git a/src/main/java/eu/siacs/conversations/utils/IP.java b/src/main/java/eu/siacs/conversations/utils/IP.java index a7182e207d670bee290439231fd703c872550e7a..948f7537ad8f6af1dc90d71691b21b964548f938 100644 --- a/src/main/java/eu/siacs/conversations/utils/IP.java +++ b/src/main/java/eu/siacs/conversations/utils/IP.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.utils; +import com.google.common.net.InetAddresses; + import java.util.regex.Pattern; public class IP { @@ -27,4 +29,14 @@ public class IP { } } + public static String unwrapIPv6(final String host) { + if (host.length() > 2 && host.charAt(0) == '[' && host.charAt(host.length() - 1) == ']') { + final String ip = host.substring(1,host.length() -1); + if (InetAddresses.isInetAddress(ip)) { + return ip; + } + } + return host; + } + } diff --git a/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java b/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java index 1c94225b1262ba374078b9595754e649d8b491c0..04573ef4ca22162bdf8fd8e7a8207861f33d04de 100644 --- a/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java +++ b/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java @@ -60,7 +60,7 @@ public class IrregularUnicodeDetector { private static final Map NORMALIZATION_MAP; private static final LruCache CACHE = new LruCache<>(4096); - private static final List AMBIGUOUS_CYRILLIC = Arrays.asList("а","г","е","ѕ","і","ј","ԛ","о","р","с","у","х"); + private static final List AMBIGUOUS_CYRILLIC = Arrays.asList("а","г","е","ѕ","і","ј","ķ","ԛ","о","р","с","у","х"); static { Map temp = new HashMap<>(); diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java index 06ba3906573deff3d105906a1e130f069be4c214..194ca9cef389d725f514c273c2fcbd02c2fe48e1 100644 --- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java @@ -28,6 +28,8 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -262,6 +264,7 @@ public final class MimeUtils { add("audio/mpeg", "mpega"); add("audio/mpeg", "mp2"); add("audio/mp4", "m4a"); + add("audio/x-m4b", "m4b"); add("audio/mpegurl", "m3u"); add("audio/ogg", "oga"); add("audio/ogg; codecs=opus", "opus"); // opus in ogg container @@ -416,6 +419,9 @@ public final class MimeUtils { applyOverrides(); } + // mime types that are more reliant by path + private static final Collection PATH_PRECEDENCE_MIME_TYPE = Arrays.asList("audio/x-m4b"); + private static void add(String mimeType, String extension) { // If we have an existing x -> y mapping, we do not want to // override it with another mapping x -> y2. @@ -540,43 +546,49 @@ public final class MimeUtils { } public static String guessMimeTypeFromUriAndMime(final Context context, final Uri uri, final String mime) { - Log.d(Config.LOGTAG, "guessMimeTypeFromUriAndMime " + uri + " and mime=" + mime); - final String guess = guessMimeTypeFromUri(context, uri); - if (guess != null) { - return guess; + Log.d(Config.LOGTAG, "guessMimeTypeFromUriAndMime(" + uri + "," + mime+")"); + final String mimeFromUri = guessMimeTypeFromUri(context, uri); + Log.d(Config.LOGTAG,"mimeFromUri:"+mimeFromUri); + if (PATH_PRECEDENCE_MIME_TYPE.contains(mimeFromUri)) { + return mimeFromUri; + } else if (mime == null || mime.equals("application/octet-stream")) { + return mimeFromUri; } else { return mime; } } - public static String guessMimeTypeFromUri(Context context, Uri uri) { - // try the content resolver - String mimeType; - try { - mimeType = context.getContentResolver().getType(uri); - } catch (final Throwable throwable) { - mimeType = null; + public static String guessMimeTypeFromUri(final Context context, final Uri uri) { + final String mimeTypeContentResolver = guessFromContentResolver(context, uri); + final String mimeTypeFromQueryParameter = uri.getQueryParameter("mimeType"); + final String name = "content".equals(uri.getScheme()) ? getDisplayName(context, uri) : null; + final String mimeTypeFromName = Strings.isNullOrEmpty(name) ? null : guessFromPath(name); + final String path = uri.getPath(); + final String mimeTypeFromPath = Strings.isNullOrEmpty(path) ? null : guessFromPath(path); + if (PATH_PRECEDENCE_MIME_TYPE.contains(mimeTypeFromName)) { + return mimeTypeFromName; } - // try the extension - if (mimeType == null || mimeType.equals("application/octet-stream")) { - final String path = uri.getPath(); - if (path != null) { - mimeType = guessFromPath(path); - } + if (PATH_PRECEDENCE_MIME_TYPE.contains(mimeTypeFromPath)) { + return mimeTypeFromPath; } - if (mimeType == null && "content".equals(uri.getScheme())) { - final String name = getDisplayName(context, uri); - if (name != null) { - mimeType = guessFromPath(name); - } + if (mimeTypeContentResolver != null && !"application/octet-stream".equals(mimeTypeContentResolver)) { + return mimeTypeContentResolver; } - // sometimes this works (as with the commit content api) - if (mimeType == null) { - try { - mimeType = uri.getQueryParameter("mimeType"); - } catch (final Throwable throwable) { } + if (mimeTypeFromName != null) { + return mimeTypeFromName; + } + if (mimeTypeFromQueryParameter != null) { + return mimeTypeFromQueryParameter; + } + return mimeTypeFromPath; + } + + private static String guessFromContentResolver(final Context context, final Uri uri) { + try { + return context.getContentResolver().getType(uri); + } catch (final Throwable e) { + return null; } - return mimeType; } private static String getDisplayName(final Context context, final Uri uri) { diff --git a/src/main/java/eu/siacs/conversations/utils/PermissionUtils.java b/src/main/java/eu/siacs/conversations/utils/PermissionUtils.java index dd74dc57c3ea5053b93c8c03dd557f046eac09e2..21d7f42a1cfd40c3992f72b6d4f578f049e31a90 100644 --- a/src/main/java/eu/siacs/conversations/utils/PermissionUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/PermissionUtils.java @@ -24,9 +24,23 @@ public class PermissionUtils { return true; } - public static boolean writeGranted(int[] grantResults, String[] permission) { + public static boolean writeGranted(final int[] grantResults, final String[] permissions) { + return permissionGranted( + Manifest.permission.WRITE_EXTERNAL_STORAGE, grantResults, permissions); + } + + public static boolean audioGranted(final int[] grantResults, final String[] permissions) { + return permissionGranted(Manifest.permission.RECORD_AUDIO, grantResults, permissions); + } + + public static boolean cameraGranted(final int[] grantResults, final String[] permissions) { + return permissionGranted(Manifest.permission.CAMERA, grantResults, permissions); + } + + private static boolean permissionGranted( + final String permission, final int[] grantResults, final String[] permissions) { for (int i = 0; i < grantResults.length; ++i) { - if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission[i])) { + if (permission.equals(permissions[i])) { return grantResults[i] == PackageManager.PERMISSION_GRANTED; } } @@ -72,7 +86,7 @@ public class PermissionUtils { public static boolean hasPermission( final Activity activity, final List permissions, final int requestCode) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { final ImmutableList.Builder missingPermissions = new ImmutableList.Builder<>(); for (final String permission : permissions) { if (ActivityCompat.checkSelfPermission(activity, permission) diff --git a/src/main/java/eu/siacs/conversations/utils/Resolver.java b/src/main/java/eu/siacs/conversations/utils/Resolver.java index 463d6eb73b0c55fbb5c3c0797844f50bc99bced5..915209413e51b77b85c7c8f9f2492087dce0f747 100644 --- a/src/main/java/eu/siacs/conversations/utils/Resolver.java +++ b/src/main/java/eu/siacs/conversations/utils/Resolver.java @@ -6,6 +6,11 @@ import android.util.Log; import androidx.annotation.NonNull; +import com.google.common.base.Strings; +import com.google.common.base.Throwables; +import com.google.common.net.InetAddresses; +import com.google.common.primitives.Ints; + import java.io.IOException; import java.lang.reflect.Field; import java.net.Inet4Address; @@ -15,6 +20,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import de.gultsch.minidns.AndroidDNSClient; import de.measite.minidns.AbstractDNSClient; import de.measite.minidns.DNSCache; import de.measite.minidns.DNSClient; @@ -112,7 +118,7 @@ public class Resolver { return port == 443 || port == 5223; } - public static List resolve(String domain) { + public static List resolve(final String domain) { final List ipResults = fromIpAddress(domain); if (ipResults.size() > 0) { return ipResults; @@ -126,8 +132,10 @@ public class Resolver { synchronized (results) { results.addAll(list); } - } catch (Throwable throwable) { - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (direct TLS)", throwable); + } catch (final Throwable throwable) { + if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) { + Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (direct TLS)", throwable); + } } }); threads[1] = new Thread(() -> { @@ -136,8 +144,10 @@ public class Resolver { synchronized (results) { results.addAll(list); } - } catch (Throwable throwable) { - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (STARTTLS)", throwable); + } catch (final Throwable throwable) { + if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) { + Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (STARTTLS)", throwable); + } } }); threads[2] = new Thread(() -> { @@ -260,8 +270,10 @@ public class Resolver { results.addAll(resolveNoSrvRecords(cname.name, false)); } } - } catch (Throwable throwable) { - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records", throwable); + } catch (final Throwable throwable) { + if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) { + Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records", throwable); + } } results.add(Result.createDefault(dnsName)); return results; @@ -274,7 +286,9 @@ public class Resolver { private static ResolverResult resolveWithFallback(DNSName dnsName, Class type, boolean validateHostname) throws IOException { final Question question = new Question(dnsName, Record.TYPE.getType(type)); if (!validateHostname) { - return ResolverApi.INSTANCE.resolve(question); + final AndroidDNSClient androidDNSClient = new AndroidDNSClient(SERVICE); + final ResolverApi resolverApi = new ResolverApi(androidDNSClient); + return resolverApi.resolve(question); } try { return DnssecResolverApi.INSTANCE.resolveDnssecReliable(question); @@ -435,6 +449,65 @@ public class Resolver { contentValues.put(AUTHENTICATED, authenticated ? 1 : 0); return contentValues; } + + public Result seeOtherHost(final String seeOtherHost) { + final String hostname = seeOtherHost.trim(); + if (hostname.isEmpty()) { + return null; + } + final Result result = new Result(); + result.directTls = this.directTls; + final int portSegmentStart = hostname.lastIndexOf(':'); + if (hostname.charAt(hostname.length() - 1) != ']' + && portSegmentStart >= 0 + && hostname.length() >= portSegmentStart + 1) { + final String hostPart = hostname.substring(0, portSegmentStart); + final String portPart = hostname.substring(portSegmentStart + 1); + final Integer port = Ints.tryParse(portPart); + if (port == null || Strings.isNullOrEmpty(hostPart)) { + return null; + } + final String host = eu.siacs.conversations.utils.IP.unwrapIPv6(hostPart); + result.port = port; + if (InetAddresses.isInetAddress(host)) { + final InetAddress inetAddress; + try { + inetAddress = InetAddresses.forString(host); + } catch (final IllegalArgumentException e) { + return null; + } + result.ip = inetAddress; + } else { + if (hostPart.trim().isEmpty()) { + return null; + } + try { + result.hostname = DNSName.from(hostPart.trim()); + } catch (final Exception e) { + return null; + } + } + } else { + final String host = eu.siacs.conversations.utils.IP.unwrapIPv6(hostname); + if (InetAddresses.isInetAddress(host)) { + final InetAddress inetAddress; + try { + inetAddress = InetAddresses.forString(host); + } catch (final IllegalArgumentException e) { + return null; + } + result.ip = inetAddress; + } else { + try { + result.hostname = DNSName.from(hostname); + } catch (final Exception e) { + return null; + } + } + result.port = port; + } + return result; + } } } diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 69f57a4dd66af42b5ec22e0c2dbd2ccfef276085..58a76307248d134fb4e9389c2a51667a42ee9d80 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -498,6 +498,8 @@ public class UIHelper { return context.getString(R.string.file); } else if (MimeUtils.AMBIGUOUS_CONTAINER_FORMATS.contains(mime)) { return context.getString(R.string.multimedia_file); + } else if (mime.equals("audio/x-m4b")) { + return context.getString(R.string.audiobook); } else if (mime.startsWith("audio/")) { return context.getString(R.string.audio); } else if (mime.startsWith("video/")) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 7e1c366754c85a324068d505809579244114982a..4a4f9ccce822217b385f84afaab0d0185a497bad 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -68,6 +68,7 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.XmppDomainVerifier; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.sasl.ChannelBinding; +import eu.siacs.conversations.crypto.sasl.ChannelBindingMechanism; import eu.siacs.conversations.crypto.sasl.HashedToken; import eu.siacs.conversations.crypto.sasl.SaslMechanism; import eu.siacs.conversations.entities.Account; @@ -192,6 +193,8 @@ public class XmppConnection implements Runnable { private HashedToken.Mechanism hashTokenRequest; private HttpUrl redirectionUrl = null; private String verifiedHostname = null; + private Resolver.Result currentResolverResult; + private Resolver.Result seeOtherHostResolverResult; private volatile Thread mThread; private CountDownLatch mStreamCountDownLatch; private static ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1); @@ -234,10 +237,11 @@ public class XmppConnection implements Runnable { return; } if (account.getStatus() != nextStatus) { - if ((nextStatus == Account.State.OFFLINE) - && (account.getStatus() != Account.State.CONNECTING) - && (account.getStatus() != Account.State.ONLINE) - && (account.getStatus() != Account.State.DISABLED)) { + if (nextStatus == Account.State.OFFLINE + && account.getStatus() != Account.State.CONNECTING + && account.getStatus() != Account.State.ONLINE + && account.getStatus() != Account.State.DISABLED + && account.getStatus() != Account.State.LOGGED_OUT) { return; } if (nextStatus == Account.State.ONLINE) { @@ -364,7 +368,12 @@ public class XmppConnection implements Runnable { + storedBackupResult); } } - for (Iterator iterator = results.iterator(); + final Resolver.Result seeOtherHost = this.seeOtherHostResolverResult; + if (seeOtherHost != null) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": injected see-other-host on position 0"); + results.add(0, seeOtherHost); + } + for (final Iterator iterator = results.iterator(); iterator.hasNext(); ) { final Resolver.Result result = iterator.next(); if (Thread.currentThread().isInterrupted()) { @@ -378,7 +387,6 @@ public class XmppConnection implements Runnable { features.encryptionEnabled = result.isDirectTls(); verifiedHostname = result.isAuthenticated() ? result.getHostname().toString() : null; - Log.d(Config.LOGTAG, "verified hostname " + verifiedHostname); final InetSocketAddress addr; if (result.getIp() != null) { addr = new InetSocketAddress(result.getIp(), result.getPort()); @@ -426,6 +434,8 @@ public class XmppConnection implements Runnable { mXmppConnectionService.databaseBackend.saveResolverResult( domain, result); } + this.currentResolverResult = result; + this.seeOtherHostResolverResult = null; break; // successfully connected to server that speaks xmpp } else { FileBackend.close(localSocket); @@ -822,10 +832,15 @@ public class XmppConnection implements Runnable { tokenMechanism = null; } if (tokenMechanism != null && !Strings.isNullOrEmpty(token)) { - this.account.setFastToken(tokenMechanism, token); - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() + ": storing hashed token " + tokenMechanism); + if (ChannelBinding.priority(tokenMechanism.channelBinding) >= ChannelBindingMechanism.getPriority(currentSaslMechanism)) { + this.account.setFastToken(tokenMechanism, token); + Log.d( + Config.LOGTAG, + 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()); + this.account.resetFastToken(); + } } else if (this.hashTokenRequest != null) { Log.w( Config.LOGTAG, @@ -1255,8 +1270,9 @@ public class XmppConnection implements Runnable { tagReader.readTag(); final Socket socket = this.socket; final SSLSocket sslSocket = upgradeSocketToTls(socket); - tagReader.setInputStream(sslSocket.getInputStream()); - tagWriter.setOutputStream(sslSocket.getOutputStream()); + this.socket = sslSocket; + this.tagReader.setInputStream(sslSocket.getInputStream()); + this.tagWriter.setOutputStream(sslSocket.getOutputStream()); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS connection established"); final boolean quickStart; try { @@ -2177,6 +2193,21 @@ public class XmppConnection implements Runnable { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": policy violation. " + text); failPendingMessages(text); throw new StateChangingException(Account.State.POLICY_VIOLATION); + } else if (streamError.hasChild("see-other-host")) { + 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); + throw new StateChangingException(Account.State.STREAM_ERROR); + } + 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; + throw new StateChangingException(Account.State.SEE_OTHER_HOST); + } else { + throw new StateChangingException(Account.State.STREAM_ERROR); + } } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": stream error " + streamError); throw new StateChangingException(Account.State.STREAM_ERROR); @@ -2200,9 +2231,13 @@ public class XmppConnection implements Runnable { private boolean establishStream(final SSLSockets.Version sslVersion) throws IOException, InterruptedException { - final SaslMechanism quickStartMechanism = - SaslMechanism.ensureAvailable(account.getQuickStartMechanism(), sslVersion); final boolean secureConnection = sslVersion != SSLSockets.Version.NONE; + final SaslMechanism quickStartMechanism; + if (secureConnection) { + quickStartMechanism = SaslMechanism.ensureAvailable(account.getQuickStartMechanism(), sslVersion); + } else { + quickStartMechanism = null; + } if (secureConnection && Config.QUICKSTART_ENABLED && quickStartMechanism != null diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 6472a5c56817b4d6e2ace822c38971cb1ba57c93..48f011f098877b225fdc6fe08528d6b825cb415f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -73,7 +73,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { static String nextRandomId() { final byte[] id = new byte[16]; new SecureRandom().nextBytes(id); - return Base64.encodeToString(id, Base64.NO_WRAP | Base64.NO_PADDING); + return Base64.encodeToString(id, Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE); } public void deliverPacket(final Account account, final JinglePacket packet) { @@ -100,7 +100,8 @@ public class JingleConnectionManager extends AbstractConnectionManager { this.terminatedSessions.asMap().containsKey(PersistableSessionId.of(id)); final boolean stranger = isWithStrangerAndStrangerNotificationsAreOff(account, id.with); - if (isBusy() != null || sessionEnded || stranger) { + final boolean busy = isBusy() != null; + if (busy || sessionEnded || stranger) { Log.d( Config.LOGTAG, id.account.getJid().asBareJid() @@ -117,6 +118,15 @@ public class JingleConnectionManager extends AbstractConnectionManager { sessionTermination.setTo(id.with); sessionTermination.setReason(Reason.BUSY, null); mXmppConnectionService.sendIqPacket(account, sessionTermination, null); + if (busy || stranger) { + writeLogMissedIncoming( + account, + id.with, + id.sessionId, + null, + System.currentTimeMillis(), + stranger); + } return; } connection = new JingleRtpConnection(this, id, from); @@ -283,6 +293,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { if ("accept".equals(message.getName())) return; } final boolean fromSelf = from.asBareJid().equals(account.getJid().asBareJid()); + // XEP version 0.6.0 sends proceed, reject, ringing to bare jid final boolean addressedDirectly = to != null && to.equals(account.getJid()); final AbstractJingleConnection.Id id; if (fromSelf) { @@ -327,6 +338,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { Config.LOGTAG, id.account.getJid().asBareJid() + ": updated previous busy because call got picked up by another device"); + mXmppConnectionService.getNotificationService().clearMissedCall(previousBusy); return; } } @@ -383,18 +395,27 @@ public class JingleConnectionManager extends AbstractConnectionManager { this.connections.put(id, rtpConnection); rtpConnection.setProposedMedia(ImmutableSet.copyOf(media)); rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp); + // TODO actually do the automatic accept?! } else { Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": our session won tie break. waiting for other party to accept. winningSession=" + ourSessionId); + // TODO reject their session with ? } return; } - final boolean stranger = isWithStrangerAndStrangerNotificationsAreOff(account, id.with); + final boolean stranger = + isWithStrangerAndStrangerNotificationsAreOff(account, id.with); if (isBusy() != null || stranger) { - writeLogMissedIncoming(account, id.with.asBareJid(), id.sessionId, serverMsgId, timestamp); + writeLogMissedIncoming( + account, + id.with.asBareJid(), + id.sessionId, + serverMsgId, + timestamp, + stranger); if (stranger) { Log.d( Config.LOGTAG, @@ -450,7 +471,9 @@ public class JingleConnectionManager extends AbstractConnectionManager { Log.d( Config.LOGTAG, account.getJid().asBareJid() - + ": no rtp session proposal found for " + + ": no rtp session (" + + sessionId + + ") proposal found for " + from + " to deliver proceed"); if (remoteMsgId == null) { @@ -489,6 +512,10 @@ public class JingleConnectionManager extends AbstractConnectionManager { + " to deliver reject"); } } + } else if (addressedDirectly && "ringing".equals(message.getName())) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + from + " started ringing"); + updateProposedSessionDiscovered( + account, from, sessionId, DeviceDiscoveryState.DISCOVERED); } else { Log.d( Config.LOGTAG, @@ -532,10 +559,11 @@ public class JingleConnectionManager extends AbstractConnectionManager { private void writeLogMissedIncoming( final Account account, - Jid with, + final Jid with, final String sessionId, - String serverMsgId, - long timestamp) { + final String serverMsgId, + final long timestamp, + final boolean stranger) { final Conversation conversation = mXmppConnectionService.findOrCreateConversation( account, with.asBareJid(), false, false); @@ -545,7 +573,12 @@ public class JingleConnectionManager extends AbstractConnectionManager { message.setBody(new RtpSessionStatus(false, 0).toString()); message.setServerMsgId(serverMsgId); message.setTime(timestamp); + message.setCounterpart(with); writeMessage(message); + if (stranger) { + return; + } + mXmppConnectionService.getNotificationService().pushMissedCallNow(message); } private void writeMessage(final Message message) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 7aec2157894bedcf742182a4ecac246a25e89264..4d9dbe18aa675d006352f8144698f05ce88f94af 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -409,7 +409,29 @@ public class JingleRtpConnection extends AbstractJingleConnection return; } if (isInState(State.SESSION_ACCEPTED)) { - receiveContentAdd(jinglePacket, modification); + final boolean hasFullTransportInfo = modification.hasFullTransportInfo(); + final ListenableFuture future = + receiveRtpContentMap( + modification, this.omemoVerification.hasFingerprint() && hasFullTransportInfo); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(final RtpContentMap rtpContentMap) { + receiveContentAdd(jinglePacket, rtpContentMap); + } + + @Override + public void onFailure(@NonNull Throwable throwable) { + respondOk(jinglePacket); + final Throwable rootCause = Throwables.getRootCause(throwable); + Log.d( + Config.LOGTAG, + id.account.getJid().asBareJid() + + ": improperly formatted contents in content-add", + throwable); + webRTCWrapper.close(); + sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage()); + } + }, MoreExecutors.directExecutor()); } else { terminateWithOutOfOrder(jinglePacket); } @@ -494,7 +516,22 @@ public class JingleRtpConnection extends AbstractJingleConnection if (ourSummary.equals(ContentAddition.summary(receivedContentAccept))) { this.outgoingContentAdd = null; respondOk(jinglePacket); - receiveContentAccept(receivedContentAccept); + final boolean hasFullTransportInfo = receivedContentAccept.hasFullTransportInfo(); + final ListenableFuture future = + receiveRtpContentMap( + receivedContentAccept, this.omemoVerification.hasFingerprint() && hasFullTransportInfo); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(final RtpContentMap result) { + receiveContentAccept(result); + } + + @Override + public void onFailure(@NonNull final Throwable throwable) { + webRTCWrapper.close(); + sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage()); + } + }, MoreExecutors.directExecutor()); } else { Log.d(Config.LOGTAG, "received content-accept did not match our outgoing content-add"); terminateWithOutOfOrder(jinglePacket); @@ -527,17 +564,20 @@ public class JingleRtpConnection extends AbstractJingleConnection sendSessionTerminate(Reason.FAILED_APPLICATION, cause.getMessage()); return; } - processCandidates(receivedContentAccept.contents.entrySet()); - updateEndUserState(); Log.d( Config.LOGTAG, id.getAccount().getJid().asBareJid() + ": remote has accepted content-add " + ContentAddition.summary(receivedContentAccept)); + processCandidates(receivedContentAccept.contents.entrySet()); + updateEndUserState(); } private void receiveContentModify(final JinglePacket jinglePacket) { - // TODO check session accepted + if (this.state != State.SESSION_ACCEPTED) { + terminateWithOutOfOrder(jinglePacket); + return; + } final Map modification = Maps.transformEntries( jinglePacket.getJingleContents(), (key, value) -> value.getSenders()); @@ -855,6 +895,7 @@ public class JingleRtpConnection extends AbstractJingleConnection final RtpContentMap contentAcceptMap = rtpContentMap.toContentModification( Collections2.transform(contentAddition, ca -> ca.name)); + Log.d( Config.LOGTAG, id.getAccount().getJid().asBareJid() @@ -864,8 +905,22 @@ public class JingleRtpConnection extends AbstractJingleConnection addIceCandidatesFromBlackLog(); modifyLocalContentMap(rtpContentMap); - sendContentAccept(contentAcceptMap); - this.webRTCWrapper.setIsReadyToReceiveIceCandidates(true); + final ListenableFuture future = prepareOutgoingContentMap(contentAcceptMap); + Futures.addCallback( + future, + new FutureCallback() { + @Override + public void onSuccess(final RtpContentMap rtpContentMap) { + sendContentAccept(rtpContentMap); + webRTCWrapper.setIsReadyToReceiveIceCandidates(true); + } + + @Override + public void onFailure(@NonNull final Throwable throwable) { + failureToPerformAction(JinglePacket.Action.CONTENT_ACCEPT, throwable); + } + }, + MoreExecutors.directExecutor()); } catch (final Exception e) { Log.d(Config.LOGTAG, "unable to accept content add", Throwables.getRootCause(e)); webRTCWrapper.close(); @@ -1078,12 +1133,20 @@ public class JingleRtpConnection extends AbstractJingleConnection private ListenableFuture receiveRtpContentMap( final JinglePacket jinglePacket, final boolean expectVerification) { - final RtpContentMap receivedContentMap; try { - receivedContentMap = RtpContentMap.of(jinglePacket); + return receiveRtpContentMap(RtpContentMap.of(jinglePacket), expectVerification); } catch (final Exception e) { return Futures.immediateFailedFuture(e); } + } + private ListenableFuture receiveRtpContentMap(final RtpContentMap receivedContentMap, final boolean expectVerification) { + Log.d( + Config.LOGTAG, + "receiveRtpContentMap(" + + receivedContentMap.getClass().getSimpleName() + + ",expectVerification=" + + expectVerification + + ")"); if (receivedContentMap instanceof OmemoVerifiedRtpContentMap) { final ListenableFuture> future = id.account @@ -1389,6 +1452,16 @@ public class JingleRtpConnection extends AbstractJingleConnection sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage()); } + private void failureToPerformAction(final JinglePacket.Action action, final Throwable throwable) { + if (isTerminated()) { + return; + } + final Throwable rootCause = Throwables.getRootCause(throwable); + Log.d(Config.LOGTAG, "unable to send " + action, rootCause); + webRTCWrapper.close(); + sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage()); + } + private void addIceCandidatesFromBlackLog() { Map.Entry foo; while ((foo = this.pendingIceCandidates.poll()) != null) { @@ -1663,6 +1736,9 @@ public class JingleRtpConnection extends AbstractJingleConnection } this.message.setTime(timestamp); startRinging(); + if (xmppConnectionService.confirmMessages() && id.getContact().showInContactList()) { + sendJingleMessage("ringing"); + } } else { Log.d( Config.LOGTAG, @@ -1980,7 +2056,6 @@ public class JingleRtpConnection extends AbstractJingleConnection final JinglePacket jinglePacket = new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId); jinglePacket.setReason(reason, text); - Log.d(Config.LOGTAG, jinglePacket.toString()); send(jinglePacket); finish(); } @@ -2558,8 +2633,12 @@ public class JingleRtpConnection extends AbstractJingleConnection sessionDescription = setLocalSessionDescription(); } catch (final Exception e) { final Throwable cause = Throwables.getRootCause(e); - Log.d(Config.LOGTAG, "failed to renegotiate", cause); webRTCWrapper.close(); + if (isTerminated()) { + Log.d(Config.LOGTAG, "failed to renegotiate. session was already terminated", cause); + return; + } + Log.d(Config.LOGTAG, "failed to renegotiate. sending session-terminate", cause); sendSessionTerminate(Reason.FAILED_APPLICATION, cause.getMessage()); return; } @@ -2637,6 +2716,27 @@ public class JingleRtpConnection extends AbstractJingleConnection private void sendContentAdd(final RtpContentMap rtpContentMap, final Collection added) { final RtpContentMap contentAdd = rtpContentMap.toContentModification(added); this.outgoingContentAdd = contentAdd; + final ListenableFuture outgoingContentMapFuture = + prepareOutgoingContentMap(contentAdd); + Futures.addCallback( + outgoingContentMapFuture, + new FutureCallback() { + @Override + public void onSuccess(final RtpContentMap outgoingContentMap) { + sendContentAdd(outgoingContentMap); + webRTCWrapper.setIsReadyToReceiveIceCandidates(true); + } + + @Override + public void onFailure(@NonNull Throwable throwable) { + failureToPerformAction(JinglePacket.Action.CONTENT_ADD, throwable); + } + }, + MoreExecutors.directExecutor()); + } + + private void sendContentAdd(final RtpContentMap contentAdd) { + final JinglePacket jinglePacket = contentAdd.toJinglePacket(JinglePacket.Action.CONTENT_ADD, id.sessionId); jinglePacket.setTo(id.with); @@ -2837,7 +2937,7 @@ public class JingleRtpConnection extends AbstractJingleConnection // STUN URLs do not support a query section since M110 final String uri; if (Arrays.asList("stun","stuns").contains(type)) { - uri = String.format("%s:%s%s", type, IP.wrapIPv6(host),port); + uri = String.format("%s:%s:%s", type, IP.wrapIPv6(host),port); } else { uri = String.format( "%s:%s:%s?transport=%s", diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java index 5b940e7b837682e25918be9a9ebe40b909dea9cc..0dc99a944e905d09bc0d3281f589ae014842218d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java @@ -275,6 +275,11 @@ public class RtpContentMap { return count == 0; } + public boolean hasFullTransportInfo() { + return Collections2.transform(this.contents.values(), dt -> dt.transport.isStub()) + .contains(false); + } + public RtpContentMap modifiedCredentials( IceUdpTransportInfo.Credentials credentials, final IceUdpTransportInfo.Setup setup) { final ImmutableMap.Builder contentMapBuilder = @@ -354,12 +359,7 @@ public class RtpContentMap { public RtpContentMap toContentModification(final Collection modifications) { return new RtpContentMap( - this.group, - Maps.transformValues( - Maps.filterKeys(contents, Predicates.in(modifications)), - dt -> - new DescriptionTransport( - dt.senders, dt.description, IceUdpTransportInfo.STUB))); + this.group, Maps.filterKeys(contents, Predicates.in(modifications))); } public RtpContentMap toStub() { @@ -396,37 +396,43 @@ public class RtpContentMap { } public RtpContentMap addContent( - final RtpContentMap modification, final IceUdpTransportInfo.Setup setup) { - final IceUdpTransportInfo.Credentials credentials = getDistinctCredentials(); - final Collection iceOptions = getCombinedIceOptions(); - final DTLS dtls = getDistinctDtls(); + final RtpContentMap modification, final IceUdpTransportInfo.Setup setupOverwrite) { final Map combined = merge(contents, modification.contents); final Map combinedFixedTransport = Maps.transformValues( combined, dt -> { final IceUdpTransportInfo iceUdpTransportInfo; - if (dt.transport.emptyCredentials()) { + if (dt.transport.isStub()) { + final IceUdpTransportInfo.Credentials credentials = + getDistinctCredentials(); + final Collection iceOptions = getCombinedIceOptions(); + final DTLS dtls = getDistinctDtls(); iceUdpTransportInfo = IceUdpTransportInfo.of( credentials, iceOptions, - setup, + setupOverwrite, dtls.hash, dtls.fingerprint); } else { + final IceUdpTransportInfo.Fingerprint fp = + dt.transport.getFingerprint(); + final IceUdpTransportInfo.Setup setup = fp.getSetup(); iceUdpTransportInfo = IceUdpTransportInfo.of( dt.transport.getCredentials(), - iceOptions, - setup, - dtls.hash, - dtls.fingerprint); + dt.transport.getIceOptions(), + setup == IceUdpTransportInfo.Setup.ACTPASS + ? setupOverwrite + : setup, + fp.getHash(), + fp.getContent()); } return new DescriptionTransport( dt.senders, dt.description, iceUdpTransportInfo); }); - return new RtpContentMap(modification.group, combinedFixedTransport); + return new RtpContentMap(modification.group, ImmutableMap.copyOf(combinedFixedTransport)); } private static Map merge( diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java index 10755abebc659a96618d4d0540f25e96da3f8a63..2d2dc95709f22e86b5d88645bb14540205eaff12 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java @@ -176,8 +176,15 @@ public class SessionDescription { mediaAttributes.put("ice-options", Joiner.on(' ').join(iceOptions)); final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint(); if (fingerprint != null) { - mediaAttributes.put( - "fingerprint", fingerprint.getHash() + " " + fingerprint.getContent()); + final String hashFunction = fingerprint.getHash(); + final String hash = fingerprint.getContent(); + if (Strings.isNullOrEmpty(hashFunction) || Strings.isNullOrEmpty(hash)) { + throw new IllegalArgumentException("DTLS-SRTP missing hash"); + } + checkNoWhitespace( + hashFunction, "DTLS-SRTP hash function must not contain whitespace"); + checkNoWhitespace(hash, "DTLS-SRTP hash must not contain whitespace"); + mediaAttributes.put("fingerprint", hashFunction + " " + hash); final IceUdpTransportInfo.Setup setup = fingerprint.getSetup(); if (setup != null) { mediaAttributes.put("setup", setup.toString().toLowerCase(Locale.ROOT)); @@ -214,12 +221,14 @@ public class SessionDescription { } checkNoWhitespace( type, "feedback negotiation type must not contain whitespace"); - mediaAttributes.put( - "rtcp-fb", - id - + " " - + type - + (Strings.isNullOrEmpty(subtype) ? "" : " " + subtype)); + if (Strings.isNullOrEmpty(subtype)) { + mediaAttributes.put("rtcp-fb", id + " " + type); + } else { + checkNoWhitespace( + subtype, + "feedback negotiation subtype must not contain whitespace"); + mediaAttributes.put("rtcp-fb", id + " " + type + " " + subtype); + } } for (RtpDescription.FeedbackNegotiationTrrInt feedbackNegotiationTrrInt : payloadType.feedbackNegotiationTrrInts()) { @@ -236,9 +245,13 @@ public class SessionDescription { throw new IllegalArgumentException("a feedback negotiation is missing type"); } checkNoWhitespace(type, "feedback negotiation type must not contain whitespace"); - mediaAttributes.put( - "rtcp-fb", - "* " + type + (Strings.isNullOrEmpty(subtype) ? "" : " " + subtype)); + if (Strings.isNullOrEmpty(subtype)) { + mediaAttributes.put("rtcp-fb", "* " + type); + } else { + checkNoWhitespace( + subtype, "feedback negotiation subtype must not contain whitespace"); + mediaAttributes.put("rtcp-fb", "* " + type + " " + subtype); /**/ + } } for (final RtpDescription.FeedbackNegotiationTrrInt feedbackNegotiationTrrInt : description.feedbackNegotiationTrrInts()) { @@ -275,6 +288,9 @@ public class SessionDescription { if (groups.size() == 0) { throw new IllegalArgumentException("A SSRC group is missing SSRC ids"); } + for (final String source : groups) { + checkNoWhitespace(source, "Sources must not contain whitespace"); + } mediaAttributes.put( "ssrc-group", String.format("%s %s", semantics, Joiner.on(' ').join(groups))); @@ -298,7 +314,14 @@ public class SessionDescription { throw new IllegalArgumentException( "A source specific media attribute is missing its value"); } - mediaAttributes.put("ssrc", id + " " + parameterName + ":" + parameterValue); + checkNoWhitespace( + parameterName, + "A source specific media attribute name not not contain whitespace"); + checkNoNewline( + parameterValue, + "A source specific media attribute value must not contain new lines"); + mediaAttributes.put( + "ssrc", id + " " + parameterName + ":" + parameterValue.trim()); } } @@ -337,6 +360,13 @@ public class SessionDescription { return input; } + public static String checkNoNewline(final String input, final String message) { + if (CharMatcher.anyOf("\r\n").matchesAnyOf(message)) { + throw new IllegalArgumentException(message); + } + return input; + } + public static int ignorantIntParser(final String input) { try { return Integer.parseInt(input); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index b6885ef5f3ab0e4ba5609705402d5d40cb797ef7..dbfef237b216b7eb84e461140a26191b17af8537 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -60,6 +60,8 @@ public class WebRTCWrapper { private static final String EXTENDED_LOGGING_TAG = WebRTCWrapper.class.getSimpleName(); private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + private final ExecutorService localDescriptionExecutorService = + Executors.newSingleThreadExecutor(); private static final int TONE_DURATION = 500; private static final Map TONE_CODES; @@ -79,8 +81,6 @@ public class WebRTCWrapper { builder.put("#", ToneGenerator.TONE_DTMF_P); TONE_CODES = builder.build(); } - private final ExecutorService localDescriptionExecutorService = - Executors.newSingleThreadExecutor(); private static final Set HARDWARE_AEC_BLACKLIST = new ImmutableSet.Builder() @@ -95,6 +95,7 @@ public class WebRTCWrapper { .add("E5823") // Sony z5 compact .add("Redmi Note 5") .add("FP2") // Fairphone FP2 + .add("FP4") // Fairphone FP4 .add("MI 5") .add("GT-I9515") // Samsung Galaxy S4 Value Edition (jfvelte) .add("GT-I9515L") // Samsung Galaxy S4 Value Edition (jfvelte) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java index ea36d01ee56f089ec870f2ac2ae23cce93a4701e..925e63977b48eea645187c7938eb316ca8c1b025 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java @@ -85,7 +85,7 @@ public class IceUdpTransportInfo extends GenericTransportInfo { iceUdpTransportInfo.addChild(Fingerprint.of(setup, hash, fingerprint)); iceUdpTransportInfo.setAttribute("ufrag", credentials.ufrag); iceUdpTransportInfo.setAttribute("pwd", credentials.password); - for(final String iceOption : iceOptions) { + for (final String iceOption : iceOptions) { iceUdpTransportInfo.addChild(new IceOption(iceOption)); } return iceUdpTransportInfo; @@ -113,8 +113,10 @@ public class IceUdpTransportInfo extends GenericTransportInfo { return new Credentials(ufrag, password); } - public boolean emptyCredentials() { - return Strings.isNullOrEmpty(this.getAttribute("ufrag")) || Strings.isNullOrEmpty(this.getAttribute("pwd")); + public boolean isStub() { + return Strings.isNullOrEmpty(this.getAttribute("ufrag")) + && Strings.isNullOrEmpty(this.getAttribute("pwd")) + && getChildren().isEmpty(); } public List getCandidates() { diff --git a/src/main/res/drawable-v24/ic_launcher_background.xml b/src/main/res/drawable-v24/ic_launcher_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..ec3c58ad515f7088afa98027b7f441c242c51e00 --- /dev/null +++ b/src/main/res/drawable-v24/ic_launcher_background.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/res/drawable/ic_logout_white_24dp.xml b/src/main/res/drawable/ic_logout_white_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..5f818ab1617a8482f2c64910880b43d8dde5f3e3 --- /dev/null +++ b/src/main/res/drawable/ic_logout_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/src/main/res/drawable/ic_play_lesson_black_24.xml b/src/main/res/drawable/ic_play_lesson_black_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..4c4a46ce7fb418541470baf37e58259c45abfd06 --- /dev/null +++ b/src/main/res/drawable/ic_play_lesson_black_24.xml @@ -0,0 +1,6 @@ + + + + diff --git a/src/main/res/drawable/ic_play_lesson_white_48dp.xml b/src/main/res/drawable/ic_play_lesson_white_48dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..67fe7c696ccaa6971a4311ad543cfb482d12ff33 --- /dev/null +++ b/src/main/res/drawable/ic_play_lesson_white_48dp.xml @@ -0,0 +1,6 @@ + + + + diff --git a/src/main/res/drawable/ic_qr_code_black_24dp.xml b/src/main/res/drawable/ic_qr_code_black_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..e33c1a6225e9db964b0ba5a37e7b95b9cb71facf --- /dev/null +++ b/src/main/res/drawable/ic_qr_code_black_24dp.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + diff --git a/src/main/res/drawable/ic_qr_code_white_24dp.xml b/src/main/res/drawable/ic_qr_code_white_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..d345816e0d64e0e97337822b15e4c2f7c9481990 --- /dev/null +++ b/src/main/res/drawable/ic_qr_code_white_24dp.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + diff --git a/src/main/res/layout/activity_contact_details.xml b/src/main/res/layout/activity_contact_details.xml index 4b32f857f9d564b6093498f8195b6837cff6b5a5..17d277547fbf2c708a259a7c1654b2acac245486 100644 --- a/src/main/res/layout/activity_contact_details.xml +++ b/src/main/res/layout/activity_contact_details.xml @@ -215,6 +215,19 @@ android:orientation="vertical" android:padding="@dimen/card_padding_list"/> + + + + + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - + android:layout_marginBottom="@dimen/activity_vertical_margin"> + app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error" + app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint"> + android:textColor="?attr/edit_text_color" /> @@ -77,21 +78,21 @@ android:id="@+id/account_password_layout" android:layout_width="match_parent" android:layout_height="wrap_content" + app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error" + app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint" app:passwordToggleDrawable="@drawable/visibility_toggle_drawable" app:passwordToggleEnabled="true" - app:passwordToggleTint="?android:textColorSecondary" - app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint" - app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"> + app:passwordToggleTint="?android:textColorSecondary"> + android:textColor="?attr/edit_text_color" /> + app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error" + app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint"> + android:inputType="textWebEmailAddress" /> @@ -136,16 +137,16 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/account_settings_port" - app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint" - app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"> + app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error" + app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint"> + android:maxLength="5" /> @@ -156,7 +157,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:text="@string/register_account"/> + android:text="@string/register_account" /> @@ -165,10 +166,10 @@ android:id="@+id/os_optimization" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" - android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" android:visibility="gone"> + android:textAppearance="@style/TextAppearance.Conversations.Title" /> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + android:textColor="?colorAccent" /> @@ -223,10 +224,10 @@ android:id="@+id/stats" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" - android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" android:visibility="gone"> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> @@ -283,7 +284,7 @@ android:ellipsize="end" android:singleLine="true" android:text="@string/server_info_pep" - android:textAppearance="@style/TextAppearance.Conversations.Body1"/> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + tools:ignore="RtlHardcoded" /> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + tools:ignore="RtlHardcoded" /> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + tools:ignore="RtlHardcoded" /> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + tools:ignore="RtlHardcoded" /> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + tools:ignore="RtlHardcoded" /> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + tools:ignore="RtlHardcoded" /> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + tools:ignore="RtlHardcoded" /> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + tools:ignore="RtlHardcoded" /> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> @@ -513,14 +514,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/no_name_set_instructions" - android:textAppearance="@style/TextAppearance.Conversations.Body1.Tertiary"/> + android:textAppearance="@style/TextAppearance.Conversations.Body1.Tertiary" /> + android:textAppearance="@style/TextAppearance.Conversations.Caption" /> + android:visibility="visible" /> + android:textAppearance="@style/TextAppearance.Conversations.Fingerprint" /> + android:textAppearance="@style/TextAppearance.Conversations.Caption" /> + android:visibility="visible" /> + android:textAppearance="@style/TextAppearance.Conversations.Fingerprint" /> + android:textAppearance="@style/TextAppearance.Conversations.Caption" /> + android:src="?attr/icon_qr_code" + android:visibility="visible" /> + android:visibility="gone" /> @@ -728,41 +729,83 @@ android:id="@+id/other_device_keys_card" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" - android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" android:visibility="gone"> + android:orientation="vertical"> - + android:orientation="vertical" + android:padding="@dimen/card_padding_list"> + + + + + + android:paddingHorizontal="@dimen/card_padding_list"> -