diff --git a/build.gradle b/build.gradle index 0f42c9c8ee7bc6ecdd1d93ae7891a989d50825aa..2a3344895d142e79096aaa017c795ad1ba7f1023 100644 --- a/build.gradle +++ b/build.gradle @@ -69,6 +69,7 @@ dependencies { implementation "androidx.preference:preference:1.2.1" implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'com.google.android.material:material:1.11.0' + implementation 'androidx.work:work-runtime:2.9.0' implementation "androidx.emoji2:emoji2:1.4.0" freeImplementation "androidx.emoji2:emoji2-bundled:1.4.0" diff --git a/fastlane/metadata/android/de-DE/changelogs/4210904.txt b/fastlane/metadata/android/de-DE/changelogs/4210904.txt new file mode 100644 index 0000000000000000000000000000000000000000..28fcd66e1f01780696d0cf4ce7d197cc499bc374 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4210904.txt @@ -0,0 +1,2 @@ +* Quicksy Registrierung auf Android 6/7 repariert +* Klingelton für eingehende Anrufe im Benachrichtigungskanal abspielen diff --git a/fastlane/metadata/android/de-DE/changelogs/4211004.txt b/fastlane/metadata/android/de-DE/changelogs/4211004.txt new file mode 100644 index 0000000000000000000000000000000000000000..7bf3e43fa682865d719ea61954e919faed8dbc91 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4211004.txt @@ -0,0 +1,2 @@ +* Anrufintegration auf einigen Android 14-Geräten behoben +* Neue Einstellung "Einladungen von Unbekannten" diff --git a/fastlane/metadata/android/es-ES/changelogs/4210804.txt b/fastlane/metadata/android/es-ES/changelogs/4210804.txt new file mode 100644 index 0000000000000000000000000000000000000000..1ece57245ad12335fd19e89a76f177baa37a08fc --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/4210804.txt @@ -0,0 +1,2 @@ +* Mostrar el estado de los mensajes como iconos +* Introducir la opción "Fuente grande" para las burbujas de mensajes diff --git a/fastlane/metadata/android/es-ES/changelogs/4210904.txt b/fastlane/metadata/android/es-ES/changelogs/4210904.txt new file mode 100644 index 0000000000000000000000000000000000000000..8068bcd41843b60764b8ff633a85b9c9c40539cd --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/4210904.txt @@ -0,0 +1,2 @@ +* Arreglar el registro de Quicksy en Android 6/7 +* Reproducir el tono de llamada entrante en el canal de notificación diff --git a/fastlane/metadata/android/es-ES/changelogs/4211004.txt b/fastlane/metadata/android/es-ES/changelogs/4211004.txt new file mode 100644 index 0000000000000000000000000000000000000000..a87e7d85c44d4beb765818aaf6f708784de74739 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/4211004.txt @@ -0,0 +1,2 @@ +* arreglar la integración de llamadas en algunos dispositivos con Android 14 +* Introducir la configuración 'Invitaciones de extraños' diff --git a/fastlane/metadata/android/gl-ES/changelogs/4210904.txt b/fastlane/metadata/android/gl-ES/changelogs/4210904.txt new file mode 100644 index 0000000000000000000000000000000000000000..14ebde079e817e5268b0c664487153fb8df52456 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/4210904.txt @@ -0,0 +1,2 @@ +* Solución para crear conta con Quicksy en Android 6/7 +* Reproducir ton de chamada recibida na canle de notificación diff --git a/fastlane/metadata/android/gl-ES/changelogs/4211004.txt b/fastlane/metadata/android/gl-ES/changelogs/4211004.txt new file mode 100644 index 0000000000000000000000000000000000000000..2244834aab50d50da6a2e38d397903c07ff96a34 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/4211004.txt @@ -0,0 +1,2 @@ +* arranxo da integración de chamadas nalgúns Android 14 +* novo axuste para 'Convites de Descoñecidas' diff --git a/fastlane/metadata/android/it-IT/changelogs/4210904.txt b/fastlane/metadata/android/it-IT/changelogs/4210904.txt new file mode 100644 index 0000000000000000000000000000000000000000..8e6d9c15b8cfd209ef384ef15d30698b14a3d3b1 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/4210904.txt @@ -0,0 +1,2 @@ +* Corretta registrazione a Quicksy su Android 6/7 +* Riproduci suoneria per chiamate in arrivo nel canale di notifica diff --git a/fastlane/metadata/android/it-IT/changelogs/4211004.txt b/fastlane/metadata/android/it-IT/changelogs/4211004.txt new file mode 100644 index 0000000000000000000000000000000000000000..771eb3ba9944659238ae679f9134d23e39ab8626 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/4211004.txt @@ -0,0 +1,2 @@ +* corretta l'integrazione delle chiamate su alcuni dispositivi con Android 14 +* Introdotta l'impostazione 'Inviti da estranei' diff --git a/fastlane/metadata/android/pl-PL/changelogs/4210904.txt b/fastlane/metadata/android/pl-PL/changelogs/4210904.txt new file mode 100644 index 0000000000000000000000000000000000000000..7284d0deeaff17b70392c99882e378d5c8d1f342 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4210904.txt @@ -0,0 +1,2 @@ +* Naprawienie rejestracji w Quicksy w Androidzie 6/7 +* Odtwarzanie dzwonka połączenia przychodzącego w kanale powiadomień diff --git a/fastlane/metadata/android/pl-PL/changelogs/4211004.txt b/fastlane/metadata/android/pl-PL/changelogs/4211004.txt new file mode 100644 index 0000000000000000000000000000000000000000..bd200026c426324710fb952a2f1b207ee6ca0942 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4211004.txt @@ -0,0 +1,2 @@ +* Naprawienie integracji rozmów na niektórych urządzeniach z Androidem 14 +* Wprowadzenie ustawienia „Zaproszenia od nieznajomych” diff --git a/fastlane/metadata/android/sq/changelogs/4210804.txt b/fastlane/metadata/android/sq/changelogs/4210804.txt new file mode 100644 index 0000000000000000000000000000000000000000..e77cdf0267c4a344f0b52b81edd069d153b89cd3 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/4210804.txt @@ -0,0 +1,2 @@ +* Shfaqe gjendje mesazhesh si ikona +* Sjellje e rregullimit “Shkronja të mëdha” për flluska mesazhesh diff --git a/fastlane/metadata/android/sq/changelogs/4210904.txt b/fastlane/metadata/android/sq/changelogs/4210904.txt new file mode 100644 index 0000000000000000000000000000000000000000..1a7c4e53123dd02b3457a93e3d1310fb034886de --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/4210904.txt @@ -0,0 +1,2 @@ +* Ndreqje regjistrimi Quicksy në Android 6/7 +* Luajtje e ziles së thirrjes ardhëse në kanal njoftimi diff --git a/fastlane/metadata/android/sq/changelogs/4211004.txt b/fastlane/metadata/android/sq/changelogs/4211004.txt new file mode 100644 index 0000000000000000000000000000000000000000..21be2d1b2d3109a4e31a7225ada9d5c592843e39 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/4211004.txt @@ -0,0 +1,2 @@ +* ndreqje integrimi thirrjesh në disa pajisje me Android 14 +* Sjellje për herë të parë e rregullimit “Ftesa nga të Panjohur” diff --git a/fastlane/metadata/android/uk/changelogs/4210904.txt b/fastlane/metadata/android/uk/changelogs/4210904.txt new file mode 100644 index 0000000000000000000000000000000000000000..45c3122aa78580d5ede4c166fe6caa433ef73256 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4210904.txt @@ -0,0 +1,2 @@ +* Виправлено реєстрацію Quicksy на Android 6/7 +* Відтворення мелодії вхідних викликів у каналі сповіщень diff --git a/fastlane/metadata/android/uk/changelogs/4211004.txt b/fastlane/metadata/android/uk/changelogs/4211004.txt new file mode 100644 index 0000000000000000000000000000000000000000..36b7f674e0aea11a5fee1f92d135bcbbe7b5376d --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4211004.txt @@ -0,0 +1,2 @@ +* Виправлено інтеграцію викликів на деяких пристроях Android 14 +* Додано налаштування «Запрошення від незнайомців» diff --git a/fastlane/metadata/android/zh-CN/changelogs/4210904.txt b/fastlane/metadata/android/zh-CN/changelogs/4210904.txt new file mode 100644 index 0000000000000000000000000000000000000000..f503dd304a91ca428d35a40efd659e30e7d7eb8e --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4210904.txt @@ -0,0 +1,2 @@ +* 修复 Android 6/7 上的 Quicksy 注册问题 +* 在通知通道上播放来电铃声 diff --git a/fastlane/metadata/android/zh-CN/changelogs/4211004.txt b/fastlane/metadata/android/zh-CN/changelogs/4211004.txt new file mode 100644 index 0000000000000000000000000000000000000000..53e308fcb00e7e859112553fbef3194aea502fd8 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4211004.txt @@ -0,0 +1,2 @@ +* 修复某些 Android 14 设备上的呼叫集成 +* 添加了“来自陌生人的邀请”设置 diff --git a/fastlane/metadata/android/zh-TW/changelogs/42015.txt b/fastlane/metadata/android/zh-TW/changelogs/42015.txt new file mode 100644 index 0000000000000000000000000000000000000000..004c1806f6294aa14245b3bbe750aaa70b369642 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42015.txt @@ -0,0 +1 @@ +* 在音頻和視頻方面略有改善 diff --git a/fastlane/metadata/android/zh-TW/changelogs/42018.txt b/fastlane/metadata/android/zh-TW/changelogs/42018.txt new file mode 100644 index 0000000000000000000000000000000000000000..64603c144dadd7fa8f88c205c34640494f03b8b2 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42018.txt @@ -0,0 +1,3 @@ +* 當遠程視頻與屏幕寬高比不匹配時顯示黑條 +* 提高搜索性能 +* 添加防止截屏的設置 diff --git a/fastlane/metadata/android/zh-TW/changelogs/42022.txt b/fastlane/metadata/android/zh-TW/changelogs/42022.txt new file mode 100644 index 0000000000000000000000000000000000000000..f9269a5bd4e01209446b83316a47aacb48f63520 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42022.txt @@ -0,0 +1,2 @@ +* 修復某些視頻無法壓縮的問題 +* 修復打開通知時罕見的崩潰問題 diff --git a/fastlane/metadata/android/zh-TW/changelogs/42023.txt b/fastlane/metadata/android/zh-TW/changelogs/42023.txt new file mode 100644 index 0000000000000000000000000000000000000000..b260e0cf94d8f3d07a57d3bae39c508343850cb6 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42023.txt @@ -0,0 +1,2 @@ +* 修復渲染某些引用時的崩潰問題 +* 修復歡迎屏幕崩潰問題 diff --git a/fastlane/metadata/android/zh-TW/changelogs/42037.txt b/fastlane/metadata/android/zh-TW/changelogs/42037.txt new file mode 100644 index 0000000000000000000000000000000000000000..13e3ef2aa30ff657274e9b463fea66967a8195ce --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42037.txt @@ -0,0 +1,11 @@ +版本 2.10.9 +* 進行音視頻通話時請求藍牙權限(如果您不使用藍牙耳機可以拒絕) +* 修復呼叫 Movim 時的錯誤 +* 修復群組聊天的顯示錯誤頭像的問題 +* 始終要求選擇退出電池優化 +* 在“x 個已連接賬號”通知上設置僅本地標誌 +* 修復與 Google 地圖分享位置插件的交互 +* 移除有關服務器費用的腳註 +* 將文件存儲在適合 Android 11 的位置 +* 網絡切換後嘗試重新連接通話 +* 在來電屏幕中顯示來電者 JID 和帳戶 JID diff --git a/fastlane/metadata/android/zh-TW/changelogs/42038.txt b/fastlane/metadata/android/zh-TW/changelogs/42038.txt new file mode 100644 index 0000000000000000000000000000000000000000..ca9a447c3c3b643605933878606afad4652e965b --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42038.txt @@ -0,0 +1,2 @@ +* 修正了一些小錯誤 +* 恢復通過 JMP 和其他服務呼叫的能力(Playstore 版本) diff --git a/fastlane/metadata/android/zh-TW/changelogs/42041.txt b/fastlane/metadata/android/zh-TW/changelogs/42041.txt new file mode 100644 index 0000000000000000000000000000000000000000..f88ad5b5131a4aa5c738e8f53519bfffb466f4f3 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42041.txt @@ -0,0 +1,5 @@ +* 實現可擴展 SASL Profile、Bind 2.0 和 Fast,以加快重新連接速度 +* 實現通道綁定 +* 增加從音頻通話切換到視頻通話的功能 +* 增加刪除自己頭像的功能 +* 增加未接來電通知功能 diff --git a/fastlane/metadata/android/zh-TW/changelogs/42042.txt b/fastlane/metadata/android/zh-TW/changelogs/42042.txt new file mode 100644 index 0000000000000000000000000000000000000000..19e92ff8645c1ac5fdb14d425dd496139d9a6c89 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42042.txt @@ -0,0 +1,2 @@ +* 修復僅支持 sm:2 的服務器上的重發循環 +* 僅當對方支持視頻時才顯示“切換到視頻” diff --git a/fastlane/metadata/android/zh-TW/changelogs/42043.txt b/fastlane/metadata/android/zh-TW/changelogs/42043.txt new file mode 100644 index 0000000000000000000000000000000000000000..29ae668a1f787ea29f9f89b0e39043ee9bd2add0 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42043.txt @@ -0,0 +1 @@ +* 修復了 P2P 文件傳輸中的缺陷 diff --git a/fastlane/metadata/android/zh-TW/changelogs/42044.txt b/fastlane/metadata/android/zh-TW/changelogs/42044.txt new file mode 100644 index 0000000000000000000000000000000000000000..4d7ef7f8287e3fdc3185bf5049b20b7b69b24ab7 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42044.txt @@ -0,0 +1,3 @@ +* 修復使用 SASL2 時重新發送消息的問題 +* 修復部分設備之間的黑屏問題 +* 修復空密碼崩潰問題 diff --git a/fastlane/metadata/android/zh-TW/changelogs/42046.txt b/fastlane/metadata/android/zh-TW/changelogs/42046.txt new file mode 100644 index 0000000000000000000000000000000000000000..a302ae7ff1f3111d47d3f779650550b89302ded9 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42046.txt @@ -0,0 +1 @@ +* 整合 UnifiedPush 分發程序,以便將消息推送到其他支持 UnifiedPush 的應用程序,例如 Tusky 和 Fedilab diff --git a/fastlane/metadata/android/zh-TW/changelogs/42059.txt b/fastlane/metadata/android/zh-TW/changelogs/42059.txt new file mode 100644 index 0000000000000000000000000000000000000000..32a329bd637056925be33ffdca4ff687ad367348 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42059.txt @@ -0,0 +1,2 @@ +* 將 Target SDK 再次提升至 33 +* 修復支持 SASL2 且不支持內聯流管理的服務器上的問題 diff --git a/fastlane/metadata/android/zh-TW/changelogs/42060.txt b/fastlane/metadata/android/zh-TW/changelogs/42060.txt new file mode 100644 index 0000000000000000000000000000000000000000..16b5141f9bede90021355dfedee1dbe2131d4ab7 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42060.txt @@ -0,0 +1 @@ +* 修復“q”被錯誤識別爲西裏爾字母的問題 diff --git a/fastlane/metadata/android/zh-TW/changelogs/42061.txt b/fastlane/metadata/android/zh-TW/changelogs/42061.txt new file mode 100644 index 0000000000000000000000000000000000000000..c35ffeb50a1f8a87425bb6f3c429fbf0210984ab --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42061.txt @@ -0,0 +1 @@ +* 從 Google Play 版本中移除頻道探索功能 diff --git a/fastlane/metadata/android/zh-TW/changelogs/42062.txt b/fastlane/metadata/android/zh-TW/changelogs/42062.txt new file mode 100644 index 0000000000000000000000000000000000000000..9c6025be5d9a10007ff975e9a218fb6d440977ed --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42062.txt @@ -0,0 +1 @@ +* 禁止從文件管理器打開備份文件(.ceb) diff --git a/fastlane/metadata/android/zh-TW/changelogs/42065.txt b/fastlane/metadata/android/zh-TW/changelogs/42065.txt new file mode 100644 index 0000000000000000000000000000000000000000..f6346f62a5eee7d5b8db1410bd1f04406622817f --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42065.txt @@ -0,0 +1 @@ +* 引入新的備份文件格式 diff --git a/fastlane/metadata/android/zh-TW/changelogs/42068.txt b/fastlane/metadata/android/zh-TW/changelogs/42068.txt new file mode 100644 index 0000000000000000000000000000000000000000..0d2fecdb3f027cf39bd7b6a550f551fb971216e0 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42068.txt @@ -0,0 +1,2 @@ +* 支持對話個別通知設置 +* 在 Android 10 上使用 opus 發送語音消息 diff --git a/fastlane/metadata/android/zh-TW/changelogs/42072.txt b/fastlane/metadata/android/zh-TW/changelogs/42072.txt new file mode 100644 index 0000000000000000000000000000000000000000..7f84a4d0a12fae23c8ac6bee9aeffa5af87de0f6 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/42072.txt @@ -0,0 +1,3 @@ +* 將 libwebrtc 依賴項提升到 M117 並提升 libvpx +* 回到 AAC 語音消息 +* 支持應用程序個別語言設置 diff --git a/fastlane/metadata/android/zh-TW/changelogs/4207704.txt b/fastlane/metadata/android/zh-TW/changelogs/4207704.txt new file mode 100644 index 0000000000000000000000000000000000000000..b455c5e1402dfbdbb74d1ee261e28220ef8f44fe --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/4207704.txt @@ -0,0 +1,3 @@ +* 支持私有 DNS(DNS over TLS) +* 支持主題啟動器圖標 +* 修復在 Android 11+ 上分享文件時罕見的權限問題 diff --git a/fastlane/metadata/android/zh-TW/changelogs/4208104.txt b/fastlane/metadata/android/zh-TW/changelogs/4208104.txt new file mode 100644 index 0000000000000000000000000000000000000000..bfeabda61e0efcffd34f7277b20919f614c83255 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/4208104.txt @@ -0,0 +1,4 @@ +* 更容易訪問「顯示二維碼」 +* 支持 PEP Native Bookmarks +* 添加對 SDP 請求/響應模型的支持(由 SIP 網關使用) +* 將目標 API 提升到 Android 14 diff --git a/fastlane/metadata/android/zh-TW/changelogs/4208804.txt b/fastlane/metadata/android/zh-TW/changelogs/4208804.txt new file mode 100644 index 0000000000000000000000000000000000000000..e79d703fc55d7df000cf154068783510da53c99b --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/4208804.txt @@ -0,0 +1,3 @@ +* 支持通過 WebRTC 數據通道進行 P2P 文件傳輸 +* 修復 ejabberd 上 Bind 2.0 的互操作性問題 +* 捆綁適用於 Android <= 7 的 Let’s Encrypt 根證書 diff --git a/fastlane/metadata/android/zh-TW/changelogs/4209004.txt b/fastlane/metadata/android/zh-TW/changelogs/4209004.txt new file mode 100644 index 0000000000000000000000000000000000000000..04c07d6464db84a2ece6a748b9c43aeea20837a3 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/4209004.txt @@ -0,0 +1,2 @@ +* 修正了一些小錯誤 +* Quicksy 嚮導流程略有修改 diff --git a/fastlane/metadata/android/zh-TW/changelogs/4209204.txt b/fastlane/metadata/android/zh-TW/changelogs/4209204.txt new file mode 100644 index 0000000000000000000000000000000000000000..792ee73cb9c228021d00a0db7b48d7e426051e6c --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/4209204.txt @@ -0,0 +1,2 @@ +* 在 Play 商店版本(Quicksy 和 Conversations)上提供對“隱私政策”的更輕鬆訪問 +* 移除 Play 商店版本的 Conversations 上的通訊錄整合功能 diff --git a/fastlane/metadata/android/zh-TW/changelogs/4209404.txt b/fastlane/metadata/android/zh-TW/changelogs/4209404.txt new file mode 100644 index 0000000000000000000000000000000000000000..22c8600901d1af35a352405a6995b4ef59d8b103 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/4209404.txt @@ -0,0 +1 @@ +* 修復 2.13.1 中出現的小問題 diff --git a/fastlane/metadata/android/zh-TW/changelogs/4210104.txt b/fastlane/metadata/android/zh-TW/changelogs/4210104.txt new file mode 100644 index 0000000000000000000000000000000000000000..3e6d47b6de00ff563984187653a6e13fb39cf0a5 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/4210104.txt @@ -0,0 +1 @@ +* 改進音頻/視頻通話與操作系統的整合 diff --git a/fastlane/metadata/android/zh-TW/changelogs/4210404.txt b/fastlane/metadata/android/zh-TW/changelogs/4210404.txt new file mode 100644 index 0000000000000000000000000000000000000000..541a5023027a1574aee3bc2267f1a4b9d36d2a67 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/4210404.txt @@ -0,0 +1,3 @@ +* 修復安卓 8 上的音頻/視頻通話 +* 修復新通話整合中的競態條件 +* 修復視頻壓縮問題 diff --git a/fastlane/metadata/android/zh-TW/changelogs/4210504.txt b/fastlane/metadata/android/zh-TW/changelogs/4210504.txt new file mode 100644 index 0000000000000000000000000000000000000000..238c4b60035c75afc8ff9f6053f32a7125a6dcb3 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/4210504.txt @@ -0,0 +1,2 @@ +* 恢復對 Android 6 和 Android 7 頻道探索的訪問 +* 改善失敗通話整合的日誌記錄 diff --git a/fastlane/metadata/android/zh-TW/changelogs/4210704.txt b/fastlane/metadata/android/zh-TW/changelogs/4210704.txt new file mode 100644 index 0000000000000000000000000000000000000000..30d613e89e659ac1f1388f908ed7003abdbbc0e4 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/4210704.txt @@ -0,0 +1,3 @@ +* 使用 Material 3 主題 +* 重新組織設置介面 +* 跨設備同步閱讀狀態 diff --git a/fastlane/metadata/android/zh-TW/changelogs/4210804.txt b/fastlane/metadata/android/zh-TW/changelogs/4210804.txt new file mode 100644 index 0000000000000000000000000000000000000000..9c727b051092d3715b0231e23810947ff763f98b --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/4210804.txt @@ -0,0 +1,2 @@ +* 以圖標形式顯示訊息狀態 +* 爲訊息氣泡引入「大字體」設置 diff --git a/fastlane/metadata/android/zh-TW/changelogs/4210904.txt b/fastlane/metadata/android/zh-TW/changelogs/4210904.txt new file mode 100644 index 0000000000000000000000000000000000000000..38b7f0ebc8e0d6af97c0625f05cf22b11e2ab697 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/4210904.txt @@ -0,0 +1,2 @@ +* 修復 Android 6/7 上的 Quicksy 註冊問題 +* 在通知通道上播放來電鈴聲 diff --git a/fastlane/metadata/android/zh-TW/changelogs/4211004.txt b/fastlane/metadata/android/zh-TW/changelogs/4211004.txt new file mode 100644 index 0000000000000000000000000000000000000000..5381a3950213bc42df70672f173871ca2b3c422b --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/4211004.txt @@ -0,0 +1,2 @@ +* 修復某些 Android 14 設備上的通話整合 +* 添加了「來自陌生人的邀請」設置 diff --git a/src/cheogram/java/com/cheogram/android/ConnectionService.java b/src/cheogram/java/com/cheogram/android/ConnectionService.java index 571bea29db861f3a0779714c3992b9e6c6e6030f..34419e45fecc5f397f3a7437641476fc1195bebc 100644 --- a/src/cheogram/java/com/cheogram/android/ConnectionService.java +++ b/src/cheogram/java/com/cheogram/android/ConnectionService.java @@ -45,10 +45,9 @@ import io.michaelrocks.libphonenumber.android.NumberParseException; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.CallIntegration; import eu.siacs.conversations.services.CallIntegrationConnectionService; -import eu.siacs.conversations.services.AvatarService; -import eu.siacs.conversations.services.EventReceiver; import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.RtpSessionActivity; @@ -57,6 +56,7 @@ import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; +import static eu.siacs.conversations.receiver.SystemEventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE; @RequiresApi(Build.VERSION_CODES.M) public class ConnectionService extends android.telecom.ConnectionService { @@ -79,7 +79,7 @@ public class ConnectionService extends android.telecom.ConnectionService { // From XmppActivity.connectToBackend Intent intent = new Intent(this, XmppConnectionService.class); intent.setAction(XmppConnectionService.ACTION_STARTING_CALL); - intent.putExtra(EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE, true); + intent.putExtra(EXTRA_NEEDS_FOREGROUND_SERVICE, true); try { startService(intent); } catch (IllegalStateException e) { diff --git a/src/cheogram/java/eu/siacs/conversations/services/ImportBackupService.java b/src/cheogram/java/eu/siacs/conversations/services/ImportBackupService.java index 16f9fc32b69545aca762f8e114a6eb9e2b00108c..1718e4c4652d610207efc6acc32b1f1463fbe5aa 100644 --- a/src/cheogram/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/src/cheogram/java/eu/siacs/conversations/services/ImportBackupService.java @@ -58,6 +58,7 @@ 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.worker.ExportBackupWorker; import eu.siacs.conversations.xmpp.Jid; public class ImportBackupService extends Service { @@ -239,7 +240,7 @@ public class ImportBackupService extends Service { return false; } - final byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt()); + final byte[] key = ExportBackupWorker.getKey(password, backupFileHeader.getSalt()); final AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); cipher.init(false, new AEADParameters(new KeyParameter(key), 128, backupFileHeader.getIv())); diff --git a/src/conversations/fastlane/metadata/android/tr-TR/full_description.txt b/src/conversations/fastlane/metadata/android/tr-TR/full_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..889d502e08270429fac00e4300d91ee3c6f61d81 --- /dev/null +++ b/src/conversations/fastlane/metadata/android/tr-TR/full_description.txt @@ -0,0 +1,39 @@ +Kullanımı kolay, güvenilir, pil ömrü dostu. Resimler, gruplar ve uçtan uca şifreleme için yerleşik destek. + +Tasarım ilkeleri: + +* Gizlilik ve güvenlikten tasarruf etmeden olabildiğince iyi görünümlü ve kolay kullanımlı olmak +* Halihazırda var olan, köklü protokollere dayanmak +* Bir Google hesabına, özellikle Google Bulut Mesajlaşması (GCM)'e, gerek duymamak +* Olabildiğince az izine gerek duymak + +Özellikler: + +* OMEMO veya OpenPGP ile uçtan uca şifreleme +* Fotoğraf gönderme ve alma +* Şifrelenmiş görüntülü ve sesli aramalar (DTLS-SRTP) +* Android tasarım standartlarına uyan öğrenmesi kolay arayüz +* Kişileriniz için profil fotoğrafları / Avatarlar +* Masaüstü uygulamasıyla senkronizasyon +* Konferanslar (yer imi desteği ile) +* Kişiler listesiyle entegrasyon +* Birden fazla hesap / Birleşik gelen kutusu +* Pil ömrüne çok düşük etki + +Conversations, kolayca ve ücretsiz olarak conversations.im sunucusunda hesap oluşturmanıza olanak tanır. Conversations başka herhangi bir XMPP sunucusuyla da çalışır. Çoğu XMPP sunucusu gönüllüler tarafından işletilir ve ücretsizdir. + +XMPP Özellikleri: + +Conversations var olan bütün XMPP sunucularıyla kullanılabilir. Ancak XMPP, eklentiler ile genişletilebilen bir protokoldür. Bu eklentiler XEP'ler olarak standardize edilmiştir. Conversations kullanıcı deneyimini iyileştirmek için bu eklentilerden birkaçını destekler. Kullandığınız XMPP sunucusu bu eklentileri desteklemiyor olabilir. Bu yüzden Conversations'tan en iyi şekilde faydalanmak için bu eklentileri destekleyen bir sunucuya geçmeli veya, daha da iyisi, siz ve arkadaşlarınız için kendi XMPP sunucunuzu kurmalısınız. + +Şimdilik bu XEP'ler: + +* XEP-0065: SOCKS5 Bytestreams (mod_proxy65). İki taraf da bir güvenlik duvarı (NAT) arkasında ise dosya aktarımı için kullanılacaktır. +* XEP-0163: Avatarlar için Kişisel Olay Protokolü (Personal Eventing Protocol) +* XEP-0191: Engelleme komutu - Spam atanları ve kişilerinizi listenizden kaldırmadan engellemenizi sağlar. +* XEP-0198: Akış Kontrolü (Stream Management) - XMPP'yi ve altındaki TCP bağlantısını küçük çaplı bağlantı kopmalarına karşı korur. +* XEP-0280: Mesaj Karbonları - Mesajlarınızı masaüstü uygulamasıyla senkronize ederek cihazlarınız arasında kesintisiz geçiş yapmanızı sağlar. +* XEP-0237: Roster Versioning (Liste Sürüm Takibi) - Zayıf mobil ağlarda bant aralığından tasarruf etmek amacıyla. +* XEP-0313: Mesaj Arşivi Yönetimi - Çevrimdışı olduğunuzda bile mesaj almaya devam edebilmeniz için mesajlarınızı sunucuyla senkronize eder. +* XEP-0352: İstemci Durum Bildirimi - Conversations'un arkaplanda çalıştığını sunucuya bildir. Sunucunun önemsiz paketleri saklayarak veriden tasarruf etmesini sağlar. +* XEP-0363: HTTP Dosya Yükleme - Konferanslarla ve çevrimdışı kişilerle dosya paylaşabilmenizi sağlar. Sunucunuzda ek bileşen gerektirir. diff --git a/src/conversations/fastlane/metadata/android/tr-TR/short_description.txt b/src/conversations/fastlane/metadata/android/tr-TR/short_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..1eb74ef8f8a3b66452e628c61ee1559a6a0a846a --- /dev/null +++ b/src/conversations/fastlane/metadata/android/tr-TR/short_description.txt @@ -0,0 +1 @@ +Mobil cihazınız için şifrelenmiş, kullanımı kolay bir XMPP mesajlaşma uygulaması diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java index 4c7387200e56f7a78eb4d06b1ceef2e01b13ee9e..17c76d167d3b7e1fdb22096ec4403eaa5f829d2d 100644 --- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java @@ -37,6 +37,7 @@ 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.worker.ExportBackupWorker; import eu.siacs.conversations.xmpp.Jid; import org.bouncycastle.crypto.engines.AESEngine; @@ -273,7 +274,7 @@ public class ImportBackupService extends Service { return false; } - final byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt()); + final byte[] key = ExportBackupWorker.getKey(password, backupFileHeader.getSalt()); final AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); cipher.init( diff --git a/src/conversations/res/values-fr/strings.xml b/src/conversations/res/values-fr/strings.xml index ba7122979650f86a7e3281a23475ab7d97be7625..68de25b54251a43abc609d88a7bb38fec262176c 100644 --- a/src/conversations/res/values-fr/strings.xml +++ b/src/conversations/res/values-fr/strings.xml @@ -12,6 +12,6 @@ Code de provisionnement mal formaté Appuyez sur le bouton partager pour envoyer à votre contact une invitation pour %1$s. Si vos contacts sont à proximité, ils peuvent aussi scanner le code ci-dessous pour accepter votre invitation. - Rejoignez %1$set discutez avec moi : %2$s + Rejoignez %1$s et discutez avec moi : %2$s Partager une invitation avec … \ 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 db1b0ac999b4b6ce09ee55526dd15203d390ff99..446c5485a9cc02a93a0e00de5994f52195f12b0b 100644 --- a/src/conversations/res/values-tr-rTR/strings.xml +++ b/src/conversations/res/values-tr-rTR/strings.xml @@ -3,14 +3,16 @@ XMPP sağlayıcınızı seçin conversations.im kullan Yeni hesap oluştur - Zaten bir XMPP hesabınız var mı? Bunun sebebi, zaten başka bir XMPP istemcisi kullanıyor oluşunuz veya Conversations\'ı önceden kullanmış olmanız olabilir. Eğer durum bu değilse şimdi yeni bir XMPP hesabı oluşturabilirsiniz.\nİpucu: Bağzı e-posta sağlayıcıları da XMPP hesapları kullanabilir. - XMPP; anlık yazışmalar için bağımsız bir sağlayıcıdır. Bu istemciyi istediğiniz herhangi bir XMPP sunucusu ile birlikte kullanabilirsiniz. -\nAncak kullanım rahatlığı adına sizin için conversations.im; Conversations için özellikle tasarlanmış bir sağlayıcıda hesap açmanızı kolaylaştırdık. - %1$s sağlayıcısına davet edildiniz. Sizi hesap oluşturulması konusunda yönlendireceğiz.\n%1$s bir sağlayıcı olark seçildiğinde, başka sağlayıcılar kullanan kullanıcılarla, onlara tam XMPP adresinizi vererek iletişim kurabileceksiniz. + Zaten bir XMPP hesabınız var mı? Bunun sebebi, zaten başka bir XMPP istemcisi kullanıyor oluşunuz veya Conversations\'ı önceden kullanmış olmanız olabilir. Eğer yoksa şimdi yeni bir XMPP hesabı oluşturabilirsiniz. +\nİpucu: Bazı e-posta sağlayıcıları da XMPP hesapları sağlayabilir. + XMPP, sağlayıcıdan bağımsız bir anlık mesajlaşma ağıdır. Bu uygulamayı seçtiğiniz herhangi bir sağlayıcıyla kullanabilirsiniz. +\nFakat, kullanım kolaylığı için, özellikle Conversations ile kullanılmak için tasarlanmış olan conversations.im sunucusunu da kullanabilirsiniz. + %1$s sağlayıcısına davet edildiniz. Sizi hesap oluşturulması konusunda yönlendireceğiz. +\n%1$s bir sağlayıcı olarak seçildiğinde, başka sağlayıcılar kullanan kullanıcılarla, onlara tam XMPP adresinizi vererek iletişim kurabileceksiniz. %1$s sağlayıcısına davet edildiniz. Sizin için zaten bir kullanıcı adı seçildi. Sizi hesap oluşturulması konusunda yönlendireceğiz.\nBaşka sağlayıcılar kullanan kullanıcılarla, onlara tam XMPP adresinizi vererek iletişim kurabileceksiniz. Sunucu davetiyeniz Yanlış ayarlanmış düzenleme kodu - Kişinize, %1$s grubuna davet etmek için Paylaş düğmesine basın. + Kişiyi %1$s\'e davet etmek için paylaş butonuna dokunun. Kişiniz yakınınızda ise, aşağıdaki kodu tarayak daveti kabul edebilirler. %1$s grubuna katıl ve benimle sohbet et: %2$s Daveti şununla paylaş… diff --git a/src/conversations/res/values-zh-rTW/strings.xml b/src/conversations/res/values-zh-rTW/strings.xml index de09fe8678196ca8bb913e6315821d577248f4d4..a356693d8db6bb284da60f225446c96e3e567f72 100644 --- a/src/conversations/res/values-zh-rTW/strings.xml +++ b/src/conversations/res/values-zh-rTW/strings.xml @@ -5,7 +5,7 @@ 建立新帳戶 您已經擁有一個 XMPP 帳戶了嗎?如果您之前使用過其他 XMPP 用戶端或 Conversations 的話,那麼您已經擁有 XMPP 帳戶了。若沒有,您現在就建立一個新的 XMPP 帳戶。 \n提示:部分電子郵件提供者也會提供 XMPP 帳戶。 - XMPP 是提供者無關的即時訊息網路。任何您選擇的 XMPP 伺服器都可在此用戶端上使用。 + XMPP 是獨立於服務提供者的即時訊息網路。任何您選擇的 XMPP 伺服器都可在此用戶端上使用。 \n不過,我們令它在 Coversations.im 中建立帳戶變得更方便;conversations.im 是特別適合 Conversations 的提供者。 你已受邀參加 %1$s 。我們將指引您完成建立帳戶的過程。 \n選擇 %1$s 作為提供者後,您可以將您完整的 XMPP 位址交給使用其他提供者的使用者,以便能與他們進行交流。 diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 392db79e6ad0110e2fb8378f7c750086b3992ca1..a0eb77b17d895700925c8d43ed1f7fb818016117 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -116,9 +116,9 @@ + android:name="androidx.work.impl.foreground.SystemForegroundService" + android:foregroundServiceType="dataSync" + tools:node="merge" /> + + @@ -157,7 +161,7 @@ diff --git a/src/main/java/eu/siacs/conversations/services/EventReceiver.java b/src/main/java/eu/siacs/conversations/receiver/SystemEventReceiver.java similarity index 89% rename from src/main/java/eu/siacs/conversations/services/EventReceiver.java rename to src/main/java/eu/siacs/conversations/receiver/SystemEventReceiver.java index b189e9a9e0acee112fd7cc2319ba7a6e1b7265f5..3efa9b67e31a3aa26eaf893158d92639fd521dc3 100644 --- a/src/main/java/eu/siacs/conversations/services/EventReceiver.java +++ b/src/main/java/eu/siacs/conversations/receiver/SystemEventReceiver.java @@ -1,4 +1,4 @@ -package eu.siacs.conversations.services; +package eu.siacs.conversations.receiver; import android.content.BroadcastReceiver; import android.content.Context; @@ -10,9 +10,10 @@ import android.util.Log; import com.google.common.base.Strings; import eu.siacs.conversations.Config; +import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.Compatibility; -public class EventReceiver extends BroadcastReceiver { +public class SystemEventReceiver extends BroadcastReceiver { public static final String SETTING_ENABLED_ACCOUNTS = "enabled_accounts"; public static final String EXTRA_NEEDS_FOREGROUND_SERVICE = "needs_foreground_service"; diff --git a/src/main/java/eu/siacs/conversations/services/UnifiedPushDistributor.java b/src/main/java/eu/siacs/conversations/receiver/UnifiedPushDistributor.java similarity index 85% rename from src/main/java/eu/siacs/conversations/services/UnifiedPushDistributor.java rename to src/main/java/eu/siacs/conversations/receiver/UnifiedPushDistributor.java index b47a61a531fdc5b4afcadd2f540acf8b803000c3..ace71ddb589ff9544d2dacb27811ad9f96b1245f 100644 --- a/src/main/java/eu/siacs/conversations/services/UnifiedPushDistributor.java +++ b/src/main/java/eu/siacs/conversations/receiver/UnifiedPushDistributor.java @@ -1,4 +1,4 @@ -package eu.siacs.conversations.services; +package eu.siacs.conversations.receiver; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -25,6 +25,7 @@ import java.util.List; import eu.siacs.conversations.Config; import eu.siacs.conversations.persistance.UnifiedPushDatabase; +import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.Compatibility; public class UnifiedPushDistributor extends BroadcastReceiver { @@ -35,9 +36,9 @@ public class UnifiedPushDistributor extends BroadcastReceiver { public static final String ACTION_REGISTER = "org.unifiedpush.android.distributor.REGISTER"; public static final String ACTION_UNREGISTER = "org.unifiedpush.android.distributor.UNREGISTER"; - // connector actions (these are actions used for distributor->connector broadcasts) - public static final String ACTION_UNREGISTERED = "org.unifiedpush.android.connector.UNREGISTERED"; + public static final String ACTION_UNREGISTERED = + "org.unifiedpush.android.connector.UNREGISTERED"; public static final String ACTION_BYTE_MESSAGE = "org.unifiedpush.android.distributor.feature.BYTES_MESSAGE"; public static final String ACTION_REGISTRATION_FAILED = @@ -69,7 +70,7 @@ public class UnifiedPushDistributor extends BroadcastReceiver { final Parcelable appVerification = intent.getParcelableExtra("app"); if (appVerification instanceof PendingIntent pendingIntent) { application = pendingIntent.getIntentSender().getCreatorPackage(); - Log.d(Config.LOGTAG,"received application name via pending intent "+ application); + Log.d(Config.LOGTAG, "received application name via pending intent " + application); } else { application = intent.getStringExtra("application"); } @@ -79,10 +80,10 @@ public class UnifiedPushDistributor extends BroadcastReceiver { switch (Strings.nullToEmpty(action)) { case ACTION_REGISTER -> register(context, application, instance, features, messenger); case ACTION_UNREGISTER -> unregister(context, instance); - case Intent.ACTION_PACKAGE_FULLY_REMOVED -> - unregisterApplication(context, intent.getData()); - default -> - Log.d(Config.LOGTAG, "UnifiedPushDistributor received unknown action " + action); + case Intent.ACTION_PACKAGE_FULLY_REMOVED -> unregisterApplication( + context, intent.getData()); + default -> Log.d( + Config.LOGTAG, "UnifiedPushDistributor received unknown action " + action); } } @@ -111,7 +112,11 @@ public class UnifiedPushDistributor extends BroadcastReceiver { Log.d( Config.LOGTAG, "successfully created UnifiedPush entry. waking up XmppConnectionService"); - quickLog(context, String.format("successfully registered %s (token = %s) for UnifiedPushed", application, instance)); + quickLog( + context, + String.format( + "successfully registered %s (token = %s) for UnifiedPushed", + application, instance)); final Intent serviceIntent = new Intent(context, XmppConnectionService.class); serviceIntent.setAction(XmppConnectionService.ACTION_RENEW_UNIFIED_PUSH_ENDPOINTS); serviceIntent.putExtra("instance", instance); @@ -140,7 +145,7 @@ public class UnifiedPushDistributor extends BroadcastReceiver { } } else { if (messenger instanceof Messenger m) { - sendRegistrationFailed(m,"Your application is not registered to receive messages"); + sendRegistrationFailed(m, "Your application is not registered to receive messages"); } Log.d( Config.LOGTAG, @@ -157,7 +162,7 @@ public class UnifiedPushDistributor extends BroadcastReceiver { try { messenger.send(message); } catch (final RemoteException e) { - Log.d(Config.LOGTAG,"unable to tell messenger of failed registration",e); + Log.d(Config.LOGTAG, "unable to tell messenger of failed registration", e); } } @@ -177,7 +182,11 @@ public class UnifiedPushDistributor extends BroadcastReceiver { } final UnifiedPushDatabase unifiedPushDatabase = UnifiedPushDatabase.getInstance(context); if (unifiedPushDatabase.deleteInstance(instance)) { - quickLog(context, String.format("successfully unregistered token %s from UnifiedPushed (application requested unregister)", instance)); + quickLog( + context, + String.format( + "successfully unregistered token %s from UnifiedPushed (application requested unregister)", + instance)); Log.d(Config.LOGTAG, "successfully removed " + instance + " from UnifiedPush"); // TODO send UNREGISTERED broadcast back to app?! } @@ -192,7 +201,11 @@ public class UnifiedPushDistributor extends BroadcastReceiver { Log.d(Config.LOGTAG, "app " + application + " has been removed from the system"); final UnifiedPushDatabase database = UnifiedPushDatabase.getInstance(context); if (database.deleteApplication(application)) { - quickLog(context, String.format("successfully removed %s from UnifiedPushed (ACTION_PACKAGE_FULLY_REMOVED)", application)); + quickLog( + context, + String.format( + "successfully removed %s from UnifiedPushed (ACTION_PACKAGE_FULLY_REMOVED)", + application)); Log.d(Config.LOGTAG, "successfully removed " + application + " from UnifiedPush"); } } @@ -210,6 +223,6 @@ public class UnifiedPushDistributor extends BroadcastReceiver { final Intent intent = new Intent(context, XmppConnectionService.class); intent.setAction(XmppConnectionService.ACTION_QUICK_LOG); intent.putExtra("message", message); - context.startService(intent); + Compatibility.startService(context, intent); } } diff --git a/src/main/java/eu/siacs/conversations/receiver/WorkManagerEventReceiver.java b/src/main/java/eu/siacs/conversations/receiver/WorkManagerEventReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..71ec74f53bf121ab981a37d8d4ae07a14245c19d --- /dev/null +++ b/src/main/java/eu/siacs/conversations/receiver/WorkManagerEventReceiver.java @@ -0,0 +1,32 @@ +package eu.siacs.conversations.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import androidx.work.WorkManager; + +import com.google.common.base.Strings; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.ui.fragment.settings.BackupSettingsFragment; + +public class WorkManagerEventReceiver extends BroadcastReceiver { + + public static final String ACTION_STOP_BACKUP = "eu.siacs.conversations.receiver.STOP_BACKUP"; + + @Override + public void onReceive(final Context context, final Intent intent) { + final var action = Strings.nullToEmpty(intent == null ? null : intent.getAction()); + if (action.equals(ACTION_STOP_BACKUP)) { + stopBackup(context); + } + } + + private void stopBackup(final Context context) { + Log.d(Config.LOGTAG, "trying to stop one-off backup worker"); + final var workManager = WorkManager.getInstance(context); + workManager.cancelUniqueWork(BackupSettingsFragment.CREATE_ONE_OFF_BACKUP); + } +} diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index 8e09a8081935e2bbc37dbe7603cacf3b9c537eba..fdcfbd234929fe7671bb8d24035f92d305c03628 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegration.java @@ -38,15 +38,8 @@ public class CallIntegration extends Connection { private static final List BROKEN_DEVICE_MODELS = Arrays.asList( - "OnePlus6", // OnePlus 6 (Android 8.1-11) Device is buggy and always starts the - // operating system call screen even though we want to be self - // managed - "RMX1921", // Realme XT (Android 9-10) shows "Call not sent" dialog - "RMX1971", // Realme 5 Pro (Android 9-11), show "Call not sent" dialog - "RMX1973", // Realme 5 Pro (see above), - "RMX2071", // Realme X50 Pro 5G (Call not sent) - "RMX2075L1", // Realme X50 Pro 5G - "RMX2076" // Realme X50 Pro 5G + "OnePlus6" // OnePlus 6 (Android 8.1-11) Device is buggy and always starts the + // OS call screen even though we want to be self managed ); public static final int DEFAULT_TONE_VOLUME = 60; @@ -63,6 +56,7 @@ public class CallIntegration extends Connection { private final AtomicBoolean isDestroyed = new AtomicBoolean(false); private List availableEndpoints = Collections.emptyList(); + private boolean isMicrophoneEnabled = true; private Callback callback = null; @@ -81,6 +75,7 @@ public class CallIntegration extends Connection { this.appRTCAudioManager.setAudioManagerEvents(this::onAudioDeviceChanged); } setRingbackRequested(true); + setConnectionCapabilities(CAPABILITY_MUTE | CAPABILITY_RESPOND_VIA_TEXT); } public void setCallback(final Callback callback) { @@ -151,10 +146,26 @@ public class CallIntegration extends Connection { Log.d(Config.LOGTAG, "ignoring onCallAudioStateChange() on Upside Down Cake"); return; } + setMicrophoneEnabled(!state.isMuted()); Log.d(Config.LOGTAG, "onCallAudioStateChange(" + state + ")"); this.onAudioDeviceChanged(getAudioDeviceOreo(state), getAudioDevicesOreo(state)); } + @Override + public void onMuteStateChanged(final boolean isMuted) { + Log.d(Config.LOGTAG, "onMuteStateChanged(" + isMuted + ")"); + setMicrophoneEnabled(!isMuted); + } + + private void setMicrophoneEnabled(final boolean enabled) { + this.isMicrophoneEnabled = enabled; + this.callback.onCallIntegrationMicrophoneEnabled(enabled); + } + + public boolean isMicrophoneEnabled() { + return this.isMicrophoneEnabled; + } + public Set getAudioDevices() { if (notSelfManaged(context)) { return getAudioDevicesFallback(); @@ -496,7 +507,7 @@ public class CallIntegration extends Connection { public static boolean selfManaged(final Context context) { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasSystemFeature(context) - && !BROKEN_DEVICE_MODELS.contains(Build.DEVICE); + && isDeviceModelSupported(); } public static boolean hasSystemFeature(final Context context) { @@ -509,6 +520,18 @@ public class CallIntegration extends Connection { } } + private static boolean isDeviceModelSupported() { + if (BROKEN_DEVICE_MODELS.contains(Build.DEVICE)) { + return false; + } + // all Realme devices at least up to and including Android 11 are broken + if ("realme".equalsIgnoreCase(Build.MANUFACTURER) + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + return false; + } + return true; + } + public static boolean notSelfManaged(final Context context) { return !selfManaged(context); } @@ -579,6 +602,8 @@ public class CallIntegration extends Connection { void onCallIntegrationSilence(); + void onCallIntegrationMicrophoneEnabled(boolean enabled); + boolean applyDtmfTone(final String dtmf); } } diff --git a/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java b/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java index 49ffeab513077ef605b0d1a060e0feff2a57e663..48f23c3fa471fcc00961607289653b7a1b590e10 100644 --- a/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java +++ b/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java @@ -22,6 +22,7 @@ import java.util.List; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.receiver.SystemEventReceiver; import eu.siacs.conversations.ui.ConversationsActivity; import eu.siacs.conversations.utils.Compatibility; @@ -45,7 +46,7 @@ public class ContactChooserTargetService extends ChooserTargetService implements @Override public List onGetChooserTargets( final ComponentName targetActivityName, final IntentFilter matchedFilter) { - if (!EventReceiver.hasEnabledAccounts(this)) { + if (!SystemEventReceiver.hasEnabledAccounts(this)) { return Collections.emptyList(); } final Intent intent = new Intent(this, XmppConnectionService.class); diff --git a/src/main/java/eu/siacs/conversations/services/ExportBackupService.java b/src/main/java/eu/siacs/conversations/services/ExportBackupService.java deleted file mode 100644 index 845faa982f790475e77707b2e1fed9a406e8bebf..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/services/ExportBackupService.java +++ /dev/null @@ -1,469 +0,0 @@ -package eu.siacs.conversations.services; - -import static eu.siacs.conversations.utils.Compatibility.s; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.os.IBinder; -import android.util.Log; - -import androidx.core.app.NotificationCompat; - -import com.google.common.base.CharMatcher; -import com.google.common.base.Strings; - -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.zip.GZIPOutputStream; - -import javax.crypto.Cipher; -import javax.crypto.CipherOutputStream; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; - -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.utils.BackupFileHeader; -import eu.siacs.conversations.utils.Compatibility; - -public class ExportBackupService extends Service { - - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US); - - public static final String KEYTYPE = "AES"; - public static final String CIPHERMODE = "AES/GCM/NoPadding"; - public static final String PROVIDER = "BC"; - - public static final String MIME_TYPE = "application/vnd.conversations.backup"; - - private static final int NOTIFICATION_ID = 19; - private static final int PAGE_SIZE = 20; - private static final AtomicBoolean RUNNING = new AtomicBoolean(false); - private DatabaseBackend mDatabaseBackend; - private List mAccounts; - private NotificationManager notificationManager; - - private static List getPossibleFileOpenIntents(final Context context, final String path) { - - //http://www.openintents.org/action/android-intent-action-view/file-directory - //do not use 'vnd.android.document/directory' since this will trigger system file manager - final Intent openIntent = new Intent(Intent.ACTION_VIEW); - openIntent.addCategory(Intent.CATEGORY_DEFAULT); - if (Compatibility.runsAndTargetsTwentyFour(context)) { - openIntent.setType("resource/folder"); - } else { - openIntent.setDataAndType(Uri.parse("file://" + path), "resource/folder"); - } - openIntent.putExtra("org.openintents.extra.ABSOLUTE_PATH", path); - - final Intent amazeIntent = new Intent(Intent.ACTION_VIEW); - amazeIntent.setDataAndType(Uri.parse("com.amaze.filemanager:" + path), "resource/folder"); - - //will open a file manager at root and user can navigate themselves - final Intent systemFallBack = new Intent(Intent.ACTION_VIEW); - systemFallBack.addCategory(Intent.CATEGORY_DEFAULT); - systemFallBack.setData(Uri.parse("content://com.android.externalstorage.documents/root/primary")); - - return Arrays.asList(openIntent, amazeIntent, systemFallBack); - } - - private static void accountExport(final SQLiteDatabase db, final String uuid, final PrintWriter writer) { - final StringBuilder builder = new StringBuilder(); - final Cursor accountCursor = db.query(Account.TABLENAME, null, Account.UUID + "=?", new String[]{uuid}, null, null, null); - while (accountCursor != null && accountCursor.moveToNext()) { - builder.append("INSERT INTO ").append(Account.TABLENAME).append("("); - for (int i = 0; i < accountCursor.getColumnCount(); ++i) { - if (i != 0) { - builder.append(','); - } - builder.append(accountCursor.getColumnName(i)); - } - builder.append(") VALUES("); - for (int i = 0; i < accountCursor.getColumnCount(); ++i) { - if (i != 0) { - builder.append(','); - } - final String value = accountCursor.getString(i); - if (value == null || Account.ROSTERVERSION.equals(accountCursor.getColumnName(i))) { - builder.append("NULL"); - } else if (Account.OPTIONS.equals(accountCursor.getColumnName(i)) && value.matches("\\d+")) { - int intValue = Integer.parseInt(value); - intValue |= 1 << Account.OPTION_DISABLED; - builder.append(intValue); - } else { - appendEscapedSQLString(builder, value); - } - } - builder.append(")"); - builder.append(';'); - builder.append('\n'); - } - if (accountCursor != null) { - accountCursor.close(); - } - writer.append(builder.toString()); - } - - private static void appendEscapedSQLString(final StringBuilder sb, final String sqlString) { - DatabaseUtils.appendEscapedSQLString(sb, CharMatcher.is('\u0000').removeFrom(sqlString)); - } - - private static void simpleExport(SQLiteDatabase db, String table, String column, String uuid, PrintWriter writer) { - final Cursor cursor = db.query(table, null, column + "=?", new String[]{uuid}, null, null, null); - while (cursor != null && cursor.moveToNext()) { - writer.write(cursorToString(table, cursor, PAGE_SIZE)); - } - if (cursor != null) { - cursor.close(); - } - } - - public static byte[] getKey(final String password, final byte[] salt) throws InvalidKeySpecException { - final SecretKeyFactory factory; - try { - factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(e); - } - return factory.generateSecret(new PBEKeySpec(password.toCharArray(), salt, 1024, 128)).getEncoded(); - } - - private static String cursorToString(final String table, final Cursor cursor, final int max) { - return cursorToString(table, cursor, max, false); - } - - private static String cursorToString(final String table, final Cursor cursor, int max, boolean ignore) { - final boolean identities = SQLiteAxolotlStore.IDENTITIES_TABLENAME.equals(table); - StringBuilder builder = new StringBuilder(); - builder.append("INSERT "); - if (ignore) { - builder.append("OR IGNORE "); - } - builder.append("INTO ").append(table).append("("); - int skipColumn = -1; - for (int i = 0; i < cursor.getColumnCount(); ++i) { - final String name = cursor.getColumnName(i); - if (identities && SQLiteAxolotlStore.TRUSTED.equals(name)) { - skipColumn = i; - continue; - } - if (i != 0) { - builder.append(','); - } - builder.append(name); - } - builder.append(") VALUES"); - for (int i = 0; i < max; ++i) { - if (i != 0) { - builder.append(','); - } - appendValues(cursor, builder, skipColumn); - if (i < max - 1 && !cursor.moveToNext()) { - break; - } - } - builder.append(';'); - builder.append('\n'); - return builder.toString(); - } - - private static void appendValues(final Cursor cursor, final StringBuilder builder, final int skipColumn) { - builder.append("("); - for (int i = 0; i < cursor.getColumnCount(); ++i) { - if (i == skipColumn) { - continue; - } - if (i != 0) { - builder.append(','); - } - final String value = cursor.getString(i); - if (value == null) { - builder.append("NULL"); - } else if (value.matches("[0-9]+")) { - builder.append(value); - } else { - appendEscapedSQLString(builder, value); - } - } - builder.append(")"); - - } - - @Override - public void onCreate() { - mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext()); - mAccounts = mDatabaseBackend.getAccounts(); - notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (RUNNING.compareAndSet(false, true)) { - new Thread(() -> { - boolean success; - List files; - try { - files = export(intent.getBooleanExtra("cheogram_db", true)); - success = true; - } catch (final Exception e) { - Log.d(Config.LOGTAG, "unable to create backup", e); - success = false; - files = Collections.emptyList(); - } - stopForeground(true); - RUNNING.set(false); - if (success) { - notifySuccess(files); - } - stopSelf(); - }).start(); - return START_STICKY; - } else { - Log.d(Config.LOGTAG, "ExportBackupService. ignoring start command because already running"); - } - return START_NOT_STICKY; - } - - private void messageExport(SQLiteDatabase db, String uuid, PrintWriter writer, Progress progress) { - Cursor cursor = db.rawQuery("select messages.* from messages join conversations on conversations.uuid=messages.conversationUuid where conversations.accountUuid=?", new String[]{uuid}); - int size = cursor != null ? cursor.getCount() : 0; - Log.d(Config.LOGTAG, "exporting " + size + " messages for account " + uuid); - int i = 0; - int p = 0; - while (cursor != null && cursor.moveToNext()) { - writer.write(cursorToString(Message.TABLENAME, cursor, PAGE_SIZE, false)); - if (i + PAGE_SIZE > size) { - i = size; - } else { - i += PAGE_SIZE; - } - final int percentage = i * 100 / size; - if (p < percentage) { - p = percentage; - notificationManager.notify(NOTIFICATION_ID, progress.build(p)); - } - } - if (cursor != null) { - cursor.close(); - } - } - - private void messageExportCheogram(SQLiteDatabase db, String uuid, PrintWriter writer, Progress progress) { - Cursor cursor = db.rawQuery("select cmessages.* from messages join cheogram.messages cmessages using (uuid) join conversations on conversations.uuid=messages.conversationUuid where conversations.accountUuid=?", new String[]{uuid}); - int size = cursor != null ? cursor.getCount() : 0; - Log.d(Config.LOGTAG, "exporting " + size + " cheogram messages for account " + uuid); - int i = 0; - int p = 0; - while (cursor != null && cursor.moveToNext()) { - writer.write(cursorToString("cheogram." + Message.TABLENAME, cursor, PAGE_SIZE, false)); - if (i + PAGE_SIZE > size) { - i = size; - } else { - i += PAGE_SIZE; - } - final int percentage = i * 100 / size; - if (p < percentage) { - p = percentage; - notificationManager.notify(NOTIFICATION_ID, progress.build(p)); - } - } - if (cursor != null) { - cursor.close(); - } - - cursor = db.rawQuery("select webxdc_updates.* from " + Conversation.TABLENAME + " join cheogram.webxdc_updates webxdc_updates on " + Conversation.TABLENAME + ".uuid=webxdc_updates." + Message.CONVERSATION + " where conversations.accountUuid=?", new String[]{uuid}); - size = cursor != null ? cursor.getCount() : 0; - Log.d(Config.LOGTAG, "exporting " + size + " WebXDC updates for account " + uuid); - while (cursor != null && cursor.moveToNext()) { - writer.write(cursorToString("cheogram.webxdc_updates", cursor, PAGE_SIZE, false)); - if (i + PAGE_SIZE > size) { - i = size; - } else { - i += PAGE_SIZE; - } - final int percentage = i * 100 / size; - if (p < percentage) { - p = percentage; - notificationManager.notify(NOTIFICATION_ID, progress.build(p)); - } - } - if (cursor != null) { - cursor.close(); - } - } - - private List export(boolean withCheogramDb) throws Exception { - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup"); - mBuilder.setContentTitle(getString(R.string.notification_create_backup_title)) - .setSmallIcon(R.drawable.ic_archive_24dp) - .setProgress(1, 0, false); - startForeground(NOTIFICATION_ID, mBuilder.build()); - int count = 0; - final int max = this.mAccounts.size(); - final SecureRandom secureRandom = new SecureRandom(); - final List files = new ArrayList<>(); - Log.d(Config.LOGTAG, "starting backup for " + max + " accounts"); - for (final Account account : this.mAccounts) { - final String password = account.getPassword(); - if (Strings.nullToEmpty(password).trim().isEmpty()) { - Log.d(Config.LOGTAG, String.format("skipping backup for %s because password is empty. unable to encrypt", account.getJid().asBareJid())); - continue; - } - Log.d(Config.LOGTAG, String.format("exporting data for account %s (%s)", account.getJid().asBareJid(), account.getUuid())); - final byte[] IV = new byte[12]; - final byte[] salt = new byte[16]; - secureRandom.nextBytes(IV); - secureRandom.nextBytes(salt); - final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name), account.getJid(), System.currentTimeMillis(), IV, salt); - final Progress progress = new Progress(mBuilder, max, count); - final String filename = - String.format( - "%s.%s.ceb", - account.getJid().asBareJid().toEscapedString(), - DATE_FORMAT.format(new Date())); - final File file = - new File( - FileBackend.getBackupDirectory(this), filename); - files.add(file); - final File directory = file.getParentFile(); - if (directory != null && directory.mkdirs()) { - Log.d(Config.LOGTAG, "created backup directory " + directory.getAbsolutePath()); - } - final FileOutputStream fileOutputStream = new FileOutputStream(file); - final DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream); - backupFileHeader.write(dataOutputStream); - dataOutputStream.flush(); - - final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); - final byte[] key = getKey(password, salt); - SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); - IvParameterSpec ivSpec = new IvParameterSpec(IV); - cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); - CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher); - - GZIPOutputStream gzipOutputStream = new GZIPOutputStream(cipherOutputStream); - PrintWriter writer = new PrintWriter(gzipOutputStream); - SQLiteDatabase db = this.mDatabaseBackend.getReadableDatabase(); - final String uuid = account.getUuid(); - accountExport(db, uuid, writer); - simpleExport(db, Conversation.TABLENAME, Conversation.ACCOUNT, uuid, writer); - messageExport(db, uuid, writer, progress); - if (withCheogramDb) messageExportCheogram(db, uuid, writer, progress); - for (String table : Arrays.asList(SQLiteAxolotlStore.PREKEY_TABLENAME, SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, SQLiteAxolotlStore.SESSION_TABLENAME, SQLiteAxolotlStore.IDENTITIES_TABLENAME)) { - simpleExport(db, table, SQLiteAxolotlStore.ACCOUNT, uuid, writer); - } - writer.flush(); - writer.close(); - mediaScannerScanFile(file); - Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile()); - count++; - } - return files; - } - - private void mediaScannerScanFile(final File file) { - final Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); - intent.setData(Uri.fromFile(file)); - sendBroadcast(intent); - } - - private void notifySuccess(final List files) { - final String path = FileBackend.getBackupDirectory(this).getAbsolutePath(); - - PendingIntent openFolderIntent = null; - - for (final Intent intent : getPossibleFileOpenIntents(this, path)) { - if (intent.resolveActivityInfo(getPackageManager(), 0) != null) { - openFolderIntent = PendingIntent.getActivity(this, 189, intent, s() - ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT - : PendingIntent.FLAG_UPDATE_CURRENT); - break; - } - } - - PendingIntent shareFilesIntent = null; - if (files.size() > 0) { - final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); - ArrayList uris = new ArrayList<>(); - for (File file : files) { - uris.add(FileBackend.getUriForFile(this, file)); - } - intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.setType(MIME_TYPE); - final Intent chooser = Intent.createChooser(intent, getString(R.string.share_backup_files)); - shareFilesIntent = PendingIntent.getActivity(this, 190, chooser, s() - ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT - : PendingIntent.FLAG_UPDATE_CURRENT); - } - - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup"); - mBuilder.setContentTitle(getString(R.string.notification_backup_created_title)) - .setContentText(getString(R.string.notification_backup_created_subtitle, path)) - .setStyle(new NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_backup_created_subtitle, FileBackend.getBackupDirectory(this).getAbsolutePath()))) - .setAutoCancel(true) - .setContentIntent(openFolderIntent) - .setSmallIcon(R.drawable.ic_archive_24dp); - - if (shareFilesIntent != null) { - mBuilder.addAction( - R.drawable.ic_share_24dp, - getString(R.string.share_backup_files), - shareFilesIntent); - } - - try { Thread.sleep(500); } catch (final Exception e) { } - notificationManager.notify(NOTIFICATION_ID, mBuilder.build()); - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - private static class Progress { - private final NotificationCompat.Builder builder; - private final int max; - private final int count; - - private Progress(NotificationCompat.Builder builder, int max, int count) { - this.builder = builder; - this.max = max; - this.count = count; - } - - private Notification build(int percentage) { - builder.setProgress(max * 100, count * 100 + percentage, false); - return builder.build(); - } - } -} diff --git a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java index bfa1785f11f855e11c1c9531331c672ce20f2431..4aab05ceeb00ddefc6d366c1dfd3e94ac4e7036b 100644 --- a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java +++ b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java @@ -28,6 +28,7 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.parser.AbstractParser; import eu.siacs.conversations.persistance.UnifiedPushDatabase; +import eu.siacs.conversations.receiver.UnifiedPushDistributor; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 8a7e2006373fdc0470cba57a000442bb0d6fdbba..801a2e7442ca373fc4e9bc68192806eec819625b 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -149,6 +149,7 @@ import eu.siacs.conversations.parser.PresenceParser; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.UnifiedPushDatabase; +import eu.siacs.conversations.receiver.SystemEventReceiver; import eu.siacs.conversations.ui.ChooseAccountForProfilePictureActivity; import eu.siacs.conversations.ui.ConversationsActivity; import eu.siacs.conversations.ui.RtpSessionActivity; @@ -855,7 +856,7 @@ public class XmppConnectionService extends Service { @Override 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); + final boolean needsForegroundService = intent != null && intent.getBooleanExtra(SystemEventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE, false); if (needsForegroundService) { Log.d(Config.LOGTAG, "toggle forced foreground service after receiving event (action=" + action + ")"); toggleForegroundService(true, action.equals(ACTION_STARTING_CALL)); @@ -1497,7 +1498,7 @@ public class XmppConnectionService extends Service { } final SharedPreferences.Editor editor = getPreferences().edit(); final boolean hasEnabledAccounts = hasEnabledAccounts(); - editor.putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply(); + editor.putBoolean(SystemEventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply(); editor.apply(); toggleSetProfilePictureActivity(hasEnabledAccounts); reconfigurePushDistributor(); @@ -1795,7 +1796,7 @@ public class XmppConnectionService extends Service { return; } final long triggerAtMillis = SystemClock.elapsedRealtime() + (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL * 1000); - final Intent intent = new Intent(this, EventReceiver.class); + final Intent intent = new Intent(this, SystemEventReceiver.class); intent.setAction(ACTION_POST_CONNECTIVITY_CHANGE); try { final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, s() @@ -1817,7 +1818,7 @@ public class XmppConnectionService extends Service { if (alarmManager == null) { return; } - final Intent intent = new Intent(this, EventReceiver.class); + final Intent intent = new Intent(this, SystemEventReceiver.class); intent.setAction(ACTION_PING); try { final PendingIntent pendingIntent = @@ -1836,7 +1837,7 @@ public class XmppConnectionService extends Service { if (alarmManager == null) { return; } - final Intent intent = new Intent(this, EventReceiver.class); + final Intent intent = new Intent(this, SystemEventReceiver.class); intent.setAction(ACTION_IDLE_PING); try { final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, s() @@ -3008,7 +3009,7 @@ public class XmppConnectionService extends Service { private void syncEnabledAccountSetting() { final boolean hasEnabledAccounts = hasEnabledAccounts(); - getPreferences().edit().putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply(); + getPreferences().edit().putBoolean(SystemEventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply(); toggleSetProfilePictureActivity(hasEnabledAccounts); } @@ -3198,7 +3199,9 @@ public class XmppConnectionService extends Service { }; mDatabaseWriterExecutor.execute(runnable); this.accounts.remove(account); - CallIntegrationConnectionService.unregisterPhoneAccount(this, account); + if (CallIntegration.hasSystemFeature(this)) { + CallIntegrationConnectionService.unregisterPhoneAccount(this, account); + } this.mRosterSyncTaskManager.clear(account); updateAccountUi(); mNotificationService.updateErrorNotification(); diff --git a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java index 9be04d05885845be804046e241b7e695f22e3986..fdca161d8db00853c874936971ed4922f67659bc 100644 --- a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java @@ -113,6 +113,9 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe mRecorder = new MediaRecorder(); mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); final String userChosenCodec = getPreferences().getString("voice_message_codec", ""); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + mRecorder.setPrivacySensitive(true); + } final int outputFormat; if (("opus".equals(userChosenCodec) || ("".equals(userChosenCodec) && Config.USE_OPUS_VOICE_MESSAGES)) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { outputFormat = MediaRecorder.OutputFormat.OGG; @@ -122,14 +125,14 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe } else if ("mpeg4".equals(userChosenCodec) || !Config.USE_OPUS_VOICE_MESSAGES) { outputFormat = MediaRecorder.OutputFormat.MPEG_4; mRecorder.setOutputFormat(outputFormat); - if (AAC_SENSITIVE_DEVICES.contains(Build.MODEL)) { - // Changing these three settings for AAC sensitive devices might lead to sporadically truncated (cut-off) voice messages. + if (AAC_SENSITIVE_DEVICES.contains(Build.MODEL) && Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) { + // Changing these three settings for AAC sensitive devices for Android<=13 might lead to sporadically truncated (cut-off) voice messages. mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC); mRecorder.setAudioSamplingRate(24_000); mRecorder.setAudioEncodingBitRate(28_000); } else { mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); - mRecorder.setAudioSamplingRate(22_050); + mRecorder.setAudioSamplingRate(44_100); mRecorder.setAudioEncodingBitRate(64_000); } } else { diff --git a/src/main/java/eu/siacs/conversations/ui/activity/result/PickRingtone.java b/src/main/java/eu/siacs/conversations/ui/activity/result/PickRingtone.java index b58a7f3982eee98cf9782a2caada0344e10cfd32..975ba9431fcf1712947592d17ba027a0793f2a20 100644 --- a/src/main/java/eu/siacs/conversations/ui/activity/result/PickRingtone.java +++ b/src/main/java/eu/siacs/conversations/ui/activity/result/PickRingtone.java @@ -5,6 +5,7 @@ import android.content.Context; import android.content.Intent; import android.media.RingtoneManager; import android.net.Uri; + import androidx.activity.result.contract.ActivityResultContract; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -26,7 +27,7 @@ public class PickRingtone extends ActivityResultContract { intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringToneType); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); - if (existing != null) { + if (noneToNull(existing) != null) { intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, existing); } return intent; @@ -37,11 +38,14 @@ public class PickRingtone extends ActivityResultContract { if (resultCode != Activity.RESULT_OK || data == null) { return null; } - final Uri pickedUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); - return pickedUri == null ? NONE : pickedUri; + return nullToNone(data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)); } public static Uri noneToNull(final Uri uri) { return uri == null || NONE.equals(uri) ? null : uri; } + + public static @NonNull Uri nullToNone(final Uri uri) { + return uri == null ? NONE : uri; + } } 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 86f8948c99a6db586dbfa002117fb95eff2bb1f7..7b3a35ab3a70a12d1b708bc6322d99c27fb05974 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java @@ -23,10 +23,10 @@ import com.google.common.base.Strings; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ItemMediaBinding; -import eu.siacs.conversations.services.ExportBackupService; import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.ui.util.Attachment; import eu.siacs.conversations.ui.util.ViewUtil; +import eu.siacs.conversations.worker.ExportBackupWorker; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -99,7 +99,7 @@ public class MediaAdapter extends RecyclerView.Adapter { } StylingHelper.format(body, viewHolder.messageBody.getCurrentTextColor()); + MyLinkify.addLinks(body, message.getConversation().getAccount(), message.getConversation().getJid()); if (highlightedTerm != null) { StylingHelper.highlight(viewHolder.messageBody, body, highlightedTerm); } - MyLinkify.addLinks(body, message.getConversation().getAccount(), message.getConversation().getJid()); + viewHolder.messageBody.setAutoLinkMask(0); viewHolder.messageBody.setText(body); BetterLinkMovementMethod method = new BetterLinkMovementMethod() { @@ -1533,7 +1536,15 @@ public class MessageAdapter extends ArrayAdapter { } public static void setTextColor(final TextView textView, final BubbleColor bubbleColor) { - textView.setTextColor(bubbleToOnSurfaceColor(textView, bubbleColor)); + final var color = bubbleToOnSurfaceColor(textView, bubbleColor); + textView.setTextColor(color); + if (BubbleColor.SURFACES.contains(bubbleColor)) { + textView.setLinkTextColor( + MaterialColors.getColor( + textView, com.google.android.material.R.attr.colorPrimary)); + } else { + textView.setLinkTextColor(color); + } } private static void setTextSize(final TextView textView, final boolean largeFont) { @@ -1549,7 +1560,7 @@ public class MessageAdapter extends ArrayAdapter { private static @ColorInt int bubbleToOnSurfaceVariant( final View view, final BubbleColor bubbleColor) { final @AttrRes int colorAttributeResId; - if (bubbleColor == BubbleColor.SURFACE_HIGH || bubbleColor == BubbleColor.SURFACE) { + if (BubbleColor.SURFACES.contains(bubbleColor)) { colorAttributeResId = com.google.android.material.R.attr.colorOnSurfaceVariant; } else { colorAttributeResId = bubbleToOnSurface(bubbleColor); @@ -1583,7 +1594,10 @@ public class MessageAdapter extends ArrayAdapter { PRIMARY, SECONDARY, TERTIARY, - WARNING + WARNING; + + private static final Collection SURFACES = + Arrays.asList(BubbleColor.SURFACE, BubbleColor.SURFACE_HIGH); } private static class BubbleDesign { diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/BackupSettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/BackupSettingsFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..f78577656d619357219dd94068dd67d968946464 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/BackupSettingsFragment.java @@ -0,0 +1,166 @@ +package eu.siacs.conversations.ui.fragment.settings; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.work.Constraints; +import androidx.work.Data; +import androidx.work.ExistingPeriodicWorkPolicy; +import androidx.work.ExistingWorkPolicy; +import androidx.work.OneTimeWorkRequest; +import androidx.work.OutOfQuotaPolicy; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkManager; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.common.base.Strings; +import com.google.common.primitives.Longs; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.worker.ExportBackupWorker; + +import java.util.concurrent.TimeUnit; + +public class BackupSettingsFragment extends XmppPreferenceFragment { + + public static final String CREATE_ONE_OFF_BACKUP = "create_one_off_backup"; + private static final String RECURRING_BACKUP = "recurring_backup"; + + private final ActivityResultLauncher requestStorageForBackupLauncher = + registerForActivityResult( + new ActivityResultContracts.RequestPermission(), + isGranted -> { + if (isGranted) { + startOneOffBackup(); + } else { + Toast.makeText( + requireActivity(), + getString( + R.string.no_storage_permission, + getString(R.string.app_name)), + Toast.LENGTH_LONG) + .show(); + } + }); + + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { + setPreferencesFromResource(R.xml.preferences_backup, rootKey); + final var createOneOffBackup = findPreference(CREATE_ONE_OFF_BACKUP); + final ListPreference recurringBackup = findPreference(RECURRING_BACKUP); + final var backupDirectory = findPreference("backup_directory"); + if (createOneOffBackup == null || recurringBackup == null || backupDirectory == null) { + throw new IllegalStateException( + "The preference resource file is missing some preferences"); + } + backupDirectory.setSummary( + getString( + R.string.pref_create_backup_summary, + FileBackend.getBackupDirectory(requireContext()).getAbsolutePath())); + createOneOffBackup.setOnPreferenceClickListener(this::onBackupPreferenceClicked); + final int[] choices = getResources().getIntArray(R.array.recurring_backup_values); + final CharSequence[] entries = new CharSequence[choices.length]; + final CharSequence[] entryValues = new CharSequence[choices.length]; + for (int i = 0; i < choices.length; ++i) { + entryValues[i] = String.valueOf(choices[i]); + entries[i] = timeframeValueToName(requireContext(), choices[i]); + } + recurringBackup.setEntries(entries); + recurringBackup.setEntryValues(entryValues); + recurringBackup.setSummaryProvider(new TimeframeSummaryProvider()); + } + + @Override + protected void onSharedPreferenceChanged(@NonNull String key) { + super.onSharedPreferenceChanged(key); + if (RECURRING_BACKUP.equals(key)) { + final var sharedPreferences = getPreferenceManager().getSharedPreferences(); + if (sharedPreferences == null) { + return; + } + final Long recurringBackupInterval = + Longs.tryParse( + Strings.nullToEmpty( + sharedPreferences.getString(RECURRING_BACKUP, null))); + if (recurringBackupInterval == null) { + return; + } + Log.d( + Config.LOGTAG, + "recurring backup interval changed to: " + recurringBackupInterval); + final var workManager = WorkManager.getInstance(requireContext()); + if (recurringBackupInterval <= 0) { + workManager.cancelUniqueWork(RECURRING_BACKUP); + } else { + final Constraints constraints = + new Constraints.Builder() + .setRequiresBatteryNotLow(true) + .setRequiresStorageNotLow(true) + .build(); + + final PeriodicWorkRequest periodicWorkRequest = + new PeriodicWorkRequest.Builder( + ExportBackupWorker.class, + recurringBackupInterval, + TimeUnit.SECONDS) + .setConstraints(constraints) + .setInputData( + new Data.Builder() + .putBoolean("recurring_backup", true) + .build()) + .build(); + workManager.enqueueUniquePeriodicWork( + RECURRING_BACKUP, ExistingPeriodicWorkPolicy.UPDATE, periodicWorkRequest); + } + } + } + + @Override + public void onStart() { + super.onStart(); + requireActivity().setTitle(R.string.backup); + } + + private boolean onBackupPreferenceClicked(final Preference preference) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + if (ContextCompat.checkSelfPermission( + requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + requestStorageForBackupLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE); + } else { + startOneOffBackup(); + } + } else { + startOneOffBackup(); + } + return true; + } + + private void startOneOffBackup() { + final OneTimeWorkRequest exportBackupWorkRequest = + new OneTimeWorkRequest.Builder(ExportBackupWorker.class) + .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) + .build(); + WorkManager.getInstance(requireContext()) + .enqueueUniqueWork( + CREATE_ONE_OFF_BACKUP, ExistingWorkPolicy.KEEP, exportBackupWorkRequest); + final MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(requireActivity()); + builder.setMessage(R.string.backup_started_message); + builder.setPositiveButton(R.string.ok, null); + builder.create().show(); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java index 9da6244cd88fab830cc8e58e19855b7c26d11137..88e9538f48869783bbe65b2bb54710cc2ee0d233 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java @@ -1,63 +1,27 @@ package eu.siacs.conversations.ui.fragment.settings; -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; -import android.widget.Toast; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.common.base.Strings; import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.R; -import eu.siacs.conversations.persistance.FileBackend; -import eu.siacs.conversations.services.ExportBackupService; public class MainSettingsFragment extends PreferenceFragmentCompat { - private static final String CREATE_BACKUP = "create_backup"; - - private final ActivityResultLauncher requestStorageForBackupLauncher = - registerForActivityResult( - new ActivityResultContracts.RequestPermission(), - isGranted -> { - if (isGranted) { - startBackup(); - } else { - Toast.makeText( - requireActivity(), - getString( - R.string.no_storage_permission, - getString(R.string.app_name)), - Toast.LENGTH_LONG) - .show(); - } - }); - @Override public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { setPreferencesFromResource(R.xml.preferences_main, rootKey); final var about = findPreference("about"); final var connection = findPreference("connection"); - final var backup = findPreference(CREATE_BACKUP); - if (about == null || connection == null || backup == null) { + if (about == null || connection == null) { throw new IllegalStateException( "The preference resource file is missing some preferences"); } - backup.setSummary( - getString( - R.string.pref_create_backup_summary, - FileBackend.getBackupDirectory(requireContext()).getAbsolutePath())); - backup.setOnPreferenceClickListener(this::onBackupPreferenceClicked); about.setTitle(getString(R.string.title_activity_about_x, BuildConfig.APP_NAME)); about.setSummary( String.format( @@ -74,31 +38,6 @@ public class MainSettingsFragment extends PreferenceFragmentCompat { } } - private boolean onBackupPreferenceClicked(final Preference preference) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - if (ContextCompat.checkSelfPermission( - requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - requestStorageForBackupLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE); - } else { - startBackup(); - } - } else { - startBackup(); - } - return true; - } - - private void startBackup() { - ContextCompat.startForegroundService( - requireContext(), new Intent(requireContext(), ExportBackupService.class)); - final MaterialAlertDialogBuilder builder = - new MaterialAlertDialogBuilder(requireActivity()); - builder.setMessage(R.string.backup_started_message); - builder.setPositiveButton(R.string.ok, null); - builder.create().show(); - } - @Override public void onStart() { super.onStart(); diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/NotificationsSettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/NotificationsSettingsFragment.java index fe895e08378277d5bec140a866f1a0b13417a526..9a526c75932103cfa64327b1569bc3166281d9b2 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/NotificationsSettingsFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/NotificationsSettingsFragment.java @@ -1,11 +1,15 @@ package eu.siacs.conversations.ui.fragment.settings; -import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.ActivityNotFoundException; +import android.content.Intent; import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.provider.Settings; import android.util.Log; +import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; @@ -56,12 +60,14 @@ public class NotificationsSettingsFragment extends XmppPreferenceFragment { @Nullable final Bundle savedInstanceState, final @Nullable String rootKey) { setPreferencesFromResource(R.xml.preferences_notifications, rootKey); final var messageNotificationSettings = findPreference("message_notification_settings"); + final var fullscreenNotification = findPreference("fullscreen_notification"); final var notificationRingtone = findPreference(AppSettings.NOTIFICATION_RINGTONE); final var notificationHeadsUp = findPreference(AppSettings.NOTIFICATION_HEADS_UP); final var notificationVibrate = findPreference(AppSettings.NOTIFICATION_VIBRATE); final var notificationLed = findPreference(AppSettings.NOTIFICATION_LED); final var foregroundService = findPreference(AppSettings.KEEP_FOREGROUND_SERVICE); if (messageNotificationSettings == null + || fullscreenNotification == null || notificationRingtone == null || notificationHeadsUp == null || notificationVibrate == null @@ -78,6 +84,44 @@ public class NotificationsSettingsFragment extends XmppPreferenceFragment { } else { messageNotificationSettings.setVisible(false); } + fullscreenNotification.setOnPreferenceClickListener(this::manageAppUseFullScreen); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE + || requireContext() + .getSystemService(NotificationManager.class) + .canUseFullScreenIntent()) { + fullscreenNotification.setVisible(false); + } + } + + @Override + public void onResume() { + super.onResume(); + final var fullscreenNotification = findPreference("fullscreen_notification"); + if (fullscreenNotification == null) { + return; + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE + || requireContext() + .getSystemService(NotificationManager.class) + .canUseFullScreenIntent()) { + fullscreenNotification.setVisible(false); + } + } + + private boolean manageAppUseFullScreen(final Preference preference) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + return false; + } + final var intent = new Intent(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT); + intent.setData(Uri.parse(String.format("package:%s", requireContext().getPackageName()))); + try { + startActivity(intent); + } catch (final ActivityNotFoundException e) { + Toast.makeText(requireContext(), R.string.unsupported_operation, Toast.LENGTH_SHORT) + .show(); + return false; + } + return true; } @Override @@ -140,7 +184,7 @@ public class NotificationsSettingsFragment extends XmppPreferenceFragment { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { channelRingtone = NotificationService.getCurrentIncomingCallChannel(requireContext()) - .transform(NotificationChannel::getSound); + .transform(channel -> PickRingtone.nullToNone(channel.getSound())); } else { channelRingtone = Optional.absent(); } diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/SecuritySettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/SecuritySettingsFragment.java index 2890db7ab57c20ee33881d58a46585f64f382fd9..0ccf2679e17d3069093148d73ba31af3dba127fc 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/SecuritySettingsFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/SecuritySettingsFragment.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.ui.fragment.settings; -import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.widget.Toast; @@ -13,13 +12,11 @@ import androidx.preference.Preference; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.common.base.Strings; -import com.google.common.primitives.Ints; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.OmemoSetting; import eu.siacs.conversations.services.MemorizingTrustManager; -import eu.siacs.conversations.utils.TimeFrameUtils; import java.security.KeyStoreException; import java.util.ArrayList; @@ -44,20 +41,13 @@ public class SecuritySettingsFragment extends XmppPreferenceFragment { final CharSequence[] entryValues = new CharSequence[choices.length]; for (int i = 0; i < choices.length; ++i) { entryValues[i] = String.valueOf(choices[i]); - entries[i] = messageDeletionValueToName(requireContext(), choices[i]); + entries[i] = timeframeValueToName(requireContext(), choices[i]); } automaticMessageDeletion.setEntries(entries); automaticMessageDeletion.setEntryValues(entryValues); - automaticMessageDeletion.setSummaryProvider(new MessageDeletionSummaryProvider()); + automaticMessageDeletion.setSummaryProvider(new TimeframeSummaryProvider()); } - private static String messageDeletionValueToName(final Context context, final int value) { - if (value == 0) { - return context.getString(R.string.never); - } else { - return TimeFrameUtils.resolve(context, 1000L * value); - } - } @Override protected void onSharedPreferenceChanged(@NonNull String key) { @@ -161,16 +151,6 @@ public class SecuritySettingsFragment extends XmppPreferenceFragment { .show(); } - private static class MessageDeletionSummaryProvider - implements Preference.SummaryProvider { - - @Nullable - @Override - public CharSequence provideSummary(@NonNull ListPreference preference) { - final Integer value = Ints.tryParse(Strings.nullToEmpty(preference.getValue())); - return messageDeletionValueToName(preference.getContext(), value == null ? 0 : value); - } - } private static class OmemoSummaryProvider implements Preference.SummaryProvider { diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/UpSettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/UpSettingsFragment.java index 7ab15e21e1707da180b7998e237a414a43f9c3fe..771acbbe1daa949a52d13f0e79bf27f3787562a9 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/UpSettingsFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/UpSettingsFragment.java @@ -13,7 +13,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import eu.siacs.conversations.R; -import eu.siacs.conversations.services.UnifiedPushDistributor; +import eu.siacs.conversations.receiver.UnifiedPushDistributor; import eu.siacs.conversations.xmpp.Jid; import java.net.URI; diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/XmppPreferenceFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/XmppPreferenceFragment.java index c7b046d4bdc00eb1c0284aed92aefd853e6c4b96..183f2c4d23245d94ef2c71f8d899ea464ae06ac2 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/XmppPreferenceFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/XmppPreferenceFragment.java @@ -1,18 +1,27 @@ package eu.siacs.conversations.ui.fragment.settings; +import android.content.Context; import android.content.SharedPreferences; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.ListPreference; +import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.Preference; import com.rarepebble.colorpicker.ColorPreference; +import com.google.common.base.Strings; +import com.google.common.primitives.Ints; + import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.XmppActivity; +import eu.siacs.conversations.utils.TimeFrameUtils; public abstract class XmppPreferenceFragment extends PreferenceFragmentCompat { @@ -28,7 +37,7 @@ public abstract class XmppPreferenceFragment extends PreferenceFragmentCompat { }; protected void onSharedPreferenceChanged(@NonNull String key) { - Log.d(Config.LOGTAG,"onSharedPreferenceChanged("+key+")"); + Log.d(Config.LOGTAG, "onSharedPreferenceChanged(" + key + ")"); } public void onBackendConnected() {} @@ -93,4 +102,23 @@ public abstract class XmppPreferenceFragment extends PreferenceFragmentCompat { protected void runOnUiThread(final Runnable runnable) { requireActivity().runOnUiThread(runnable); } + + protected static String timeframeValueToName(final Context context, final int value) { + if (value == 0) { + return context.getString(R.string.never); + } else { + return TimeFrameUtils.resolve(context, 1000L * value); + } + } + + protected static class TimeframeSummaryProvider + implements Preference.SummaryProvider { + + @Nullable + @Override + public CharSequence provideSummary(@NonNull ListPreference preference) { + final Integer value = Ints.tryParse(Strings.nullToEmpty(preference.getValue())); + return timeframeValueToName(preference.getContext(), value == null ? 0 : value); + } + } } diff --git a/src/main/java/eu/siacs/conversations/utils/Compatibility.java b/src/main/java/eu/siacs/conversations/utils/Compatibility.java index ebe113a4555a723b722203795a7bfeb404eaaeff..f4bdd8a5aff333319849bc94de213379c1790b21 100644 --- a/src/main/java/eu/siacs/conversations/utils/Compatibility.java +++ b/src/main/java/eu/siacs/conversations/utils/Compatibility.java @@ -1,6 +1,6 @@ package eu.siacs.conversations.utils; -import static eu.siacs.conversations.services.EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE; +import static eu.siacs.conversations.receiver.SystemEventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE; import android.annotation.SuppressLint; import android.app.ActivityOptions; @@ -12,8 +12,6 @@ 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; import android.util.Log; @@ -26,10 +24,6 @@ import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - public class Compatibility { public static boolean hasStoragePermission(final Context context) { @@ -99,7 +93,7 @@ public class Compatibility { R.bool.enable_foreground_service); } - public static void startService(Context context, Intent intent) { + public static void startService(final Context context, final Intent intent) { try { if (Compatibility.runsAndTargetsTwentySix(context)) { intent.putExtra(EXTRA_NEEDS_FOREGROUND_SERVICE, true); @@ -107,7 +101,7 @@ public class Compatibility { } else { context.startService(intent); } - } catch (RuntimeException e) { + } catch (final RuntimeException e) { Log.d( Config.LOGTAG, context.getClass().getSimpleName() + " was unable to start service"); diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java index 25f8e112deebe0ca43c4c1dfe5696fe358a31841..14f3337d399c064117a4695e7131f34d62c131a9 100644 --- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java @@ -37,7 +37,7 @@ import java.util.Properties; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Transferable; -import eu.siacs.conversations.services.ExportBackupService; +import eu.siacs.conversations.worker.ExportBackupWorker; /** * Utilities for dealing with MIME types. @@ -91,7 +91,7 @@ public final class MimeUtils { add("application/vnd.amazon.mobi8-ebook", "kfx"); add("application/vnd.android.package-archive", "apk"); add("application/vnd.cinderella", "cdy"); - add(ExportBackupService.MIME_TYPE, "ceb"); + add(ExportBackupWorker.MIME_TYPE, "ceb"); add("application/vnd.ms-pki.stl", "stl"); add("application/vnd.oasis.opendocument.database", "odb"); add("application/vnd.oasis.opendocument.formula", "odf"); diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index fdae2ddc8ba48976fd48a0d573e41ccf889a73f5..e9c6290476157f6eeac7fc5e2ef75b671ea7a573 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -35,16 +35,15 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversational; -import eu.siacs.conversations.entities.ListItem; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.Presence; import eu.siacs.conversations.entities.RtpSessionStatus; import eu.siacs.conversations.entities.Transferable; -import eu.siacs.conversations.services.ExportBackupService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.util.MyLinkify; import eu.siacs.conversations.ui.util.QuoteHelper; +import eu.siacs.conversations.worker.ExportBackupWorker; import eu.siacs.conversations.xmpp.Jid; public class UIHelper { @@ -429,7 +428,7 @@ public class UIHelper { return context.getString(R.string.pdf_document); } else if (mime.equals("application/vnd.android.package-archive")) { return context.getString(R.string.apk); - } else if (mime.equals(ExportBackupService.MIME_TYPE)) { + } else if (mime.equals(ExportBackupWorker.MIME_TYPE)) { return context.getString(R.string.conversations_backup); } else if (mime.contains("vcard")) { return context.getString(R.string.vcard); diff --git a/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java b/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java new file mode 100644 index 0000000000000000000000000000000000000000..1b36d554034c29107f7a357fe1e123365622662b --- /dev/null +++ b/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java @@ -0,0 +1,607 @@ +package eu.siacs.conversations.worker; + +import static eu.siacs.conversations.utils.Compatibility.s; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ServiceInfo; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.SystemClock; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; +import androidx.work.ForegroundInfo; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.gson.stream.JsonWriter; + +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.receiver.WorkManagerEventReceiver; +import eu.siacs.conversations.utils.BackupFileHeader; +import eu.siacs.conversations.utils.Compatibility; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.zip.GZIPOutputStream; + +import javax.crypto.Cipher; +import javax.crypto.CipherOutputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +public class ExportBackupWorker extends Worker { + + private static final SimpleDateFormat DATE_FORMAT = + new SimpleDateFormat("yyyy-MM-dd-HH-mm", Locale.US); + + public static final String KEYTYPE = "AES"; + public static final String CIPHERMODE = "AES/GCM/NoPadding"; + public static final String PROVIDER = "BC"; + + public static final String MIME_TYPE = "application/vnd.conversations.backup"; + + private static final int NOTIFICATION_ID = 19; + private static final int PAGE_SIZE = 50; + private static final int BACKUP_CREATED_NOTIFICATION_ID = 23; + + private static final int PENDING_INTENT_FLAGS = + s() + ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT + : PendingIntent.FLAG_UPDATE_CURRENT; + + private final boolean recurringBackup; + + public ExportBackupWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + final var inputData = workerParams.getInputData(); + this.recurringBackup = inputData.getBoolean("recurring_backup", false); + } + + @NonNull + @Override + public Result doWork() { + final List files; + try { + files = export(); + } catch (final IOException + | InvalidKeySpecException + | InvalidAlgorithmParameterException + | InvalidKeyException + | NoSuchPaddingException + | NoSuchAlgorithmException + | NoSuchProviderException e) { + Log.d(Config.LOGTAG, "could not create backup", e); + return Result.failure(); + } finally { + getApplicationContext() + .getSystemService(NotificationManager.class) + .cancel(NOTIFICATION_ID); + } + Log.d(Config.LOGTAG, "done creating " + files.size() + " backup files"); + if (files.isEmpty() || recurringBackup) { + return Result.success(); + } + notifySuccess(files); + return Result.success(); + } + + @NonNull + @Override + public ForegroundInfo getForegroundInfo() { + Log.d(Config.LOGTAG, "getForegroundInfo()"); + final NotificationCompat.Builder notification = getNotification(); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { + return new ForegroundInfo( + NOTIFICATION_ID, + notification.build(), + ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC); + } else { + return new ForegroundInfo(NOTIFICATION_ID, notification.build()); + } + } + + private List export() + throws IOException, + InvalidKeySpecException, + InvalidAlgorithmParameterException, + InvalidKeyException, + NoSuchPaddingException, + NoSuchAlgorithmException, + NoSuchProviderException { + final Context context = getApplicationContext(); + final var database = DatabaseBackend.getInstance(context); + final var accounts = database.getAccounts(); + + int count = 0; + final int max = accounts.size(); + final ImmutableList.Builder files = new ImmutableList.Builder<>(); + Log.d(Config.LOGTAG, "starting backup for " + max + " accounts"); + for (final Account account : accounts) { + if (isStopped()) { + Log.d(Config.LOGTAG, "ExportBackupWorker has stopped. Returning what we have"); + return files.build(); + } + final String password = account.getPassword(); + if (Strings.nullToEmpty(password).trim().isEmpty()) { + Log.d( + Config.LOGTAG, + String.format( + "skipping backup for %s because password is empty. unable to encrypt", + account.getJid().asBareJid())); + count++; + continue; + } + final String filename = + String.format( + "%s.%s.ceb", + account.getJid().asBareJid().toEscapedString(), + DATE_FORMAT.format(new Date())); + final File file = new File(FileBackend.getBackupDirectory(context), filename); + try { + export(database, account, password, file, max, count); + } catch (final WorkStoppedException e) { + if (file.delete()) { + Log.d( + Config.LOGTAG, + "deleted in progress backup file " + file.getAbsolutePath()); + } + Log.d(Config.LOGTAG, "ExportBackupWorker has stopped. Returning what we have"); + return files.build(); + } + files.add(file); + count++; + } + return files.build(); + } + + private void export( + final DatabaseBackend database, + final Account account, + final String password, + final File file, + final int max, + final int count) + throws IOException, + InvalidKeySpecException, + InvalidAlgorithmParameterException, + InvalidKeyException, + NoSuchPaddingException, + NoSuchAlgorithmException, + NoSuchProviderException, + WorkStoppedException { + final var context = getApplicationContext(); + final SecureRandom secureRandom = new SecureRandom(); + Log.d( + Config.LOGTAG, + String.format( + "exporting data for account %s (%s)", + account.getJid().asBareJid(), account.getUuid())); + final byte[] IV = new byte[12]; + final byte[] salt = new byte[16]; + secureRandom.nextBytes(IV); + secureRandom.nextBytes(salt); + final BackupFileHeader backupFileHeader = + new BackupFileHeader( + context.getString(R.string.app_name), + account.getJid(), + System.currentTimeMillis(), + IV, + salt); + final var notification = getNotification(); + if (!recurringBackup) { + final var cancel = new Intent(context, WorkManagerEventReceiver.class); + cancel.setAction(WorkManagerEventReceiver.ACTION_STOP_BACKUP); + final var cancelPendingIntent = + PendingIntent.getBroadcast(context, 197, cancel, PENDING_INTENT_FLAGS); + notification.addAction( + new NotificationCompat.Action.Builder( + R.drawable.ic_cancel_24dp, + context.getString(R.string.cancel), + cancelPendingIntent) + .build()); + } + final Progress progress = new Progress(notification, max, count); + final File directory = file.getParentFile(); + if (directory != null && directory.mkdirs()) { + Log.d(Config.LOGTAG, "created backup directory " + directory.getAbsolutePath()); + } + final FileOutputStream fileOutputStream = new FileOutputStream(file); + final DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream); + backupFileHeader.write(dataOutputStream); + dataOutputStream.flush(); + + final Cipher cipher = + Compatibility.twentyEight() + ? Cipher.getInstance(CIPHERMODE) + : Cipher.getInstance(CIPHERMODE, PROVIDER); + final byte[] key = getKey(password, salt); + SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(IV); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); + CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher); + + final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(cipherOutputStream); + final SQLiteDatabase db = database.getReadableDatabase(); + final var writer = new PrintWriter(gzipOutputStream); + final String uuid = account.getUuid(); + accountExport(db, uuid, writer); + simpleExport(db, Conversation.TABLENAME, Conversation.ACCOUNT, uuid, writer); + messageExport(db, uuid, writer, progress); + messageExportCheogram(db, uuid, writer, progress); + for (final String table : + Arrays.asList( + SQLiteAxolotlStore.PREKEY_TABLENAME, + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.IDENTITIES_TABLENAME)) { + throwIfWorkStopped(); + simpleExport(db, table, SQLiteAxolotlStore.ACCOUNT, uuid, writer); + } + writer.flush(); + writer.close(); + mediaScannerScanFile(file); + Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile()); + } + + private NotificationCompat.Builder getNotification() { + final var context = getApplicationContext(); + final NotificationCompat.Builder notification = + new NotificationCompat.Builder(context, "backup"); + notification + .setContentTitle(context.getString(R.string.notification_create_backup_title)) + .setSmallIcon(R.drawable.ic_archive_24dp) + .setProgress(1, 0, false); + notification.setOngoing(true); + notification.setLocalOnly(true); + return notification; + } + + private void throwIfWorkStopped() throws WorkStoppedException { + if (isStopped()) { + throw new WorkStoppedException(); + } + } + + private void mediaScannerScanFile(final File file) { + final Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + intent.setData(Uri.fromFile(file)); + getApplicationContext().sendBroadcast(intent); + } + + private void messageExport(SQLiteDatabase db, String uuid, PrintWriter writer, Progress progress) { + final var notificationManager = getApplicationContext().getSystemService(NotificationManager.class); + Cursor cursor = db.rawQuery("select messages.* from messages join conversations on conversations.uuid=messages.conversationUuid where conversations.accountUuid=?", new String[]{uuid}); + int size = cursor != null ? cursor.getCount() : 0; + Log.d(Config.LOGTAG, "exporting " + size + " messages for account " + uuid); + int i = 0; + int p = 0; + while (cursor != null && cursor.moveToNext()) { + writer.write(cursorToString(Message.TABLENAME, cursor, PAGE_SIZE, false)); + if (i + PAGE_SIZE > size) { + i = size; + } else { + i += PAGE_SIZE; + } + final int percentage = i * 100 / size; + if (p < percentage) { + p = percentage; + notificationManager.notify(NOTIFICATION_ID, progress.build(p)); + } + } + if (cursor != null) { + cursor.close(); + } + } + + private void messageExportCheogram(SQLiteDatabase db, String uuid, PrintWriter writer, Progress progress) { + final var notificationManager = getApplicationContext().getSystemService(NotificationManager.class); + Cursor cursor = db.rawQuery("select cmessages.* from messages join cheogram.messages cmessages using (uuid) join conversations on conversations.uuid=messages.conversationUuid where conversations.accountUuid=?", new String[]{uuid}); + int size = cursor != null ? cursor.getCount() : 0; + Log.d(Config.LOGTAG, "exporting " + size + " cheogram messages for account " + uuid); + int i = 0; + int p = 0; + while (cursor != null && cursor.moveToNext()) { + writer.write(cursorToString("cheogram." + Message.TABLENAME, cursor, PAGE_SIZE, false)); + if (i + PAGE_SIZE > size) { + i = size; + } else { + i += PAGE_SIZE; + } + final int percentage = i * 100 / size; + if (p < percentage) { + p = percentage; + notificationManager.notify(NOTIFICATION_ID, progress.build(p)); + } + } + if (cursor != null) { + cursor.close(); + } + + cursor = db.rawQuery("select webxdc_updates.* from " + Conversation.TABLENAME + " join cheogram.webxdc_updates webxdc_updates on " + Conversation.TABLENAME + ".uuid=webxdc_updates." + Message.CONVERSATION + " where conversations.accountUuid=?", new String[]{uuid}); + size = cursor != null ? cursor.getCount() : 0; + Log.d(Config.LOGTAG, "exporting " + size + " WebXDC updates for account " + uuid); + while (cursor != null && cursor.moveToNext()) { + writer.write(cursorToString("cheogram.webxdc_updates", cursor, PAGE_SIZE, false)); + if (i + PAGE_SIZE > size) { + i = size; + } else { + i += PAGE_SIZE; + } + final int percentage = i * 100 / size; + if (p < percentage) { + p = percentage; + notificationManager.notify(NOTIFICATION_ID, progress.build(p)); + } + } + if (cursor != null) { + cursor.close(); + } + } + + private static void accountExport(final SQLiteDatabase db, final String uuid, final PrintWriter writer) { + final StringBuilder builder = new StringBuilder(); + final Cursor accountCursor = db.query(Account.TABLENAME, null, Account.UUID + "=?", new String[]{uuid}, null, null, null); + while (accountCursor != null && accountCursor.moveToNext()) { + builder.append("INSERT INTO ").append(Account.TABLENAME).append("("); + for (int i = 0; i < accountCursor.getColumnCount(); ++i) { + if (i != 0) { + builder.append(','); + } + builder.append(accountCursor.getColumnName(i)); + } + builder.append(") VALUES("); + for (int i = 0; i < accountCursor.getColumnCount(); ++i) { + if (i != 0) { + builder.append(','); + } + final String value = accountCursor.getString(i); + if (value == null || Account.ROSTERVERSION.equals(accountCursor.getColumnName(i))) { + builder.append("NULL"); + } else if (Account.OPTIONS.equals(accountCursor.getColumnName(i)) && value.matches("\\d+")) { + int intValue = Integer.parseInt(value); + intValue |= 1 << Account.OPTION_DISABLED; + builder.append(intValue); + } else { + appendEscapedSQLString(builder, value); + } + } + builder.append(")"); + builder.append(';'); + builder.append('\n'); + } + if (accountCursor != null) { + accountCursor.close(); + } + writer.append(builder.toString()); + } + + private static void simpleExport(SQLiteDatabase db, String table, String column, String uuid, PrintWriter writer) { + final Cursor cursor = db.query(table, null, column + "=?", new String[]{uuid}, null, null, null); + while (cursor != null && cursor.moveToNext()) { + writer.write(cursorToString(table, cursor, PAGE_SIZE)); + } + if (cursor != null) { + cursor.close(); + } + } + + private static String cursorToString(final String table, final Cursor cursor, final int max) { + return cursorToString(table, cursor, max, false); + } + + private static String cursorToString(final String table, final Cursor cursor, int max, boolean ignore) { + final boolean identities = SQLiteAxolotlStore.IDENTITIES_TABLENAME.equals(table); + StringBuilder builder = new StringBuilder(); + builder.append("INSERT "); + if (ignore) { + builder.append("OR IGNORE "); + } + builder.append("INTO ").append(table).append("("); + int skipColumn = -1; + for (int i = 0; i < cursor.getColumnCount(); ++i) { + final String name = cursor.getColumnName(i); + if (identities && SQLiteAxolotlStore.TRUSTED.equals(name)) { + skipColumn = i; + continue; + } + if (i != 0) { + builder.append(','); + } + builder.append(name); + } + builder.append(") VALUES"); + for (int i = 0; i < max; ++i) { + if (i != 0) { + builder.append(','); + } + appendValues(cursor, builder, skipColumn); + if (i < max - 1 && !cursor.moveToNext()) { + break; + } + } + builder.append(';'); + builder.append('\n'); + return builder.toString(); + } + + private static void appendValues(final Cursor cursor, final StringBuilder builder, final int skipColumn) { + builder.append("("); + for (int i = 0; i < cursor.getColumnCount(); ++i) { + if (i == skipColumn) { + continue; + } + if (i != 0) { + builder.append(','); + } + final String value = cursor.getString(i); + if (value == null) { + builder.append("NULL"); + } else if (value.matches("[0-9]+")) { + builder.append(value); + } else { + appendEscapedSQLString(builder, value); + } + } + builder.append(")"); + + } + + private static void appendEscapedSQLString(final StringBuilder sb, final String sqlString) { + DatabaseUtils.appendEscapedSQLString(sb, CharMatcher.is('\u0000').removeFrom(sqlString)); + } + + public static byte[] getKey(final String password, final byte[] salt) + throws InvalidKeySpecException { + final SecretKeyFactory factory; + try { + factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + return factory.generateSecret(new PBEKeySpec(password.toCharArray(), salt, 1024, 128)) + .getEncoded(); + } + + private void notifySuccess(final List files) { + final var context = getApplicationContext(); + final String path = FileBackend.getBackupDirectory(context).getAbsolutePath(); + + final var openFolderIntent = getOpenFolderIntent(path); + + final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); + final ArrayList uris = new ArrayList<>(); + for (final File file : files) { + uris.add(FileBackend.getUriForFile(context, file)); + } + intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setType(MIME_TYPE); + final Intent chooser = + Intent.createChooser(intent, context.getString(R.string.share_backup_files)); + final var shareFilesIntent = + PendingIntent.getActivity(context, 190, chooser, PENDING_INTENT_FLAGS); + + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context, "backup"); + mBuilder.setContentTitle(context.getString(R.string.notification_backup_created_title)) + .setContentText( + context.getString(R.string.notification_backup_created_subtitle, path)) + .setStyle( + new NotificationCompat.BigTextStyle() + .bigText( + context.getString( + R.string.notification_backup_created_subtitle, + FileBackend.getBackupDirectory(context) + .getAbsolutePath()))) + .setAutoCancel(true) + .setSmallIcon(R.drawable.ic_archive_24dp); + + if (openFolderIntent.isPresent()) { + mBuilder.setContentIntent(openFolderIntent.get()); + } else { + Log.w(Config.LOGTAG, "no app can display folders"); + } + + mBuilder.addAction( + R.drawable.ic_share_24dp, + context.getString(R.string.share_backup_files), + shareFilesIntent); + final var notificationManager = context.getSystemService(NotificationManager.class); + notificationManager.notify(BACKUP_CREATED_NOTIFICATION_ID, mBuilder.build()); + } + + private Optional getOpenFolderIntent(final String path) { + final var context = getApplicationContext(); + for (final Intent intent : getPossibleFileOpenIntents(context, path)) { + if (intent.resolveActivityInfo(context.getPackageManager(), 0) != null) { + return Optional.of( + PendingIntent.getActivity(context, 189, intent, PENDING_INTENT_FLAGS)); + } + } + return Optional.absent(); + } + + private static List getPossibleFileOpenIntents( + final Context context, final String path) { + + // http://www.openintents.org/action/android-intent-action-view/file-directory + // do not use 'vnd.android.document/directory' since this will trigger system file manager + final Intent openIntent = new Intent(Intent.ACTION_VIEW); + openIntent.addCategory(Intent.CATEGORY_DEFAULT); + if (Compatibility.runsAndTargetsTwentyFour(context)) { + openIntent.setType("resource/folder"); + } else { + openIntent.setDataAndType(Uri.parse("file://" + path), "resource/folder"); + } + openIntent.putExtra("org.openintents.extra.ABSOLUTE_PATH", path); + + final Intent amazeIntent = new Intent(Intent.ACTION_VIEW); + amazeIntent.setDataAndType(Uri.parse("com.amaze.filemanager:" + path), "resource/folder"); + + // will open a file manager at root and user can navigate themselves + final Intent systemFallBack = new Intent(Intent.ACTION_VIEW); + systemFallBack.addCategory(Intent.CATEGORY_DEFAULT); + systemFallBack.setData( + Uri.parse("content://com.android.externalstorage.documents/root/primary")); + + return Arrays.asList(openIntent, amazeIntent, systemFallBack); + } + + private static class Progress { + private final NotificationCompat.Builder notification; + private final int max; + private final int count; + + private Progress( + final NotificationCompat.Builder notification, final int max, final int count) { + this.notification = notification; + this.max = max; + this.count = count; + } + + private Notification build(int percentage) { + notification.setProgress(max * 100, count * 100 + percentage, false); + return notification.build(); + } + } + + private static class WorkStoppedException extends Exception {} +} 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 601447b422010d01fde97b25547d214befc7df64..9ed32c0f7d6b2d2245dbbe289417f837ea1ad930 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -648,7 +648,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { } public JingleRtpConnection getOngoingRtpConnection() { - for(final AbstractJingleConnection jingleConnection : this.connections.values()) { + for (final AbstractJingleConnection jingleConnection : this.connections.values()) { if (jingleConnection instanceof JingleRtpConnection jingleRtpConnection) { if (jingleRtpConnection.isTerminated()) { continue; @@ -984,10 +984,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { this.rtpSessionProposals.remove(sessionProposal); sessionProposal.getCallIntegration().error(); mXmppConnectionService.notifyJingleRtpConnectionUpdate( - account, - sessionProposal.with, - sessionProposal.sessionId, - endUserState); + account, sessionProposal.with, sessionProposal.sessionId, endUserState); return; } @@ -1225,6 +1222,9 @@ public class JingleConnectionManager extends AbstractConnectionManager { @Override public void onCallIntegrationSilence() {} + @Override + public void onCallIntegrationMicrophoneEnabled(boolean enabled) {} + @Override public boolean applyDtmfTone(final String dtmf) { return false; 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 c8a9998790c9e754e9e58cac5f5d5839442c1123..6390aad5f5fe8fb17a54f1de7cd819737a546fbd 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -2333,6 +2333,7 @@ public class JingleRtpConnection extends AbstractJingleConnection this.jingleConnectionManager.ensureConnectionIsRegistered(this); this.webRTCWrapper.setup(this.xmppConnectionService); this.webRTCWrapper.initializePeerConnection(media, iceServers, trickle); + this.webRTCWrapper.setMicrophoneEnabledOrThrow(callIntegration.isMicrophoneEnabled()); } private void acceptCallFromProposed() { @@ -2490,7 +2491,7 @@ public class JingleRtpConnection extends AbstractJingleConnection this.webRTCWrapper.execute(this::renegotiate); } - private void renegotiate() { + private synchronized void renegotiate() { final SessionDescription sessionDescription; try { sessionDescription = setLocalSessionDescription(); @@ -2539,10 +2540,11 @@ public class JingleRtpConnection extends AbstractJingleConnection + this.webRTCWrapper.getSignalingState()); } - if (diff.added.size() > 0) { - modifyLocalContentMap(rtpContentMap); - sendContentAdd(rtpContentMap, diff.added); + if (diff.added.isEmpty()) { + return; } + modifyLocalContentMap(rtpContentMap); + sendContentAdd(rtpContentMap, diff.added); } private void initiateIceRestart(final RtpContentMap rtpContentMap) { @@ -2695,7 +2697,7 @@ public class JingleRtpConnection extends AbstractJingleConnection } public boolean setMicrophoneEnabled(final boolean enabled) { - return webRTCWrapper.setMicrophoneEnabled(enabled); + return webRTCWrapper.setMicrophoneEnabledOrThrow(enabled); } public boolean isVideoEnabled() { @@ -2771,6 +2773,11 @@ public class JingleRtpConnection extends AbstractJingleConnection xmppConnectionService.getNotificationService().stopSoundAndVibration(); } + @Override + public void onCallIntegrationMicrophoneEnabled(final boolean enabled) { + this.webRTCWrapper.setMicrophoneEnabled(enabled); + } + @Override public void onAudioDeviceChanged( final CallIntegration.AudioDevice selectedAudioDevice, 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 8aabe5654a4c94731d5e86f846daa3d18665645c..38c865443cba9397cb908da57111cafcdc58b984 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -538,26 +538,33 @@ public class WebRTCWrapper { } } - boolean setMicrophoneEnabled(final boolean enabled) { - Optional audioTrack = null; + boolean setMicrophoneEnabledOrThrow(final boolean enabled) { + final Optional audioTrack = + TrackWrapper.get(peerConnection, this.localAudioTrack); + if (audioTrack.isPresent()) { + return setEnabled(audioTrack.get(), enabled); + + } else { + throw new IllegalStateException("Local audio track does not exist (yet)"); + } + } + + private static boolean setEnabled(final AudioTrack audioTrack, final boolean enabled) { try { - audioTrack = TrackWrapper.get(peerConnection, this.localAudioTrack); + audioTrack.setEnabled(enabled); + return true; } catch (final IllegalStateException e) { - Log.d(Config.LOGTAG, "unable to toggle microphone", e); - // ignoring race condition in case sender has been disposed + Log.d(Config.LOGTAG, "unable to toggle audio track", e); + // ignoring race condition in case MediaStreamTrack has been disposed return false; } + } + + void setMicrophoneEnabled(final boolean enabled) { + final Optional audioTrack = + TrackWrapper.get(peerConnection, this.localAudioTrack); if (audioTrack.isPresent()) { - try { - audioTrack.get().setEnabled(enabled); - return true; - } catch (final IllegalStateException e) { - Log.d(Config.LOGTAG, "unable to toggle microphone", e); - // ignoring race condition in case MediaStreamTrack has been disposed - return false; - } - } else { - throw new IllegalStateException("Local audio track does not exist (yet)"); + setEnabled(audioTrack.get(), enabled); } } diff --git a/src/main/res/drawable/cursor_on_tertiary_container.xml b/src/main/res/drawable/cursor_on_tertiary_container.xml new file mode 100644 index 0000000000000000000000000000000000000000..9a86446ec6cdbc6458c93d502f6b9f6ca7d5d813 --- /dev/null +++ b/src/main/res/drawable/cursor_on_tertiary_container.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/ic_calendar_month_24dp.xml b/src/main/res/drawable/ic_calendar_month_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..007f1fa45f442d9ecc6a49cae3053f05b6c43c4c --- /dev/null +++ b/src/main/res/drawable/ic_calendar_month_24dp.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/src/main/res/drawable/ic_smartphone_24dp.xml b/src/main/res/drawable/ic_smartphone_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..832fa3a7662bdb24cd9a64ad060d1940f2417b63 --- /dev/null +++ b/src/main/res/drawable/ic_smartphone_24dp.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml index 59639a9fdfdb4aadc9fb851821211370d02b391f..29cdbe54596fa43b345718dd0836367f83b0cb3e 100644 --- a/src/main/res/layout/fragment_conversation.xml +++ b/src/main/res/layout/fragment_conversation.xml @@ -130,6 +130,7 @@ android:hint="Subject" android:textColor="?colorOnTertiaryContainer" android:textColorHint="@color/hint_on_tertiary_container" + android:textCursorDrawable="@drawable/cursor_on_tertiary_container" android:maxLines="1" android:padding="8dp" android:imeOptions="flagNoExtractUi" diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index e91f1add3928616aa2cb4885b9d2ea111c922630..f186d2bb033a0c3a5cf3b1f48fb2291b318affb6 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -404,7 +404,7 @@ Eingabe Eingabetaste sendet Nachricht Nutze die Eingabetaste zum Versenden einer Nachricht. Strg+Eingabetaste sendet die Nachricht unabhängig von dieser Einstellung. - Zeige Eingabetaste + Eingabetaste anzeigen Emoji-Taste durch Eingabetaste ersetzen Audio Video @@ -445,7 +445,7 @@ Schnell-Tasten Keine Zuletzt verwendet - Wähle Schnell-Taste + Schnell-Taste auswählen Kontakte durchsuchen Private Nachricht senden %1$s hat den Gruppenchat verlassen @@ -465,9 +465,9 @@ Abwesend bei gesperrtem Gerät Als abwesend anzeigen, wenn das Gerät gesperrt ist Beschäftigt im lautlosen Modus - Als Beschäftigt anzeigen, wenn sich das Gerät im lautlosen Modus befindet + Als beschäftigt anzeigen, wenn sich das Gerät im lautlosen Modus befindet Vibration als Lautlos behandeln - Als Beschäftigt anzeigen, wenn das Gerät auf Vibration eingestellt ist + Als beschäftigt anzeigen, wenn das Gerät auf Vibration eingestellt ist Hostname & Port Erweiterte Verbindungseinstellungen beim Einrichten eines Kontos anzeigen xmpp.domain.de @@ -600,7 +600,7 @@ Bitte warten, bis die Schlüssel abgerufen werden Als Barcode teilen Als XMPP-URI teilen - Als HTTP Link teilen + Als HTTP-Link teilen Blind vertrauen vor der Überprüfung Neuen Geräten von nicht verifizierten Kontakten vertrauen, aber bei verifizierten Kontakten eine manuelle Bestätigung der neuen Geräte verlangen. Blind vertraute OMEMO-Schlüssel bedeutet, dass es sich um eine andere Person handeln könnte oder dass jemand sie abgehört haben könnte. @@ -728,7 +728,7 @@ XMPP-Adresse kopieren HTTP-Dateifreigabe für S3 Direkte Suche - Beim Dialog \'Neuer Chat\' Tastatur öffnen und den Cursor im Suchfeld platzieren + Beim Dialog \"Neuer Chat\" Tastatur öffnen und den Cursor im Suchfeld platzieren Gruppenchat-Profilbild Host unterstützt keine Gruppenchat-Profilbilder Nur der Eigentümer kann das Gruppenchat-Profilbild ändern @@ -1064,4 +1064,6 @@ Verschlüsselt schreiben… Schriftgröße der Nachrichten erhöhen Große Schrift + Einladungen von Unbekannten + Einladungen zu Gruppenchats von Unbekannten annehmen \ No newline at end of file diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 75cbaa30f42646e715b76ef6a345df603fbb393e..7aac6c3b022f214ce3b21f5ba0afecfeade4d532 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -109,11 +109,11 @@ Aceptar archivos De forma automática aceptar archivos menores que… Adjuntos - Notificaciones + Notificación Vibrar Vibra cuando llega un nuevo mensaje - Luz - La luz parpadea cuando llega un nuevo mensaje + Led de notificaciones + La luz de notificación parpadea cuando llega un nuevo mensaje Tono de llamada Sonido de notificación Sonido de notificación para nuevos mensajes @@ -561,7 +561,7 @@ Medio Largo Visto por ultima vez - Permite que tus contactos sepan cuando usas Conversations + Permitir que tus contactos vean la última vez que usaste la aplicación Privacidad Tema Selecciona el color de la paleta @@ -702,14 +702,14 @@ No se ha podido conseguir la lista de dispositivos No se han podido conseguir las claves de cifrado Consejo: En algunas ocasiones esto puede corregirse agregando a tu contacto a tu lista de contactos. Tu contacto deberá asegurarse también que estás en su lista de contactos. - ¿Estás seguro de que quieres deshabilitar el cifrado OMEMO para esta conversación? -\nEsto permitiría al administrador de tu servidor leer tus mensajes, aunque esta podría ser la única via de comunicación con personas que usen clientes desactualizados. + ¿Estás seguro de que deseas desactivar el cifrado OMEMO para este chat? +\nEsto permitirá que el administrador de su servidor lea sus mensajes, pero podría ser la única forma de comunicarse con personas que utilizan clientes obsoletos. Deshabilitar ahora Borrador: Cifrado OMEMO OMEMO siempre será usado para conversaciones uno a uno y en conversaciones en grupo privadas. - OMEMO será usado por defecto para nuevas conversaciones. - OMEMO tendrá que ser explícitamente activado para nuevas conversaciones. + OMEMO será usado por defecto para chats nuevos. + OMEMO tendrá que ser activado explícitamente para los nuevos chats. Crear acceso directo Activo por defecto Desactivado por defecto @@ -730,14 +730,14 @@ Permitir a %1$s acceder al micrófono Buscar mensajes GIF - Ver conversación + Ver el chat Plugin para Compartir Ubicación Usar el Plugin Compartir Ubicación en lugar del propio de la aplicación Copiar dirección web Copiar dirección XMPP Compartición de Archivos mediante S3 Búsqueda directa - En la pantalla de \'Nueva Conversación\' abrir el teclado y poner el cursor en el campo de búsqueda + En la pantalla \"Nuevo chat\", abra el teclado y coloque el cursor en el campo de búsqueda Avatar de la conversación en grupo El servidor no soporta avatares en conversaciones en grupo Solo el propietario de la conversación puede cambiar el avatar @@ -789,20 +789,20 @@ Verificar %s %s.]]> Hemos enviado otro mensaje SMS con un código de 6 dígitos. - Por favor, introduce el código de 6 dígitos abajo. + Por favor, introduzca a continuación el PIN de 6 dígitos. Reenviar SMS Reenviar SMS (%s) Por favor, espera (%s) - atrás - Automáticamente pegar el posible pin del portapapeles. - Por favor, introduce tu código de 6 dígitos. + Atrás + Pegado automático del posible PIN desde el portapapeles. + Por favor, introduzca su PIN de 6 dígitos. ¿Estás seguro de que quieres abortar el proceso de registro? No Verificando… Solicitando un mensaje de texto… - El código que has introducido no es correcto. - El código que te hemos enviado ha expirado. + El PIN introducido es incorrecto. + El PIN que te hemos enviado ha caducado. Error desconocido de red. Respuesta de servidor desconocida. No se ha podido conectar al servidor. @@ -951,8 +951,8 @@ Desfijar de la parte superior Recorrido GPX No se pudo corregir el mensaje - Todas las conversaciones - Esta conversación + Todos los chats + Este chat Tu imagen de perfil Imagen de perfil de %s Encriptado con OMEMO @@ -1024,7 +1024,7 @@ Sin permiso para llamar por teléfono Contacto no disponible ¡Sin integración de llamadas! - Borrar y cerrar + Borrar y cerrar el chat ¿Quieres eliminar el marcador de %s ? ¿Quieres eliminar el marcador de %s y guardar el chat? Enviar informes de errores @@ -1034,4 +1034,50 @@ Guardar este chat Chat guardado Unirse a Conversation + Burbujas de chat de colores + Colores de fondo distintos para mensajes enviados y recibidos + El código de barras no contiene huellas digitales para este chat. + Teclado + Notificaciones de participación + Solicitud + Interacción + Nombre del host y puerto, Tor + Nombre del host y puerto, Tor, descubrimiento de canales + Cifrado E2E, confianza ciega antes de la verificación, detección de MITM + Al actuar como un Distribuidor de UnifiedPush la conexión XMPP persistente, fiable y de bajo consumo de batería se utilizará para despertar a otras aplicaciones compatibles con UnifiedPush como Tusky, Ltt.rs, FluffyChat y más. + Enviar mensaje cifrado + Interfaz + Tema, Colores, Capturas de pantalla, Entrada + Seguridad + Relé de notificaciones para aplicaciones de terceros compatibles con UnifiedPush + Notificaciones + Período de gracia, Tono de llamada, Vibración, Extraños + Enviando + Recibiendo + Descarga automática + Apariencia + Modo claro/oscuro + Permitir capturas de pantalla + Cifrado de extremo a extremo + Organismos de certificación + Confiar en los certificados CA del sistema + Requerir enlace al canal + La vinculación de canales puede detectar algunos ataques al intermediario + Conexión al servidor + Sistema operativo + En el dispositivo + Texto grande + Aumentar el tamaño del texto en las burbujas de mensajes + Chats correspondientes archivados. + Colores dinámicos + Colores del sistema (Material You) + Cambiar al chat + Notificaciones de escritura, Visto por última vez, Disponibilidad + Iniciar un chat + El descubrimiento de canales utiliza un servicio de terceros llamado <a href=https://search.jabber.network>search.jabber.network</a>.<br><br>Usar esta función transmitirá tu dirección IP y términos de búsqueda a ese servicio. Consulte su <a href=https://search.jabber.network/privacy>Política de privacidad</a> para obtener más información. + ¡No se ha seleccionado ningún certificado de cliente! + Tamaño de archivo, Compresión de imagen, Calidad de vídeo + Mostrar el contenido de la aplicación en el conmutador de aplicaciones y permitir la realización de capturas de pantalla + Invitaciones de extraños + Aceptar invitaciones a chats grupales de extraños \ No newline at end of file diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index b824fe0eb1f12cbd866a6a516212c366b5b72b62..43e17bb31e4624bab18b201ba2bbb6f499253cf4 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -276,7 +276,7 @@ Ignora Attenzione: inviarlo senza aggiornamenti della presenza reciproci può causare problemi inaspettati.\n\nVai nei dettagli del contatto per verificare le tue sottoscrizioni alla presenza. Sicurezza - Correzione del messaggio + Correzione dei messaggi Consenti ai tuoi contatti di modificare retroattivamente i loro messaggi Impostazioni per esperti Fai attenzione con queste impostazioni @@ -566,8 +566,8 @@ Tema Seleziona il colore Automatica - Chiaro - Scuro + Chiara + Scura Impossibile connettersi a OpenKeychain Questo dispositivo non è più in uso Computer @@ -1050,11 +1050,11 @@ Sistema operativo Nome host e porta, Tor, scoperta dei canali Tastiera - Notifiche di coinvolgimento + Notifiche di partecipazione Applicazione Interazione Sul dispositivo - Quando si agisce come distributore di UnifiedPush, la connessione XMPP persistente, affidabile e a basso consumo di batteria viene usata per risvegliare altre app compatibili con UnifiedPush, come Tusky, Ltt.rs, FluffyChat ed altre. + Quando si agisce come distributore di UnifiedPush, viene usata la connessione XMPP persistente, affidabile e a basso consumo di batteria per risvegliare altre app compatibili con UnifiedPush, come Tusky, Ltt.rs, FluffyChat ed altre. L\'integrazione dell\'elenco di contatti non è disponibile Informativa sulla privacy Autorizzazione mancante per effettuare una chiamata diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index e32b862f3e13c6b1f4c19d9e0f14b904435347b4..1c8e801d70c6d86c03871b15144b657f73a4a824 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -39,10 +39,10 @@ 参加者 訪問者 連絡先リストから%sを削除しますか? この連絡先との会話は削除されません。 - %sからあなたに送信されるメッセージをブロックしますか? + %sからあなたに送信されるメッセージをブロックしますか ? %s のブロックを解除し、あなたにメッセージを送信できるようにしますか? - %sの連絡先をすべてブロックしますか? - %sのすべての連絡先のブロックを解除しますか? + %sの連絡先をすべてブロックしますか ? + %sのすべての連絡先のブロックを解除しますか ? 連絡先をブロックしました ブロックしました サーバーに新規アカウントを登録 @@ -69,7 +69,7 @@ 複数のアカウントに接続できません タップしてアカウントを管理 ファイルを添付 - 連絡先が連絡先リストにありません。リストに追加しますか? + 連絡先が連絡先リストにありません。リストに追加しますか ? 連絡先を追加 配信に失敗しました 送信用画像の準備中 @@ -83,7 +83,7 @@ ファイルを削除 このファイルを削除してもよろしいですか?\n\n警告: これは、他のデバイスやサーバーに保存されているファイルのコピーは削除しません。 デバイスを選択 - 暗号化されていないメッセージを送信 + 暗号化されないメッセージを送信 メッセージを送信 メッセージを %s に送信 v\\OMEMO 暗号化メッセージを送信 @@ -108,7 +108,7 @@ 自動的に小さいファイルを受取… 添付ファイル 通知 - 振動 + バイブレート 新着メッセージが届いたときに振動します LED 通知 新着メッセージが届いたときに通知ライトを点滅します @@ -121,7 +121,7 @@ 詳細 スタックトレースを送信すると、 開発の助けとなります メッセージを確認 - あなたがメッセージを受信して読んだときに、連絡先に知らせる + あなたがメッセージを受信して読んだときに、連絡先に知らせます スクリーンショットを防ぐ アプリスイッチャー内でアプリの内容を隠し、スクリーンショットを防ぐ UI @@ -148,7 +148,7 @@ オンライン 接続中\u2026 オフライン - 許可されていません + 認証されていません サーバーが見つかりません 接続なし 登録に失敗しました @@ -175,7 +175,7 @@ 出席情報告知から OpenPGP 公開鍵を削除してもよろしいですか?\n連絡先はあなたに OpenPGP 暗号化メッセージを送信できなくなります。 OpenPGP 公開鍵を公開しました。 アカウントを有効化 - アカウントを削除してよろしいですか?アカウントを削除すると会話履歴がすべて消去されます + アカウントを削除してよろしいですか ? アカウントを削除すると会話履歴がすべて消去されます 音声を録音 XMPP アドレス XMPP アドレスをブロック @@ -272,7 +272,7 @@ 無視 警告: 相互の出席情報アップデートなしにこれを送信すると、予期しない問題が発生する可能性があります。\n\nあなたの出席情報サブスクリプションを検証するために、“連絡先の詳細”に移動します。 セキュリティ - メッセージの修正を許可 + メッセージの修正 連絡先が、遡及的に自分のメッセージを編集することを許可します エキスパート設定 ご利用には注意してください @@ -401,7 +401,7 @@ Enter で送信 メッセージの送信に Enter キーを使用します。このオプションが無効でも、常に Ctrl+Enter でメッセージを送信できます。 Enter キーを表示 - 絵文字キーを Enter キーに変更 + 絵文字キーを Enter キーに変更します 音声 ビデオ 画像 @@ -419,7 +419,7 @@ %s さんが入力中… %s さんが入力を止めました 入力中通知 - あなたがメッセージを書いているときに、連絡先に知らせる + あなたがメッセージを書いているときに、連絡先に知らせます 位置を送信 位置を表示 位置を表示するアプリケーションが見つかりません @@ -436,7 +436,7 @@ %d個の証明書を削除しました - “送信”ボタンをクイックアクションで置き換える + “送信”ボタンをクイックアクションで置き換えます クイックアクション なし 最近使用した @@ -458,20 +458,20 @@ 壊れています 在席状況 デバイスがロックされているときは離席 - デバイスがロックされているときは離席と表示 + デバイスがロックされているときは離席と表示します サイレントモードのときは取込中 - デバイスがサイレントモードのときは取込中と表示 + デバイスがサイレントモードのときは取込中と表示します バイブレートをサイレントモードとして扱う - デバイスがバイブレートのときは取込中と表示 - 拡張接続設定 - アカウントを設定するときに、ホスト名とポートの設定を表示 + デバイスがバイブレートのときは取込中と表示します + ホスト名とポート番号 + アカウントを設定するときに、拡張設定項目を表示します xmpp.example.com 証明書でログイン 証明書を解析できません - アーカイブ設定 - サーバー側のアーカイブ設定 - アーカイブ設定を取得しています。しばらくお待ちください… - アーカイブ設定を取得できません + 保管の設定 + サーバー側の保管の設定 + 保管の設定を取得しています。しばらくお待ちください… + 保管の設定を取得できません キャプチャが要求されました 上の画像からテキストを入力してください 信頼できない証明書チェーン @@ -551,8 +551,8 @@ - ブロードキャストを使用 - Conversations を使用するときに、連絡先に知らせましょう + 最後に見たのは + 最後にこのアプリを使用したのがいつかを、連絡先に知らせます プライバシー テーマ カラーパレットの選択 @@ -674,7 +674,7 @@ 一度だけ QR コードスキャナーはカメラにアクセスが必要です 一番下へスクロール - メッセージ送信後に下へスクロール + メッセージ送信後に下へスクロールします ステータスメッセージを編集 ステータスメッセージを編集 暗号化が無効 @@ -743,7 +743,7 @@ 配信に失敗 メッセージ通知設定 着信通話の通知設定 - 重要性、音、振動 + 重要性、音、バイブレーション ビデオの圧縮 メディアを表示 参加者 @@ -768,20 +768,20 @@ %s を検証 %sにSMSを送りました。]]> 6桁のコードを含む別のSMSを送信しました。 - 以下に6桁の pin を入力してください。 + 以下に6桁の PIN を入力してください。 SMS 再送信 SMS 再送信(%s) しばらくお待ちください(%s) 戻る - クリップボードから可能な pin を自動的に貼り付ける。 - 6桁の pin を入力してください。 - 登録手続きを中止してもよろしいのですか? + クリップボードから可能な PIN を自動的に貼り付ける。 + 6桁の PIN を入力してください。 + 登録手続きを中止してもよろしいですか ? はい いいえ 検証しています… SMSを要求しています… - 入力された pin が正しくありません。 - 送信した pin の有効期限が切れています。 + 入力された PIN が正しくありません。 + 送信した PIN の有効期限が切れています。 未知のネットワークエラー。 サーバーからの不明な応答。 サーバーに接続できません。 @@ -875,8 +875,8 @@ アカウントを有効化してください 通話をする 着信通話 - 着信映像通話 - ビデオ通話に切り替えますか? + ビデオ通話の着信 + ビデオ通話に切り替えますか ? 接続中 接続しました 再接続中 @@ -912,7 +912,7 @@ %2$d人から%1$d件の不在着信 音声通話 - 映像通話 + ビデオ通話 ヘルプ マイクが利用できません 1度に1回線の通話のみ。 @@ -945,16 +945,16 @@ アプリケーションが見つかりません Conversationsに招待 招待を解析できません - サーバーは招待の作成をサポートしていません + サーバーは招待の作成に対応していません この機能をサポートするアクティブなアカウントがありません バックアップを開始しました。 バックアップが完了すると通知が届きます。 - 映像を有効化できません。 + ビデオを有効化できません。 プレーンテキスト文書 アカウント登録はサポートされていません XMPPアドレスがみつかりません 一時的な認証失敗 アバターを削除 - Tor使用中のため通話できません + Tor 使用中のため通話できません ビデオ通話へ切替 このアカウントをログアウトしました ルート @@ -993,9 +993,48 @@ 連絡先は未検証のデバイスを使用しています。 QR コードをスキャンして検証を実行し、アクティブな MITM 攻撃を阻止してください。 未検証のデバイスを使用しています。他のデバイスで QR コードをスキャンして検証を実行し、アクティブな MITM 攻撃を阻止してください。 電話をかける権限がありません - %s のブックマークを削除して会話を閉じますか ? + %s のブックマークを削除して会話を保管しますか ? %s のブックマークを削除しますか ? 通話の統合は利用できません。 連絡先は利用できません - 削除して閉じる + 削除して会話を保管 + クラッシュ報告を送信 + 対応する会話は保管されました。 + クライアント証明書が選択されていません。 + ホスト名とポート番号、Tor + このデバイスで + 猶予期間、着信音、バイブレーション、見知らぬ人 + 端末間暗号化、検証前の無条件の信頼、MITM検出 + 会話を保管 + 新しい会話 + この会話を保管 + システムの配色 (Material You) + 暗号化メッセージを送信 + 会話は保管されました + 多彩な会話の吹き出し + 会話に切り替え + インターフェース + テーマ、配色、スクリーンショット、入力 + 外観 + 明暗モード + スクリーンショットを許可 + アプリのスイッチャーでアプリのコンテンツを表示し、スクリーンショットの撮影を許可します + セキュリティ + 端末間暗号化 + 認証局 + 通知 + ファイルサイズ、画像圧縮、ビデオ品質 + 自動ダウンロード + 受信時 + 送信時 + 会話を開始 + アプリケーション + オペレーティングシステム + キーボード + サーバー接続 + システムの CA を信頼します + ホスト名とポート番号、Tor、チャンネル探索 + 送受信メッセージの個別の背景色 + メッセージの吹き出しのフォントサイズを大きくする + 大きなフォント diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index f1e23d47d4f11dc107e11bf17f1344f8813cac8a..f7a2868edeacdfc23a95f0a711c6538e59f76913 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -798,4 +798,8 @@ Onversleuteld document Accountregistraties zijn niet ondersteund Door crashrapportages te versturen help je de ontwikkeling + Inloggen met certificaat + Chat archiveren + Kan niet verbinden met OpenKeychain + Chat starten \ No newline at end of file diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index fd3cc97f82e97556e89745c87bc829bd96880fd0..9dd02cef9bff55aca26b70365757656899e05307 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -65,7 +65,7 @@ Zapisz Ok %1$s uległo awarii - Używając swojego konta XMPP do wysyłania śladów stosu pomagasz w rozwoju %1$s. + Używanie Twojego konta XMPP do wysyłania śladów stosu pomaga w ciągłym rozwoju %1$s. Wyślij teraz Nie pytaj ponownie Nie można połączyć z kontem @@ -122,7 +122,7 @@ Czas bez powiadomień Długość czasu kiedy powiadomienia są uśpione po wykryciu aktywności na jednym z twoich innych urządzeń. Zaawansowane - Wysyłając nam ślady stosu pomagasz w rozwoju + Wysyłając ślady stosu pomagasz w rozwoju Potwierdzenia wiadomości Zezwól na wysyłanie do osób z twojej listy kontaktów informacji o tym, kiedy otrzymałeś i przeczytałeś wiadomość od nich Zapobiegaj zrzutom ekranu @@ -527,9 +527,9 @@ Tylko duże obrazki Optymalizacje zużycia baterii włączone Twoje urządzenie ma włączone agresywne oszczędzanie baterii przez co %1$s może odbierać wiadomości z opóźnieniem.\nZalecamy wyłączenie tych optymalizacji. - Twoje urządzenie stosuje agresywne oszczędzanie baterii, przez co %1$s może odbierać wiadomości z opóźnieniem lub je tracić. + Twoje urządzenie stosuje agresywne oszczędzanie baterii, przez co %1$s może odbierać wiadomości z opóźnieniem lub nawet je tracić. \n -\nZostaniesz poproszony o jego wyłączenie. +\nZostaniesz poproszony o wyłączenie tej funkcjonalności. Wyłącz Zaznaczony obszar jest zbyt duży (Brak aktywynych kont) @@ -1048,7 +1048,7 @@ Archiwizuj rozmowę Nowa rozmowa Archiwizuj tę rozmowę - Kolorowe bańki rozmowy + Kolorowe dymki rozmowy Kod kreskowy nie zawiera odcisków palca dla tej rozmowy. Powiązane rozmowy zarchiwizowane. Rozpocznij rozmowę @@ -1091,7 +1091,7 @@ Aplikacja Interakcja Na urządzeniu - Zwiększ rozmiar czcionki w bańkach wiadomości + Zwiększ rozmiar czcionki w dymkach wiadomości Różne kolory tła dla wysłanych i odebranych wiadomości Wyślij zaszyfrowaną wiadomość Dołącz do rozmowy diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index 6f59306feb15280d42e942a26c46d21264a129c5..9eae2ce6c84c8f04ba937c6e7fb9da715eb67130 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -1016,7 +1016,7 @@ V-ați deconectat de la acest cont Conectați-vă Ascunde notificare - Persoana de contact utilizează dispozitive neverificate. Scanați codul QR al acestora pentru a efectua verificarea și a împiedica atacurile MITM active. + Persoana de contact utilizează dispozitive neverificate. Scanați codul QR al acesteia pentru a efectua verificarea și a împiedica atacurile MITM active. Deconectare Deconectat Folosiți dispozitive neverificate. Scanați codul QR pe celelalte dispozitive pentru a efectua verificarea și a împiedica atacurile MITM active. diff --git a/src/main/res/values-sq-rAL/strings.xml b/src/main/res/values-sq-rAL/strings.xml index fa0b62ec218393f1f5d71cd0281deea4e2cf4c2a..c37741aa94fa0e2d70d002797ba103d5951ac775 100644 --- a/src/main/res/values-sq-rAL/strings.xml +++ b/src/main/res/values-sq-rAL/strings.xml @@ -1022,7 +1022,7 @@ Doni të hiqet faqerojtësi për %s dhe arkivohet fjalosja? Fshije & Arkivoje fjalosjen Doni të hiqet faqerojtësi për %s? - Flluska të ngjyrosura fjalosjeje ndihmojnë të dalloni mesazhe të dërguar dhe të marrë + Ngjyra të dallueshme sfondi për mesazhe të dërguar dhe të marrë Ngjyra dinamike Ngjyra Sistemi (Material You) U ngjit automatikisht nga e papastra PIN i mundshëm. @@ -1074,4 +1074,8 @@ Kur veprohet si një Distributor UnifiedPush, për të zgjuar aplikacion tjetër të përputhshëm me UnifiedPush , fjala vjen, Tusky, Ltt.rs, FluffyChat, etj, do të përdoret lidhja XMPP, e vazhdueshme, e qëndrueshme dhe miqësore ndaj baterisë. Distributor UnifiedPush Dërgo mesazh të fshehtëzuar + Ftesa nga të panjohur + Prano ftesa për fjalosje në grup nga të panjohur + Shkronja të mëdha + Rrit madhësi shkronja në flluska mesazhesh \ No newline at end of file diff --git a/src/main/res/values-tr-rTR/strings.xml b/src/main/res/values-tr-rTR/strings.xml index 7f46296f2836a6d192b5f5b80208e82a30d3f323..3128b7b515ea6d52c5b31d60373addc942633027 100644 --- a/src/main/res/values-tr-rTR/strings.xml +++ b/src/main/res/values-tr-rTR/strings.xml @@ -26,8 +26,8 @@ 1 dakika önce %d dakika önc - %d okunmamış konuşma - %d okunmamış konuşmalar + %d okunmamış sohbet + %d okunmamış sohbet gönderiyor… İleti deşifre ediliyor. Lütfen bekleyin… @@ -39,7 +39,7 @@ Moderatör Katılımcı Ziyaretçi - %s adlı kişiyi listenizden çıkarmak ister misiniz? Bu kişi ile olan konuşmalar silinmeyecektir. + %s adlı kişiyi listenizden çıkarmak ister misiniz? Bu kişi ile olan sohbet silinmeyecektir. %s kişisinin size ileti göndermesini engellemek istiyor musunuz? %s kişisinin size ileti göndermesine koyduğunuz engellemeyi kaldırmak ve size ileti göndermesine izin vermek istiyor musunuz? %s üzerinden gelen tüm kişileri engellemek istiyor musunuz? @@ -77,7 +77,7 @@ Görüntüler gönderilmeye hazırlanılıyor Dosyalar Paylaşılıyor. Lütfen bekleyin… Geçmişi sil - Konuşma geçmişini sil + Sohbet geçmişini sil Bu konuşmadaki tüm mesajları silmek istiyor musunuz? \n \nUyarı: Bu eylem, diğer aygıt ve sunucularda kayıtlı mesajları etkilemeyecektir. @@ -176,7 +176,7 @@ OpenPGP genel anahtarınız Çevrim içi durum anonsunuzdan kaldırmak istediğinizden emin misiniz?\nArtık kişileriniz size şifrelenmiş OpenPGP mesajları gönderemeyecek. OpenPGP genel anahtarı yayınlandı. Hesabı etkinleştir - Hesabınızı silmekten emin misiniz? Hesabınızın silinmesi bütün konuşma geçmişinizi siler + Hesabınızı silmek istediğinizden emin misiniz? Bütün sohbetleriniz silinecektir Ses kaydet XMPP adresi XMPP adresini engelle @@ -277,7 +277,7 @@ Yok say Uyarı: Bu mesajı karşılıklı çevrim içi durum bildirimleri olmadan göndermek, beklenmeyen problemlere neden olabilir. \n\n Çevrim içi durum aboneliklerini doğrulamak için \"Kişi Bilgileri\" kısmına gidin. Güvenlik - İleti düzeltmeye izin ver + Mesaj düzeltme Kişilerinizin geçmiş iletilerini düzeltmelerine izin ver Uzman seçenekleri Lütfen dikkatli olun @@ -313,8 +313,8 @@ XMPP adresi panoya kopyalandı Hata mesajı panoya kopyalandı web adresi - 2B Barkod Tara - 2B Barkod Göster + QR Kodu tara + QR Kodu göster Engellenenler listesini göster Hesap bilgileri Doğrula @@ -357,7 +357,7 @@ OMEMO bildirimindeki diğer aygıtların hepsini silmek istediğinizden emin misiniz? Aygıtlarınız yeniden bağlandıklarında kendilerini yeniden bildirecekler ama bu süre zarfındaki iletileri alamayabilirler. Bu kişi için kullanılabilecek bir anahtar bulunmuyor.\nSunucudan yeni anahtarlar alınamıyor. Belki bağlantınızın sunucusunda bir sorun vardır? Bu kişi için kullanılabilir bir anahtar yok. -\nİkinizin de çevrim içi durum aboneliği oldudğundan emin olun. +\nİkinizin de çevrimiçi durum aboneliği olduğundan emin olun. Bir şeyler ters gitti Sunucudan geçmiş alınıyor Sunucuda başka geçmiş kalmadı @@ -470,8 +470,8 @@ Telefonunuz sessizdeyken, durum bildiriminizi müsait değil olarak gösterir. Titreşim kipini sessiz kip olarak değerlendir Telefonunuz titreşimdeyken, durum bildiriminizi müsait değil olarak gösterir. - Genişletilmiş bağlantı seçenekleri - Hesap oluştururken sunucu adıyla bağlantı noktası seçeneğini göster + Barındırıcı adı ve Port + Hesap oluştururken gelişmiş bağlantı ayarlarını göster xmpp.ornek.com Sertifika ile giriş yap Sertifika çözümlenemedi @@ -507,11 +507,10 @@ Metin %s ile paylaşıldı %1$s\'ın harici depolama erişimine izin ver %1$s\'ın kamera erişimine izin ver - Kişilerle senkronize et - %1$s XMPP listenizi telefon rehberinizle eşleştirmek için izin istiyor. -\nBöylelikle, tüm rehberinizindeki kişilerin tam adları ve avatarlarını görebileceksiniz. + Kişiler entegrasyonu + %1$s kişilerinizi yerel olarak, cihazınızda, XMPP\'de bulunan kişilerinizin isimlerini ve profil fotoğraflarını göstermek için işler. \n -\n%1$s kişilerinizi sunucunuza yüklemeyecek olup, sadece cihazınız üzerinden eşleştirme yapacaktır. +\nKişileriniz hakkında hiçbir bilgi cihazınızdan ayrılmaz! Tüm iletilerde uyar Yalnızca bahsedilğinde haber ver Uyarılar devre dışı @@ -529,13 +528,14 @@ Bu alan zorunludur ileti düzelt Düzeltilmiş iletiyi gönder - Bu kişinin güvenini doğrulamak için parmak izini zaten güvenle onayladınız. \"Tamam\"ı seçerek sadece%s kişisinin, grup konuşmasının bir parçası olduğunu doğruluyorsunuz. + Bu kişinin parmak izine zaten güvenmiştiniz. \"Tamam\"ı seçerek sadece %s\'in bu grupta olduğunu onaylamış olacaksınız. Bu hesabı devre dışı bıraktınız Güvenlik hatası: Geçersiz dosya erişimi! URL paylaşacak uygulama bulunamadı URI paylaş ile… Kabul et ve devam et - Conversations\'da hesap kurulum için bir rehber hazırlanmıştır.¹\nConversations.im\'i bir sağlayıcı olarak seçtikten sonra başka sağlayıcılar kullanan kullanıcılarla onlara tam XMPP adresinizi vererek iletişim kurabilirsiniz. + Conversations.im üzerinde hesap oluşturma rehberi bulunmaktadır. +\nConversations.im\'i sağlayıcınız olarak seçtiğinizde diğer sağlayıcıların kullanıcılarıyla tam XMPP adresinizi paylaşarak iletişim kurabileceksiniz. Tam XMPP adresiniz %s olacak Hesap Oluştur Kendi sağlayıcımı kullan @@ -559,8 +559,8 @@ Kısa Orta Uzun - Kullanımı yayınla - Kişiler Conversations\'ı kullandığınız zaman bundan haberdar olur. + Son görülme + Kişilerinizin uygulamayı en son ne zaman kullandığınızı görmesine izin verin Gizlilik Gövde Renk paletini seçin @@ -607,7 +607,7 @@ Doğrulanmamış kişilerin tüm yani aygıtlarına güven, ama doğrulanmış kişilerden aygıtlarını onaylamalarını iste. OMEMO anahtarlarına körlemisne güvenildi, yani bu kişi bir başkası olabilir veya biri konuşmayı dinleyebilir. Güvenilmeyen - Geçersiz 2D barkod + Geçersiz QR Kodu Önbellek dizinini temizle (Kamera uygulamasının kullandığı) Önbelleği temizle Özel depolama alanını temizle @@ -696,14 +696,14 @@ Aygıt listesi alınamadı Şifreleme anahtarları alınamadı İpucu: Kimi durumlarda bu sorun, birbirinizi kişi listenize eklemenizle çözülebilir. - Bu konuşma için OMEMO şifrelemesini devre dışı bırakmak istediğinizden emin misiniz? -\nBu, sunucu yöneticinizin mesajlarınızı okumasını mümkün kılsa da, tarihi geçmiş istemcileri kullanan insanlarla iletişim kurmanın tek yolu olabilir. + Bu sohbet için OMEMO şifrelemesini devre dışı bırakmak istediğinizden emin misiniz? +\nBunu yapmanız sunucu yöneticinizin mesajlarınızı okumasına izin verecektir, fakat güncel olmayan istemcileri kullanan kişilerle konuşmanın tek yolu bu olabilir. Şimdi devre dışı bırak Taslak: OMEMO Şifrelemesi Bire bir ve grup konuşmalarında her zaman OMEMO kullanılacak. - Yeni konuşmalarda OMEMO varsayılan olarak kullanılacak. - Özellikle yeni konuşmalarda OMEMO aktif hale getirilecek. + Yeni sohbetlerde OMEMO varsayılan olarak kullanılacak. + Yeni sohbetlerde OMEMO\'nun el ile aktifleştirilmesi gerekecektir. Kısayol oluştur Varsayılan olarak aktif Varsayılan olarak devre dışı @@ -724,14 +724,14 @@ %1$s\'ın mikrofon erişimine izin ver İleti ara GIF - Konuşma görüntüle + Sohbeti görüntüle Konum Eklentisini Paylaş Varolan harita yerine Konum Eklentisini Paylaş\'ı kullan Web adresini kopyala XMPP adresini kopyala S3 için HTTP Dosya Paylaşımı Doğrudan arama - \'Konuşma Başlat\" ekranında klavyeyi aç ve arama kısmına imleci getir + \"Yeni sohbet\" ekranında klavyeyi arama kutusunda aç Grup konuşması avatarı Yönetici grup konuşması avatarlarını desteklemiyor Yalnızca yönetici grup konuşması avatarını değiştirebilir @@ -783,13 +783,13 @@ %s doğrula %s telefonunuza bir SMS gönderdik.]]> Size 6 haneli kodun olduğu başka bir SMS gönderdik. - Lüfen aşağıya 6 haneli kodu girin. + Lütfen aşağıya 6 haneli kodu girin. Tekrar sms gönder Tekrar sms gönder (%s) Lütfen bekleyin (%s) - geri - Olası kod, otomatik olarak panodan yapıştırıldı. - Lütfen 6 haneli kodu girin. + Geri + Olası kod, otomatik olarak yapıştırıldı. + Lütfen 6 haneli kodunuzu girin. Kayıt sürecini iptal etmek istediğinizden emin misiniz? Evet Hayır @@ -940,8 +940,8 @@ En baştan kaldır GPX izi İleti düzeltilemedi - Bütün konuşmalar - Bu konuşma + Tüm sohbetler + Bu sohbet Avatarınız %s avatarı OMEMO ile şifrelendi @@ -978,4 +978,92 @@ Videoya geç Videoya geçme isteğini reddet XMPP Hesabı + Oturum kapatıldı + Bildirim Sunucusu + Çökme raporları gönder + Sohbete Katıl + Quicksy\'e hoşgeldiniz! + Kendiniz oluşturmadığınız yedekleri geri yüklemeye çalışmayın! + Oturumu kapat + Bildirimi gizle + Spam bildir + İstemci sertifikası seçilmedi! + Oturum aç + Arayüz + Tema, Renkler, Ekran Görüntüleri, Girdi + Açık/Koyu tema + Yazıyor bildirimi, Son görülme, Müsaitlik + Barındırıcı adı ve Portu, Tor + Barındırıcı adı ve Portu, Tor, Kanal Keşfetme + Kişiler entegrasyonu mevcut değil + Gizlilik politikası + Şununla paylaş… + Gelen arama (%s) · %s + Giden arama (%s) · %s + Giden arama · %s + Doğrulanmamış bir cihaz kullanıyorsunuz. Aktif MITM (Ortadaki Adam) saldırılarını önlemek için diğer cihazlarınızda QR kodunu okutarak doğrulama yapın. + Spam bildir ve kişiyi engelle + Sohbeti sil ve arşivle + Sohbet başlat + Kişiniz doğrulanmamış bir cihaz kullanıyor. Aktif MITM (Ortadaki Adam) saldırılarını önlemek için kişinizin QR kodunu okutarak doğrulama yapın. + Arama entegrasyonu mevcut değil! + Sohbeti arşivle + Bu sohbeti arşivle + Sohbete git + Bildirimlerin alınacağı hesap. + UnifiedPush dağıtıcısı + Hiçbiri (devre dışı) + Yeni sohbet + Görüntülü aramaya geçilsin mi? + Uygulama değiştiricide içeriği göster ve ekran görüntüsü almaya izin ver + %s için yer imini kaldırmak ve sohbeti arşivlemek ister misiniz? + Grup olarak kaydet + Sesli kitap + Şifrelenmiş ileti gönder + Gruplar + Quicksy, verilerinizi kullanmak için izninizi istiyor + Bu hesabın oturumunu kapatmışsınız + Renkli konuşma balonları + Gönderilen ve alınan mesajlar için farklı renkler kullan + Arama yapma izni yok + Uygun gelen sohbetler arşivlendi + Dinamik renklendirme + Sistem renkleri (Material You) + Kişi müsait değil + Reddet + Hesabı sunucudan sil + Hesap sunucudan silinemedi + Güvenlik + UnifiedPush destekleyen üçüncü parti uygulamalar için bildirim aktarıcısı + Bildirimler + Dosya büyüklüğü, Resim sıkıştırma, Video kalitesi + Yok sayma süresi, Zil sesi, Titreşim, Yabancılar + Gönderme + Teslim alma + Otomatik indirme + Görünüm + Ekran görüntüsü almaya izin ver + Uçtan uca şifreleme + Sertifika yetkilileri + Sistemin CA sertifikalarına güven + Kanal bağlamayı zorunlu kıl + Kanal bağlama bazı aradaki makine saldırılarını tespit edebilir + Sunucu bağlantısı + İşletim Sistemi + Klavye + Uygulama + Etkileşim + Bu Cihazda + Büyük punto + Konuşma balonlarındaki yazı büyüklüğünü arttır + %s için yer imini kaldırmak ister misiniz? + Bir gruba katılırken \"autojoin\" (otomatik katıl) işaretini ayarla ve diğer istemciler tarafından yapılan değişikliklere tepki ver. + Sohbet arşivlendi + Grup ara + Barkod, bu sohbet için parmak izi bilgisi barındırmıyor. + Grup keşfetme <a href=https://search.jabber.network>search.jabber.network</a> adlı bir hizmeti kullanır.<br><br>Bu özelliği kullanmanız IP adresinizi ve aramalarınızı bu hizmete gönderecektir. Daha fazla bilgi için hizmetin <a href=https://search.jabber.network/privacy>Gizlilik Politikasına</a> göz atın. + Artık desteklenmeyen bir yedek dosyası türünü geri yüklemeye çalışıyorsunuz + Uçtan Uca Şifreleme, Doğrulamadan Körü Körüne Güven, MITM Algılama + Etkileşim bildirimleri + UnifiedPush Dağıtıcısı olarak davranılırken devamlı, güvenli ve pil ömrü dostu olan XMPP bağlantısı diğer UnifiedPush ile uyumlu olan Tusky, Ltt.rs, FluffyChat ve benzeri uygulamaları uyandırmak için kullanılacaktır. \ No newline at end of file diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index f0e771ef23ed8b2da7d7f5fbda937aa4cf4e45b2..ea39060af51053c7f77debbc8db5a1b7d9190e7a 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -646,7 +646,7 @@ Сповіщення від незнайомців Сповіщати про повідомлення і виклики від незнайомців. Отримано повідомлення від незнайомця - Заблокувати невідомий контакт + Заблокувати незнайомців Заблокувати весь домен зараз у мережі Спробувати знову розшифрувати diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index d5d2e5c9f27aecbde3a99eaee4369a5de6350e15..699d26d5cb185e58f44aaec6a4ead3b7031d9fb2 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -1022,7 +1022,7 @@ 频道发现使用称为 <a href=https://search.jabber.network>search.jabber.network</a> 的第三方服务。<br><br>使用此功能会将您的 IP 地址和搜索词传输到此服务。请参阅其 <a href=https://search.jabber.network/privacy>隐私政策</a> 以获取更多信息。 开始聊天 分享至… - 已发送和已接收消息的背景颜色各不相同 + 为已发送和已接收消息使用不同的背景颜色 未选择客户端证书! 彩色聊天气泡 动态色彩 diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml index 3fdd692237b4a759fa7ae64810fe2b85bdcafac3..7300d8c0144772b319401bc30ebc5702576416fd 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -78,12 +78,12 @@ 清除會話記錄 刪除檔案 選擇裝置 - 傳送未加密的訊息 + 傳送明文訊息 傳送訊息 傳送訊息至 %s 傳送 v\\OMEMO 加密訊息 新暱稱已被使用 - 不加密傳送 + 傳送明文 解密失敗,可能是私密金鑰不正確。 OpenKeychain 重新啟動 @@ -254,7 +254,7 @@ 立即要求 忽略 安全性 - 允許訊息修正 + 訊息修正 允許您的聯絡人追溯編輯他們的訊息 專家設定 請謹慎使用 @@ -290,8 +290,8 @@ 已複製 XMPP 位址到剪貼簿 已複製錯誤訊息到剪貼簿 網頁位址 - 掃描二維條碼 - 顯示二維條碼 + 掃描 QR 碼 + 顯示 QR 碼 顯示封鎖清單 帳戶詳細資料 確認 @@ -441,8 +441,8 @@ 靜音模式時顯示為忙碌 靜音模式開啟震動 裝置震動時顯示為忙碌 - 進階連線設定 - 註冊帳戶時顯示主機名稱和連接埠設定 + 主機名和端口 + 註冊帳戶時顯示進階連線設定 xmpp.example.com 以憑證登入 無法解析憑證 @@ -477,7 +477,7 @@ 與 %s 分享的文字 授予 %1$s 外部儲存空間存取權 授予 %1$s 相機存取權 - 同步處理聯絡人 + 整合通訊錄 通知所有訊息 僅在被提及時通知 通知已停用 @@ -564,7 +564,7 @@ 分享為 HTTP 連結 驗證前盲目信任 未受信任 - 無效的二維條碼 + 無效的 QR 碼 清理快取 清理私人儲存空間 清理儲存檔案的私人空間 (檔案可從伺服器重新下載) @@ -641,7 +641,7 @@ OMEMO 加密 OMEMO 將一律用於一對一和私人群組聊天。 OMEMO 將預設用於新會話。 - OMEMO 將明確用於新會話。 + OMEMO 將需要明確地用於新會話。 建立捷徑 預設開啟 預設關閉 @@ -669,7 +669,7 @@ 複製 XMPP 位址 用於 S3 的 HTTP 檔案分享 直接搜尋 - 在「開始對話」畫面上開啟鍵盤並將遊標放在搜尋欄位 + 在「開始會話」畫面上開啟鍵盤並將遊標放在搜尋欄位 群組聊天頭像 主機不支援群組聊天頭像 只有擁有者才能變更群組聊天頭像 @@ -800,7 +800,7 @@ 附加 探索頻道 搜尋頻道 - 可能違反隱私權! + 可能會侵犯隱私! 我已經有一個帳戶 新增現有帳戶 註冊新帳戶 @@ -905,7 +905,7 @@ 暫時驗證失敗 刪除頭像 使用 Tor 時通話已停用 - 在您使用 Conversations 時讓您的聯絡人知道 + 讓您的聯絡人知道您最後使用此應用的時間 您確定要刪除此會話中的所有訊息嗎? \n \n警告:這將不會影響儲存在其他裝置或伺服器上的訊息。 @@ -935,16 +935,15 @@ \n前往「聯絡人詳細資料」以驗證您的線上狀態訂閱。 加入或離開多使用者聊天時設定「自動加入」旗標,並回應其他用戶端所做的修改。 您確定要從 OMEMO 宣告中清除所有裝置嗎?您的裝置在下次連線時將會重新宣告,但可能不會收到您傳送的訊息。 - %1$s想要您通訊錄的存取權以將其與您的 XMPP 聯絡人清單相符。 -\n這將顯示您聯絡人的完整名稱和頭像。 + %1$s 在您的設備上本地處理您的通訊錄,以向您顯示 XMPP 上相符的聯絡人名稱和個人資料頭像。 \n -\n%1$s僅會讀取您的通訊錄並在本機進行相符處理,不會上傳任何內容至您的伺服器。 +\n任何通訊錄數據都不會離開您的設備! 您的裝置正在為 %1$s 採用強力電池效能最佳化,這可能會導致通知延遲甚至訊息遺失。 \n \n您將被要求將其停用。 您的作業系統正在限制 %1$s 在背景存取網際網路。若要接收新訊息的通知,您應該允許 %1$s 在「數據節省」開啟時無限制地存取。 \n在可能的狀況下,%1$s 仍會努力地節省數據。 - 廣播使用 + 最後在線 您確定要刪除此檔案嗎? \n \n警告:這將不會影響儲存在其他裝置或伺服器上的檔案複本。 @@ -957,7 +956,7 @@ 在您的其他裝置上偵測到活動後,通知被靜音的時間長度。 透過傳送堆疊追蹤,您可以協助開發 您用來選取此圖像的應用程式沒有足夠的權限以讀取此檔案。 - UnifiedPush 散發者 + UnifiedPush 散發程序 XMPP 帳戶 推送伺服器 無 (已停用) @@ -1000,11 +999,74 @@ 登入 隱藏通知 在其他主機上重新連接 - 您的聯絡人使用未驗證的設備。掃描他們的二維條碼進行驗證,以防止主動中間人攻擊。 + 您的聯絡人使用未驗證的設備。請掃描他們的 QR 碼進行驗證,以防止主動中間人攻擊。 登出 您正在嘗試匯入一個過時的備份檔案格式 已登出 - 您正在使用未驗證的設備。掃描您其他設備上的二維條碼進行驗證,以防止主動中間人攻擊。 + 您正在使用未驗證的設備。請掃描您其他設備上的 QR 碼進行驗證,以防止主動中間人攻擊。 有聲書 請勿嘗試還原非您自己建立的備份! + 報告垃圾訊息 + 發送崩潰報告 + 相應的會話已存檔。 + 聯絡人整合不可用 + 隱私權政策 + 報告垃圾訊息並封鎖垃圾訊息發送者 + 刪除並存檔會話 + 用戶界面 + 未選擇用戶端證書! + 安全性 + 文件大小、圖片壓縮、影片質量 + 兼容 UnifiedPush 的第三方應用的通知轉發器 + 通知 + 證書頒發機構 + 信任系統的 CA 證書 + 需要通道綁定 + 通道綁定可以檢測某些中間人攻擊 + 伺服器連接 + 作業系統 + 輸入通知、最後在線、在線狀態 + 主機名和端口、Tor + 主機名和端口、Tor、頻道探索 + 鍵盤 + 參與通知 + 應用程序 + 互動 + 端對端加密、驗證前盲目信任、中間人攻擊檢測 + 分享至… + 新會話 + 存檔會話 + 存檔此會話 + 切換到會話 + 傳送加密消息 + Quicksy 請求您同意使用您的數據 + 參與會話 + 歡迎使用 Quicksy! + 彩色聊天氣泡 + 沒有撥打電話的權限 + 二維碼並不包含此會話的指紋。 + 系統色彩 (Material You) + 聯絡人不可用 + 通話整合不可用! + 開始會話 + 主題、配色、截屏、輸入 + 靜默期、鈴聲、震動、陌生人 + 發送 + 接收 + 自動下載 + 外觀 + 淺色/深色模式 + 允許截屏 + 在切換應用時顯示應用內容並允許截屏 + 端對端加密 + 在設備上 + 在充當 UnifiedPush 散發者時,將利用持久、可靠且省電的 XMPP 連接來喚醒其他兼容 UnifiedPush 的應用,如 Tusky、Ltt.rs、FluffyChat 等。 + 會話已存檔 + 為已發送和已接收訊息使用個別的背景顏色 + 大字體 + 增加訊息氣泡中的字體大小 + 是否移除 %s 的書籤? + 是否移除 %s 的書籤並存檔會話? + 自動配色 + 頻道探索使用一個名為 <a href=https://search.jabber.network>search.jabber.network</a> 的第三方服務,<br><br>使用此功能會將您的 IP 位址和搜尋詞彙傳送至此服務。更多資訊請參見他們的 <a href=https://search.jabber.network/privacy>隱私權政策</a>。 diff --git a/src/main/res/values/arrays.xml b/src/main/res/values/arrays.xml index 594b432922ad88ec4760860699d25750ddfddcf2..d457da458089a6e14822aa5b3fe7868e6d568e52 100644 --- a/src/main/res/values/arrays.xml +++ b/src/main/res/values/arrays.xml @@ -86,6 +86,13 @@ 2592000 15811200 + + 0 + 86400 + 172800 + 604800 + 2592000 + always default_on diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index a8a34743bddedf68f699cbee520993b4db4176bb..cf755759852458768d5545818b21482bef9cb7bb 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -1066,5 +1066,10 @@ When acting as a UnifiedPush Distributor the persistent, reliable and battery-friendly XMPP connection will be utilized to wake up other UnifiedPush compatible app such as Tusky, Ltt.rs, FluffyChat and more. Large font Increase font size in message bubbles - + Create one-off, Schedule recurring + Create one-off backup + Recurring backup + Full screen notifications + Allow this app to show incoming call notifications that take up the full screen when the device is locked. + Unsupported operation diff --git a/src/main/res/xml/preferences_backup.xml b/src/main/res/xml/preferences_backup.xml new file mode 100644 index 0000000000000000000000000000000000000000..b0362c7856b02349c266f04c63359e97e9634a51 --- /dev/null +++ b/src/main/res/xml/preferences_backup.xml @@ -0,0 +1,20 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/xml/preferences_main.xml b/src/main/res/xml/preferences_main.xml index 8e5bf1155ed428efae7ad5197e5f1e7be5da49ac..cdef97d2909ba9b9c8c0dac9a17f39c768f134a4 100644 --- a/src/main/res/xml/preferences_main.xml +++ b/src/main/res/xml/preferences_main.xml @@ -34,9 +34,10 @@ app:title="@string/pref_connection_options" /> + android:key="backup" + app:fragment="eu.siacs.conversations.ui.fragment.settings.BackupSettingsFragment" + android:summary="@string/pref_backup_summary" + android:title="@string/backup" /> + diff --git a/src/playstore/java/eu/siacs/conversations/services/PushMessageReceiver.java b/src/playstore/java/eu/siacs/conversations/receiver/PushMessageReceiver.java similarity index 74% rename from src/playstore/java/eu/siacs/conversations/services/PushMessageReceiver.java rename to src/playstore/java/eu/siacs/conversations/receiver/PushMessageReceiver.java index f060747a5bd981bb057d981dd7071916466e2b7d..40812c10d910b0748c4b8ae2e471b0fbcecb96ee 100644 --- a/src/playstore/java/eu/siacs/conversations/services/PushMessageReceiver.java +++ b/src/playstore/java/eu/siacs/conversations/receiver/PushMessageReceiver.java @@ -1,21 +1,24 @@ -package eu.siacs.conversations.services; +package eu.siacs.conversations.receiver; import android.content.Intent; import android.util.Log; +import androidx.annotation.NonNull; + import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; import java.util.Map; import eu.siacs.conversations.Config; +import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.Compatibility; public class PushMessageReceiver extends FirebaseMessagingService { @Override - public void onMessageReceived(RemoteMessage message) { - if (!EventReceiver.hasEnabledAccounts(this)) { + public void onMessageReceived(@NonNull final RemoteMessage message) { + if (!SystemEventReceiver.hasEnabledAccounts(this)) { Log.d(Config.LOGTAG,"PushMessageReceiver ignored message because no accounts are enabled"); return; } @@ -27,8 +30,8 @@ public class PushMessageReceiver extends FirebaseMessagingService { } @Override - public void onNewToken(String token) { - if (!EventReceiver.hasEnabledAccounts(this)) { + public void onNewToken(@NonNull final String token) { + if (!SystemEventReceiver.hasEnabledAccounts(this)) { Log.d(Config.LOGTAG,"PushMessageReceiver ignored new token because no accounts are enabled"); return; } diff --git a/src/quicksy/fastlane/metadata/android/tr-TR/short_description.txt b/src/quicksy/fastlane/metadata/android/tr-TR/short_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..5ce53527e57cca8aa38fb86b68478e1364501c1d --- /dev/null +++ b/src/quicksy/fastlane/metadata/android/tr-TR/short_description.txt @@ -0,0 +1 @@ +Kolay giriş ve keşfetmeli Jabber/XMPP diff --git a/src/quicksy/res/values-pl/strings.xml b/src/quicksy/res/values-pl/strings.xml index d94b0c7750eb36d77ecbb2162c694b42fe5ba038..fb4d545992b89c651f0a0806d53347cb1e87800c 100644 --- a/src/quicksy/res/values-pl/strings.xml +++ b/src/quicksy/res/values-pl/strings.xml @@ -1,7 +1,7 @@ Czas, przez który Quicksy jest cicho po zobaczeniu aktywności na innym urządzeniu - Wysyłając nam ślady stosu pomagasz w rozwoju Quicksy + Wysyłając ślady stosu pomagasz w ciągłym rozwoju Quicksy Powiadom kontakty o tym że używasz Quicksy Aby otrzymywać powiadomienia nawet kiedy ekran jest wyłączony musisz dodać Quicksy do listy chronionych aplikacji. Obrazek profilowy Quicksy